Compare commits

...

25 Commits

Author SHA1 Message Date
Leonid
c94fd93c2d Fix BSC RPC exceed maximum block range 2021-04-11 18:30:39 +03:00
Leonid Tyurin
ae83c76be9 Fix monitor metrics (#533) 2021-04-02 03:59:49 -06:00
Kirill Fedoseev
dc3026e584 Fix undefined is not an object exception (#535) 2021-03-26 12:16:23 -06:00
Kirill Fedoseev
b6ba0744b9 Fix ALM react subscriptions leading to duplicated RPC requests (#534) 2021-03-18 00:04:33 -06:00
Kirill Fedoseev
4dba9a50e8 Extract tx resend intervals in env variables (#532) 2021-03-17 21:48:40 -06:00
Kirill Fedoseev
818bc4675d Fallback eth_getLogs to explorer API for BSC (#530) 2021-03-14 07:39:23 -06:00
Alexander Kolotov
f93ab330cc Merge the develop branch to the master branch, preparation to v2.7.0-rc0 (#515)
This merge contains the following set of changes:
  * [Improvement] Add /metrics endpoint for prometheus support (#512)
  * [Improvement] Add monitor script for detecting failed AMB messages (#513)
  * [Improvement] Improve performance of BS requests in ALM (#516)
  * [Fix] Add pretty error messages for manual execution transaction reverts (#511)
  * [Fix] Fix resend of stuck pending transactions (#514)
2021-02-25 20:42:51 -06:00
Leonid Tyurin
f64f8b1c91 Add /metrics endpoint for prometheus support (#512) 2021-02-25 20:39:48 -06:00
Kirill Fedoseev
9fd3f6ab82 Improve performance of BS requests in ALM (#516) 2021-02-25 20:38:13 -06:00
Kirill Fedoseev
626f9376b2 Fix resend of stuck pending transactions (#514) 2021-02-24 16:11:28 -06:00
Kirill Fedoseev
894134ba26 Add monitor script for detecting failed AMB messages (#513) 2021-02-18 19:26:07 -06:00
Kirill Fedoseev
e1536755f4 Add pretty error messages for manual execution transaction reverts (#511) 2021-02-18 19:25:01 -06:00
Alexander Kolotov
0451d6e373 Merge the develop branch to the master branch, preparation to v2.6.0 2021-01-11 20:17:53 -06:00
Kirill Fedoseev
409044b8a5 Add env variables for selective validator balance checks (#507) 2021-01-10 20:18:06 -06:00
Kirill Fedoseev
5fc52f42d7 ALM: fix blocking Blockscout/Etherscan requests (#505) 2021-01-07 13:35:23 -06:00
Alexander Kolotov
8a0d9f38b0 Added mediator endpoint in the monitor web service (#503) 2020-12-24 19:15:51 +03:00
Alexander Kolotov
1aee0a84ef Merge the develop branch to the master branch, preparation to v2.6.0-rc3 2020-12-22 10:58:25 +03:00
Alexander Kolotov
811b1a27f1 setLogger added to the RedundantHttpListProvider (#501) 2020-12-21 22:16:37 +03:00
Kirill Fedoseev
4d468ae107 Detect all AMB mediators in monitor (#493) 2020-12-20 01:19:49 +03:00
Kirill Fedoseev
4497a024b1 Fix RPC urls in the ultimate tests (#498) 2020-12-20 01:15:46 +03:00
Kirill Fedoseev
6ce98ff3dd Fetch signatures from RPC endpoint immediatly after enough signatures are collected (#499) 2020-12-20 01:13:49 +03:00
Kirill Fedoseev
04f66b243c Handle RPC error about ancient blocks (#500) 2020-12-20 01:10:47 +03:00
Kirill Fedoseev
21581b3c01 Change message status when signatures are manually submitted (#497) 2020-12-07 23:12:30 +03:00
Kirill Fedoseev
bbc68f9fa2 ALM manual signatures execution (#471) 2020-11-26 00:22:21 +03:00
Kirill Fedoseev
5327688a20 Handle JSONRPC error codes in web3 providers (#491) 2020-11-12 02:20:03 +03:00
92 changed files with 2407 additions and 1604 deletions

View File

@@ -182,6 +182,7 @@ jobs:
deployment: deployment:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- build-e2e-images
- build-molecule-runner - build-molecule-runner
strategy: strategy:
fail-fast: false fail-fast: false
@@ -197,11 +198,11 @@ jobs:
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }} run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- run: deployment-e2e/molecule.sh ${{ matrix.task }} - run: deployment-e2e/molecule.sh ${{ matrix.task }}
ultimate: ultimate:
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags') || contains(github.event.head_commit.message, 'ultimate')
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- initialize - initialize
- build-e2e-images - build-e2e-images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags')
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -209,12 +210,16 @@ jobs:
include: include:
- task: erc-to-erc - task: erc-to-erc
ui-e2e-grep: 'ERC TO ERC' ui-e2e-grep: 'ERC TO ERC'
ui-config: 'e2e-commons/components-envs/ui-erc20.env'
- task: erc-to-native - task: erc-to-native
ui-e2e-grep: 'ERC TO NATIVE' ui-e2e-grep: 'ERC TO NATIVE'
ui-config: 'e2e-commons/components-envs/ui-erc20-native.env'
- task: native-to-erc - task: native-to-erc
ui-e2e-grep: 'NATIVE TO ERC' ui-e2e-grep: 'NATIVE TO ERC'
ui-config: 'e2e-commons/components-envs/ui.env'
- task: amb-stake-erc-to-erc - task: amb-stake-erc-to-erc
ui-e2e-grep: '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:
@@ -240,7 +245,20 @@ jobs:
- 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 blocks
- name: Pull e2e oracle image - name: Pull e2e oracle image
run: docker-compose -f ./e2e-commons/docker-compose.yml pull oracle run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
- if: ${{ matrix.ui-e2e-grep }}
name: Pull e2e ui image
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull ui
docker build \
--build-arg DOCKER_IMAGE_BASE=${DOCKER_IMAGE_BASE} \
--build-arg UI_TAG=${UI_TAG} \
--build-arg DOT_ENV_PATH=${{ matrix.ui-config }} \
-f ./e2e-commons/Dockerfile.ui \
-t ui_ui:latest \
.
- name: Deploy oracle and ui - name: Deploy oracle 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

3
.gitignore vendored
View File

@@ -49,3 +49,6 @@ monitor/responses/*
monitor/cache/* monitor/cache/*
!monitor/cache/.gitkeep !monitor/cache/.gitkeep
!monitor/.gitkeep !monitor/.gitkeep
# Local Netlify folder
.netlify

View File

@@ -42,6 +42,8 @@ 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 ## UI configuration
@@ -82,3 +84,7 @@ MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for ot
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST | File with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests should be automatically processed by the CollectedSignatures watcher. | string MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST | File with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests should be automatically processed by the CollectedSignatures watcher. | string
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST | File with a list of addresses, separated by newlines. If set, determines the set of accounts whose requests should be marked as unclaimed. Has a lower priority than the `MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST`. | string MONITOR_HOME_TO_FOREIGN_BLOCK_LIST | File with a list of addresses, separated by newlines. If set, determines the set of accounts whose requests should be marked as unclaimed. Has a lower priority than the `MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST`. | string
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false` MONITOR_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE | If set, defines the list of home validator addresses for which balance should be checked. | `string`
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE | If set, defines the list of foreign validator addresses for which balance should be checked. | `string`
MONITOR_HOME_EXPLORER_API | The HTTPS URL of the Home network explorer API. If set, may be used as a fallback in case of the RPC node failure. | URL
MONITOR_FOREIGN_EXPLORER_API | The HTTPS URL of the Foreign network explorer API. If set, may be used as a fallback in case of the RPC node failure. | URL

View File

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

@@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ethersproject/bignumber": ">=5.0.0-beta.130",
"@react-hook/window-size": "^3.0.6", "@react-hook/window-size": "^3.0.6",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
@@ -15,6 +16,8 @@
"@types/react-router-dom": "^5.1.5", "@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.0", "@types/styled-components": "^5.1.0",
"@use-it/interval": "^0.1.3", "@use-it/interval": "^0.1.3",
"@web3-react/core": "^6.1.1",
"@web3-react/injected-connector": "^6.0.7",
"customize-cra": "^1.0.0", "customize-cra": "^1.0.0",
"date-fns": "^2.14.0", "date-fns": "^2.14.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
@@ -27,8 +30,9 @@
"react-scripts": "3.0.1", "react-scripts": "3.0.1",
"styled-components": "^5.1.1", "styled-components": "^5.1.1",
"typescript": "^3.5.2", "typescript": "^3.5.2",
"web3": "1.2.7", "web3": "1.2.11",
"web3-eth-contract": "1.2.7" "web3-eth-contract": "1.2.11",
"web3-utils": "1.2.11"
}, },
"scripts": { "scripts": {
"start": "yarn createSnapshots && ./load-env.sh react-app-rewired start", "start": "yarn createSnapshots && ./load-env.sh react-app-rewired start",
@@ -54,6 +58,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"eslint-plugin-prettier": "^3.1.3" "eslint-plugin-prettier": "^3.1.3",
"node-fetch": "^2.6.1"
} }
} }

1
alm/public/_redirects Normal file
View File

@@ -0,0 +1 @@
/* /index.html 200

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

@@ -1,14 +1,18 @@
import React from 'react' import React from 'react'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import { Web3ReactProvider } from '@web3-react/core'
import Web3 from 'web3'
import { MainPage } from './components/MainPage' import { MainPage } from './components/MainPage'
import { StateProvider } from './state/StateProvider' import { StateProvider } from './state/StateProvider'
function App() { function App() {
return ( return (
<BrowserRouter> <BrowserRouter>
<StateProvider> <Web3ReactProvider getLibrary={provider => new Web3(provider)}>
<MainPage /> <StateProvider>
</StateProvider> <MainPage />
</StateProvider>
</Web3ReactProvider>
</BrowserRouter> </BrowserRouter>
) )
} }

View File

@@ -39,21 +39,38 @@ export interface ConfirmationsContainerParams {
message: MessageObject message: MessageObject
receipt: Maybe<TransactionReceipt> receipt: Maybe<TransactionReceipt>
fromHome: boolean fromHome: boolean
timestamp: number homeStartBlock: Maybe<number>
foreignStartBlock: Maybe<number>
} }
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => { export const ConfirmationsContainer = ({
message,
receipt,
fromHome,
homeStartBlock,
foreignStartBlock
}: ConfirmationsContainerParams) => {
const { const {
home: { name: homeName }, home: { name: homeName },
foreign: { name: foreignName } foreign: { name: foreignName }
} = useStateProvider() } = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt }) const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt }) const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
const { confirmations, status, executionData, signatureCollected, waitingBlocksResolved } = useMessageConfirmations({ const {
confirmations,
status,
executionData,
signatureCollected,
waitingBlocksResolved,
setExecutionData,
executionEventsFetched,
setPendingExecution
} = useMessageConfirmations({
message, message,
receipt, receipt,
fromHome, fromHome,
timestamp, homeStartBlock,
foreignStartBlock,
requiredSignatures, requiredSignatures,
validatorList, validatorList,
blockConfirmations blockConfirmations
@@ -102,7 +119,17 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
validatorList={validatorList} validatorList={validatorList}
waitingBlocksResolved={waitingBlocksResolved} waitingBlocksResolved={waitingBlocksResolved}
/> />
{signatureCollected && <ExecutionConfirmation executionData={executionData} isHome={!fromHome} />} {signatureCollected && (
<ExecutionConfirmation
messageData={message.data}
executionData={executionData}
isHome={!fromHome}
signatureCollected={signatureCollected}
setExecutionData={setExecutionData}
executionEventsFetched={executionEventsFetched}
setPendingExecution={setPendingExecution}
/>
)}
</StyledConfirmationContainer> </StyledConfirmationContainer>
</div> </div>
) )

View File

@@ -1,24 +1,47 @@
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 { 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 { 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'
const StyledExecutionConfirmation = styled.div` const StyledExecutionConfirmation = styled.div`
margin-top: 30px; margin-top: 30px;
` `
export interface ExecutionConfirmationParams { export interface ExecutionConfirmationParams {
messageData: string
executionData: ExecutionData executionData: ExecutionData
setExecutionData: Function
signatureCollected: boolean | string[]
isHome: boolean isHome: boolean
executionEventsFetched: boolean
setPendingExecution: Function
} }
export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfirmationParams) => { export const ExecutionConfirmation = ({
messageData,
executionData,
setExecutionData,
signatureCollected,
isHome,
executionEventsFetched,
setPendingExecution
}: ExecutionConfirmationParams) => {
const availableManualExecution =
!isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED ||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
executionEventsFetched &&
!!executionData.validator))
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
const showAgeColumn = !requiredManualExecution || executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
const windowWidth = useWindowWidth() const windowWidth = useWindowWidth()
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome) const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
@@ -48,26 +71,47 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
<table> <table>
<Thead> <Thead>
<tr> <tr>
<th>Executed by</th> <th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
<th className="text-center">Status</th> <th className="text-center">Status</th>
<th className="text-center">Age</th> {showAgeColumn && <th className="text-center">Age</th>}
{availableManualExecution && <th className="text-center">Actions</th>}
</tr> </tr>
</Thead> </Thead>
<tbody> <tbody>
<tr> <tr>
<td>{formattedValidator ? formattedValidator : <SimpleLoading />}</td> <td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd> {requiredManualExecution ? (
<AgeTd className="text-center"> 'Manual user action is required to complete the operation'
{executionData.timestamp > 0 ? ( ) : formattedValidator ? (
<ExplorerTxLink href={txExplorerLink} target="_blank"> formattedValidator
{formatTimestamp(executionData.timestamp)}
</ExplorerTxLink>
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
''
) : ( ) : (
SEARCHING_TX <SimpleLoading />
)} )}
</AgeTd> </td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
{showAgeColumn && (
<AgeTd className="text-center">
{executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank">
{formatTimestamp(executionData.timestamp)}
</ExplorerTxLink>
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
''
) : (
SEARCHING_TX
)}
</AgeTd>
)}
{availableManualExecution && (
<td>
<ManualExecutionButton
messageData={messageData}
setExecutionData={setExecutionData}
signatureCollected={signatureCollected as string[]}
setPendingExecution={setPendingExecution}
/>
</td>
)}
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -8,6 +8,7 @@ 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;
@@ -51,7 +52,7 @@ export interface FormSubmitParams {
export const MainPage = () => { export const MainPage = () => {
const history = useHistory() const history = useHistory()
const { home, foreign } = useStateProvider() const { home, foreign, error, setError } = 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)
@@ -131,6 +132,7 @@ 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

@@ -0,0 +1,146 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { InjectedConnector } from '@web3-react/injected-connector'
import { useWeb3React } from '@web3-react/core'
import {
DOUBLE_EXECUTION_ATTEMPT_ERROR,
EXECUTION_FAILED_ERROR,
EXECUTION_OUT_OF_GAS_ERROR,
FOREIGN_EXPLORER_API,
INCORRECT_CHAIN_ERROR,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import { useStateProvider } from '../state/StateProvider'
import { signatureToVRS, packSignatures } from '../utils/signatures'
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
import { TransactionReceipt } from 'web3-eth'
const StyledButton = styled.button`
color: var(--button-color);
border-color: var(--font-color);
margin-top: 10px;
&:focus {
outline: var(--button-color);
}
`
interface ManualExecutionButtonParams {
messageData: string
setExecutionData: Function
signatureCollected: string[]
setPendingExecution: Function
}
export const ManualExecutionButton = ({
messageData,
setExecutionData,
signatureCollected,
setPendingExecution
}: ManualExecutionButtonParams) => {
const { foreign, setError } = useStateProvider()
const { library, activate, account, active } = useWeb3React()
const [manualExecution, setManualExecution] = useState(false)
useEffect(
() => {
if (!manualExecution || !foreign.chainId) return
if (!active) {
activate(new InjectedConnector({ supportedChainIds: [foreign.chainId] }), e => {
if (e.message.includes('Unsupported chain id')) {
setError(INCORRECT_CHAIN_ERROR)
const { ethereum } = window as any
// remove the error message after chain is correctly changed to the foreign one
const listener = (chainId: string) => {
if (parseInt(chainId.slice(2), 16) === foreign.chainId) {
ethereum.removeListener('chainChanged', listener)
setError((error: string) => (error === INCORRECT_CHAIN_ERROR ? '' : error))
}
}
ethereum.on('chainChanged', listener)
} else {
setError(e.message)
}
setManualExecution(false)
})
return
}
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
const signatures = packSignatures(signatureCollected.map(signatureToVRS))
const messageId = messageData.slice(0, 66)
const bridge = foreign.bridgeContract
const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI()
setManualExecution(false)
library.eth
.sendTransaction({
from: account,
to: foreign.bridgeAddress,
data
})
.on('transactionHash', (txHash: string) => {
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
validator: account,
txHash,
timestamp: Math.floor(new Date().getTime() / 1000.0),
executionResult: false
})
setPendingExecution(true)
})
.on('error', async (e: Error, receipt: TransactionReceipt) => {
if (e.message.includes('Transaction has been reverted by the EVM')) {
const successExecutionData = await getSuccessExecutionData(
bridge,
'RelayedMessage',
library,
messageId,
FOREIGN_EXPLORER_API
)
if (successExecutionData) {
setExecutionData(successExecutionData)
setError(DOUBLE_EXECUTION_ATTEMPT_ERROR)
} else {
const { gas } = await library.eth.getTransaction(receipt.transactionHash)
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
validator: account,
txHash: receipt.transactionHash,
timestamp: Math.floor(new Date().getTime() / 1000.0),
executionResult: false
})
setError(gas === receipt.gasUsed ? EXECUTION_OUT_OF_GAS_ERROR : EXECUTION_FAILED_ERROR)
}
} else {
setError(e.message)
}
})
},
[
manualExecution,
library,
activate,
active,
account,
foreign.chainId,
foreign.bridgeAddress,
foreign.bridgeContract,
setError,
messageData,
signatureCollected,
setExecutionData,
setPendingExecution
]
)
return (
<div className="is-center">
<StyledButton className="button outline" onClick={() => setManualExecution(true)}>
Execute
</StyledButton>
</div>
)
}

View File

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

View File

@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
export const CloseIcon = () => ( export const CloseIcon = ({ color }: { color?: string }) => (
<svg <svg
aria-hidden="true" aria-hidden="true"
focusable="false" focusable="false"
@@ -10,12 +10,9 @@ export const CloseIcon = () => (
role="img" role="img"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 352 512" viewBox="0 0 352 512"
fill="#1890ff" fill={color || '#1890ff'}
height="1em" height="1em"
> >
<path <path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />
fill="#1890ff"
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
/>
</svg> </svg>
) )

View File

@@ -0,0 +1,48 @@
import React from 'react'
import styled from 'styled-components'
import { InfoIcon } from './InfoIcon'
import { CloseIcon } from './CloseIcon'
import { ExplorerTxLink } from './ExplorerTxLink'
const StyledErrorAlert = styled.div`
border: 1px solid var(--failed-color);
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 ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
const errorArray = error.split('%link')
const text = errorArray[0]
let link
if (errorArray.length > 1) {
link = (
<ExplorerTxLink href={errorArray[1]} target="_blank" rel="noopener noreferrer">
{errorArray[1]}
</ExplorerTxLink>
)
}
return (
<div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row">
<InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10">
{text}
{link}
</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--failed-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)
}

View File

@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
export const InfoIcon = () => ( export const InfoIcon = ({ color }: { color?: string }) => (
<svg <svg
className="col-1 is-left" className="col-1 is-left"
viewBox="64 64 896 896" viewBox="64 64 896 896"
@@ -8,7 +8,7 @@ export const InfoIcon = () => (
data-icon="info-circle" data-icon="info-circle"
width="1em" width="1em"
height="1em" height="1em"
fill="#1890ff" fill={color || '#1890ff'}
aria-hidden="true" aria-hidden="true"
> >
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z" /> <path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z" />

View File

@@ -13,11 +13,13 @@ export const FOREIGN_EXPLORER_TX_TEMPLATE: string = process.env.REACT_APP_ALM_FO
export const HOME_EXPLORER_API: string = process.env.REACT_APP_ALM_HOME_EXPLORER_API || '' export const HOME_EXPLORER_API: string = process.env.REACT_APP_ALM_HOME_EXPLORER_API || ''
export const FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || '' export const FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || ''
export const ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION: boolean =
(process.env.REACT_APP_ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION || '').toLowerCase() === 'true'
export const HOME_RPC_POLLING_INTERVAL: number = 5000 export const HOME_RPC_POLLING_INTERVAL: number = 5000
export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000 export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000
export const BLOCK_RANGE: number = 50 export const BLOCK_RANGE: number = 500
export const ONE_DAY_TIMESTAMP: number = 86400 export const MAX_TX_SEARCH_BLOCK_RANGE: number = 10000
export const THREE_DAYS_TIMESTAMP: number = 259200
export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f' export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f'
export const SUBMIT_SIGNATURE_HASH = '630cea8e' export const SUBMIT_SIGNATURE_HASH = '630cea8e'
@@ -61,3 +63,14 @@ export const VALIDATOR_CONFIRMATION_STATUS = {
} }
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 DOUBLE_EXECUTION_ATTEMPT_ERROR = `Your execution transaction has been reverted.
However, the execution completed successfully in the transaction sent by a different party.`
export const EXECUTION_FAILED_ERROR = `Your execution transaction has been reverted.
Please, contact the support by messaging on %linkhttps://forum.poa.network/c/support`
export const EXECUTION_OUT_OF_GAS_ERROR = `Your execution transaction has been reverted due to Out-of-Gas error.
Please, resend the transaction and provide more gas to it.`

View File

@@ -1,4 +1,6 @@
// %t will be replaced by the time -> x minutes/hours/days ago // %t will be replaced by the time -> x minutes/hours/days ago
import { ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from './constants'
export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = { export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = {
SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:', SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:',
SUCCESS_ONE_MESSAGE: 'was initiated %t', SUCCESS_ONE_MESSAGE: 'was initiated %t',
@@ -24,7 +26,7 @@ export const CONFIRMATIONS_STATUS_LABEL_HOME: { [key: string]: string } = {
SUCCESS_MESSAGE_FAILED: 'Success', SUCCESS_MESSAGE_FAILED: 'Success',
EXECUTION_FAILED: 'Execution failed', EXECUTION_FAILED: 'Execution failed',
EXECUTION_PENDING: 'Execution pending', EXECUTION_PENDING: 'Execution pending',
EXECUTION_WAITING: 'Execution waiting', EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION ? 'Manual execution waiting' : 'Execution waiting',
FAILED: 'Confirmation Failed', FAILED: 'Confirmation Failed',
PENDING: 'Confirmation Pending', PENDING: 'Confirmation Pending',
WAITING_VALIDATORS: 'Confirmation Waiting', WAITING_VALIDATORS: 'Confirmation Waiting',
@@ -55,11 +57,12 @@ export const CONFIRMATIONS_STATUS_DESCRIPTION_HOME: { [key: string]: string } =
SUCCESS_MESSAGE_FAILED: SUCCESS_MESSAGE_FAILED:
'The specified transaction was included in a block,\nthe validators collected signatures and the cross-chain relay was executed correctly,\nbut the contained message execution failed.\nContact the support of the application you used to produce the transaction for the clarifications.', 'The specified transaction was included in a block,\nthe validators collected signatures and the cross-chain relay was executed correctly,\nbut the contained message execution failed.\nContact the support of the application you used to produce the transaction for the clarifications.',
EXECUTION_FAILED: EXECUTION_FAILED:
'The specified transaction was included in a block\nand the validators collected signatures. The\nvalidators transaction with collected signatures was\nsent but did not succeed. Contact to the validators by messaging\non %linkhttps://forum.poa.network/c/support', 'The specified transaction was included in a block\nand the validators collected signatures. The\n transaction with collected signatures was\nsent but did not succeed. Contact to the validators by messaging\non %linkhttps://forum.poa.network/c/support',
EXECUTION_PENDING: EXECUTION_PENDING:
'The specified transaction was included in a block\nand the validators collected signatures. The\nvalidators transaction with collected signatures was\nsent but is not yet added to a block.', 'The specified transaction was included in a block\nand the validators collected signatures. The\n transaction with collected signatures was\nsent but is not yet added to a block.',
EXECUTION_WAITING: EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
'The specified transaction was included in a block\nand the validators collected signatures. Either\n1. One of the validators is waiting for chain finalization.\n2. A validator skipped its duty to relay signatures.\n3. The execution transaction is still pending (e.g. due to the gas price spike).\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support', ? 'The specified transaction was included in a block\nand the validators collected signatures.\nNow the manual user action is required to complete message execution.\n Please, press the "Execute" button.'
: 'The specified transaction was included in a block\nand the validators collected signatures. Either\n1. One of the validators is waiting for chain finalization.\n2. A validator skipped its duty to relay signatures.\n3. The execution transaction is still pending (e.g. due to the gas price spike).\nCheck status again after a few blocks or force execution by pressing the "Execute" button.\nIf the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
FAILED: FAILED:
'The specified transaction was included in a block,\nbut transactions with signatures sent by a majority of\nvalidators failed. The cross-chain relay request will\nnot be processed. Contact to the validators by\nmessaging on %linkhttps://forum.poa.network/c/support', 'The specified transaction was included in a block,\nbut transactions with signatures sent by a majority of\nvalidators failed. The cross-chain relay request will\nnot be processed. Contact to the validators by\nmessaging on %linkhttps://forum.poa.network/c/support',
PENDING: PENDING:

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

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

View File

@@ -3,7 +3,6 @@ import { TransactionReceipt } from 'web3-eth'
import { MessageObject } from '../utils/web3' import { MessageObject } from '../utils/web3'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { EventData } from 'web3-eth-contract' import { EventData } from 'web3-eth-contract'
import { getAffirmationsSigned, getMessagesSigned } from '../utils/contract'
import { import {
BLOCK_RANGE, BLOCK_RANGE,
CONFIRMATIONS_STATUS, CONFIRMATIONS_STATUS,
@@ -12,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 {
@@ -29,7 +25,8 @@ export interface useMessageConfirmationsParams {
message: MessageObject message: MessageObject
receipt: Maybe<TransactionReceipt> receipt: Maybe<TransactionReceipt>
fromHome: boolean fromHome: boolean
timestamp: number homeStartBlock: Maybe<number>
foreignStartBlock: Maybe<number>
requiredSignatures: number requiredSignatures: number
validatorList: string[] validatorList: string[]
blockConfirmations: number blockConfirmations: number
@@ -57,17 +54,19 @@ export const useMessageConfirmations = ({
message, message,
receipt, receipt,
fromHome, fromHome,
timestamp, homeStartBlock,
foreignStartBlock,
requiredSignatures, requiredSignatures,
validatorList, validatorList,
blockConfirmations blockConfirmations
}: useMessageConfirmationsParams) => { }: useMessageConfirmationsParams) => {
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
const [confirmations, setConfirmations] = useState<Array<ConfirmationParam>>([]) 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)
const [signatureCollected, setSignatureCollected] = useState(false) const [signatureCollected, setSignatureCollected] = useState<boolean | string[]>(false)
const [executionEventsFetched, setExecutionEventsFetched] = useState(false)
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null) const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
const [executionData, setExecutionData] = useState<ExecutionData>({ const [executionData, setExecutionData] = useState<ExecutionData>({
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
@@ -83,155 +82,188 @@ 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
useEffect(
() => {
if (!home.web3 || !foreign.web3) return
homeBlockNumberProvider.start(home.web3)
foreignBlockNumberProvider.start(foreign.web3)
},
[foreign.web3, home.web3]
)
// Check if the validators are waiting for block confirmations to verify the message // Check if the validators are waiting for block confirmations to verify the message
useEffect( useEffect(
() => { () => {
if (!receipt || !blockConfirmations) 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 web3 = fromHome ? home.web3 : foreign.web3
blockProvider.start(web3)
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)
blockProvider.stop() setWaitingBlocks(false)
} else if (currentBlock) {
setWaitingBlocks(true)
setConfirmations(validatorsWaiting)
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, interval)
} else {
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, 500)
}
} }
checkSignaturesWaitingForBLocks()
return () => clearTimeout(timeoutId)
}, },
[blockConfirmations, foreign.web3, fromHome, validatorList, home.web3, receipt] [blockConfirmations, fromHome, receipt, validatorList, waitingBlocksResolved]
) )
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if // The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
// the execution tx on the foreign network is waiting for block confirmations // the execution tx on the foreign network is waiting for block confirmations
// This is executed if the message is in Home to Foreign direction only // This is executed if the message is in Home to Foreign direction only
const hasCollectedSignatures = !!signatureCollected // true or string[]
useEffect( useEffect(
() => { () => {
if (!fromHome || !receipt || !home.web3 || !signatureCollected) return if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !hasCollectedSignatures) return
const subscriptions: Array<number> = [] let timeoutId: number
let isCancelled = false
const unsubscribe = () => { 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)
}
} }
homeBlockNumberProvider.start(home.web3) getCollectedSignaturesEvent(receipt.blockNumber, receipt.blockNumber + BLOCK_RANGE)
const fromBlock = receipt.blockNumber
const toBlock = fromBlock + BLOCK_RANGE
const messageHash = home.web3.utils.soliditySha3Raw(message.data)
getCollectedSignaturesEvent(
home.web3,
home.bridgeContract,
fromBlock,
toBlock,
messageHash,
setCollectedSignaturesEvent,
subscriptions
)
return () => { return () => {
unsubscribe() clearTimeout(timeoutId)
homeBlockNumberProvider.stop() isCancelled = true
} }
}, },
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected] [fromHome, home.bridgeContract, home.web3, message.data, receipt, hasCollectedSignatures]
) )
// Check if the responsible validator is waiting for block confirmations to execute the message on foreign network // Check if the responsible validator is waiting for block confirmations to execute the message on foreign network
// This is executed if the message is in Home to Foreign direction only // 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
const subscriptions: Array<number> = [] let timeoutId: number
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
homeBlockNumberProvider.start(home.web3)
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 = {
homeBlockNumberProvider.stop() 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] [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 || !timestamp || !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 = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
getConfirmationsForTx( getConfirmationsForTx(
message.data, message.data,
home.web3, home.web3,
validatorList, validatorList,
home.bridgeContract, home.bridgeContract,
confirmationContractMethod, fromHome,
setConfirmations, setConfirmations,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, id => (timeoutId = id),
subscriptions, () => isCancelled,
timestamp, homeStartBlock,
getValidatorFailedTransactionsForMessage, getValidatorFailedTransactionsForMessage,
setFailedConfirmations, setFailedConfirmations,
getValidatorPendingTransactionsForMessage, getValidatorPendingTransactionsForMessage,
@@ -240,7 +272,8 @@ export const useMessageConfirmations = ({
) )
return () => { return () => {
unsubscribe() clearTimeout(timeoutId)
isCancelled = true
} }
}, },
[ [
@@ -251,7 +284,7 @@ export const useMessageConfirmations = ({
home.bridgeContract, home.bridgeContract,
requiredSignatures, requiredSignatures,
waitingBlocksResolved, waitingBlocksResolved,
timestamp homeStartBlock
] ]
) )
@@ -262,38 +295,34 @@ export const useMessageConfirmations = ({
() => { () => {
if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return
const subscriptions: Array<number> = []
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const contractEvent = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract const 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,
bridgeContract, bridgeContract,
contractEvent, web3,
providedWeb3,
setExecutionData, setExecutionData,
waitingBlocksResolved,
message, message,
interval, id => (timeoutId = id),
subscriptions, () => isCancelled,
timestamp, startBlock,
collectedSignaturesEvent, collectedSignaturesEvent,
getExecutionFailedTransactionForMessage, getExecutionFailedTransactionForMessage,
setFailedExecution, setFailedExecution,
getExecutionPendingTransactionsForMessage, getExecutionPendingTransactionsForMessage,
setPendingExecution setPendingExecution,
setExecutionEventsFetched
) )
return () => { return () => {
unsubscribe() clearTimeout(timeoutId)
isCancelled = true
} }
}, },
[ [
@@ -305,8 +334,9 @@ export const useMessageConfirmations = ({
home.web3, home.web3,
waitingBlocksResolved, waitingBlocksResolved,
waitingBlocksForExecutionResolved, waitingBlocksForExecutionResolved,
timestamp, collectedSignaturesEvent,
collectedSignaturesEvent foreignStartBlock,
homeStartBlock
] ]
) )
@@ -318,6 +348,9 @@ export const useMessageConfirmations = ({
? CONFIRMATIONS_STATUS.SUCCESS ? CONFIRMATIONS_STATUS.SUCCESS
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
setStatus(newStatus) setStatus(newStatus)
foreignBlockNumberProvider.stop()
homeBlockNumberProvider.stop()
} else if (signatureCollected) { } else if (signatureCollected) {
if (fromHome) { if (fromHome) {
if (waitingBlocksForExecution) { if (waitingBlocksForExecution) {
@@ -369,6 +402,9 @@ export const useMessageConfirmations = ({
status, status,
signatureCollected, signatureCollected,
executionData, executionData,
waitingBlocksResolved setExecutionData,
waitingBlocksResolved,
executionEventsFetched,
setPendingExecution
} }
} }

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

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

View File

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

View File

@@ -1,4 +1,4 @@
import React, { createContext, ReactNode } from 'react' import React, { createContext, ReactNode, useState } from 'react'
import { useNetwork } from '../hooks/useNetwork' import { useNetwork } from '../hooks/useNetwork'
import { import {
HOME_RPC_URL, HOME_RPC_URL,
@@ -25,6 +25,8 @@ export interface StateContext {
home: BaseNetworkParams home: BaseNetworkParams
foreign: BaseNetworkParams foreign: BaseNetworkParams
loading: boolean loading: boolean
error: string
setError: Function
} }
const initialState = { const initialState = {
@@ -42,7 +44,9 @@ 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)
@@ -54,6 +58,7 @@ 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: {
@@ -68,7 +73,9 @@ 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

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

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

View File

@@ -9,7 +9,7 @@ import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations'
jest.mock('../validatorConfirmationHelpers') jest.mock('../validatorConfirmationHelpers')
const getValidatorSuccessTransaction = helpers.getValidatorSuccessTransaction as jest.Mock<any> const getSuccessExecutionTransaction = helpers.getSuccessExecutionTransaction as jest.Mock<any>
const getValidatorConfirmation = helpers.getValidatorConfirmation as jest.Mock<any> const getValidatorConfirmation = helpers.getValidatorConfirmation as jest.Mock<any>
const getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock<any> const getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock<any>
const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock<any> const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock<any>
@@ -24,10 +24,17 @@ const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f' const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
const validatorList = [validator1, validator2, validator3] const validatorList = [validator1, validator2, validator3]
const bridgeContract = {} as Contract const signature =
const confirmationContractMethod = () => {} '0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4'
const bridgeContract = {
methods: {
signature: () => ({
call: () => signature
})
}
} 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([])
@@ -42,7 +49,7 @@ const unsubscribe = () => {
beforeEach(() => { beforeEach(() => {
// Clear all instances and calls to constructor and all methods: // Clear all instances and calls to constructor and all methods:
getValidatorSuccessTransaction.mockClear() getSuccessExecutionTransaction.mockClear()
getValidatorConfirmation.mockClear() getValidatorConfirmation.mockClear()
getValidatorFailedTransaction.mockClear() getValidatorFailedTransaction.mockClear()
getValidatorPendingTransaction.mockClear() getValidatorPendingTransaction.mockClear()
@@ -54,7 +61,7 @@ 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
})) }))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '', txHash: '',
@@ -83,12 +90,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -102,9 +109,10 @@ describe('getConfirmationsForTx', () => {
expect(subscriptions.length).toEqual(1) expect(subscriptions.length).toEqual(1)
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(2)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(2)
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)
@@ -114,14 +122,16 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual( const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
expect(res1).toEqual(
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.UNDEFINED }
]) ])
) )
expect(setResult.mock.calls[1][0]).toEqual( expect(res2).toEqual(
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 },
@@ -134,7 +144,7 @@ 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
})) }))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '', txHash: '',
@@ -163,12 +173,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -179,9 +189,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe() unsubscribe()
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(1)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -198,7 +208,7 @@ 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
})) }))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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' : '',
@@ -227,12 +237,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -244,11 +254,12 @@ describe('getConfirmationsForTx', () => {
unsubscribe() unsubscribe()
expect(subscriptions.length).toEqual(0) expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(2)
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)
@@ -258,14 +269,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual( const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
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 }
]) ])
) )
expect(setResult.mock.calls[1][0]).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 },
@@ -283,7 +304,7 @@ describe('getConfirmationsForTx', () => {
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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' : '',
@@ -315,12 +336,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -332,11 +353,12 @@ describe('getConfirmationsForTx', () => {
unsubscribe() unsubscribe()
expect(subscriptions.length).toEqual(0) expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(2)
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)
@@ -346,7 +368,27 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual( const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
const res4 = setResult.mock.calls[3][0](res3)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res3).toEqual(
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 },
@@ -354,7 +396,7 @@ describe('getConfirmationsForTx', () => {
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
]) ])
) )
expect(setResult.mock.calls[1][0]).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 },
@@ -372,7 +414,7 @@ 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
})) }))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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' : '',
@@ -407,12 +449,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -423,9 +465,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe() unsubscribe()
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -437,14 +479,32 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true) expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
expect(setResult.mock.calls[0][0]()).toEqual( const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
const res4 = setResult.mock.calls[3][0](res3)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
])
)
expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
]) ])
) )
expect(setResult.mock.calls[1][0]).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, txHash: '0x123', timestamp: 123 },
@@ -461,7 +521,7 @@ 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
})) }))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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' : '',
@@ -493,12 +553,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -510,9 +570,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe() unsubscribe()
expect(subscriptions.length).toEqual(0) expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -524,14 +584,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual( const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }
]) ])
) )
expect(setResult.mock.calls[1][0]).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.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
@@ -555,7 +625,7 @@ describe('getConfirmationsForTx', () => {
status: status:
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getValidatorSuccessTransaction getSuccessExecutionTransaction
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
@@ -604,12 +674,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -620,9 +690,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe() unsubscribe()
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -634,14 +704,32 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true) expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
expect(setResult.mock.calls[0][0]()).toEqual( const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
const res4 = setResult.mock.calls[3][0](res3)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
])
)
expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
]) ])
) )
expect(setResult.mock.calls[1][0]).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, txHash: '0x123', timestamp: 123 },
@@ -654,12 +742,12 @@ describe('getConfirmationsForTx', () => {
web3, web3,
validatorList, validatorList,
bridgeContract, bridgeContract,
confirmationContractMethod, true,
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@@ -670,12 +758,13 @@ describe('getConfirmationsForTx', () => {
unsubscribe() unsubscribe()
expect(setResult).toBeCalledTimes(4) expect(setResult).toBeCalledTimes(7)
expect(getValidatorConfirmation).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(2)
expect(getValidatorSuccessTransaction).toBeCalledTimes(2) expect(getSuccessExecutionTransaction).toBeCalledTimes(2)
expect(setSignatureCollected).toBeCalledTimes(2) expect(setSignatureCollected).toBeCalledTimes(3)
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)
@@ -687,14 +776,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true) expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
expect(setPendingConfirmations.mock.calls[1][0]).toEqual(false) expect(setPendingConfirmations.mock.calls[1][0]).toEqual(false)
expect(setResult.mock.calls[2][0]()).toEqual( const res5 = setResult.mock.calls[4][0](res4)
const res6 = setResult.mock.calls[5][0](res5)
const res7 = setResult.mock.calls[6][0](res6)
expect(res5).toEqual(
expect.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.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
]) ])
) )
expect(setResult.mock.calls[3][0]).toEqual( expect(res6).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
])
)
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: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },

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
@@ -61,22 +59,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn() const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn() const getPendingExecution = jest.fn()
const setPendingExecution = jest.fn() const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution, setFailedExecution,
getPendingExecution, getPendingExecution,
setPendingExecution setPendingExecution,
setExecutionEventsFetched
) )
unsubscribe() unsubscribe()
@@ -99,7 +98,7 @@ describe('getFinalizationEvent', () => {
}) })
test('should retry to get finalization event and not try to get failed or pending transactions if foreign to home transaction', async () => { 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
@@ -110,22 +109,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn() const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn() const getPendingExecution = jest.fn()
const setPendingExecution = jest.fn() const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution, setFailedExecution,
getPendingExecution, getPendingExecution,
setPendingExecution setPendingExecution,
setExecutionEventsFetched
) )
unsubscribe() unsubscribe()
@@ -141,7 +141,7 @@ describe('getFinalizationEvent', () => {
}) })
test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => { 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: {
@@ -159,22 +159,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn() const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([]) const getPendingExecution = jest.fn().mockResolvedValue([])
const setPendingExecution = jest.fn() const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution, setFailedExecution,
getPendingExecution, getPendingExecution,
setPendingExecution setPendingExecution,
setExecutionEventsFetched
) )
unsubscribe() unsubscribe()
@@ -190,7 +191,7 @@ describe('getFinalizationEvent', () => {
}) })
test('should retry to get finalization event and not to try to get failed transaction if pending transactions found if home to foreign transaction', async () => { 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: {
@@ -208,22 +209,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn() const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([{ hash: txHash }]) const getPendingExecution = jest.fn().mockResolvedValue([{ hash: txHash }])
const setPendingExecution = jest.fn() const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution, setFailedExecution,
getPendingExecution, getPendingExecution,
setPendingExecution setPendingExecution,
setExecutionEventsFetched
) )
unsubscribe() unsubscribe()
@@ -246,7 +248,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: {
@@ -264,22 +266,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn() const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([]) const getPendingExecution = jest.fn().mockResolvedValue([])
const setPendingExecution = jest.fn() const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution, setFailedExecution,
getPendingExecution, getPendingExecution,
setPendingExecution setPendingExecution,
setExecutionEventsFetched
) )
unsubscribe() unsubscribe()

View File

@@ -1,18 +1,33 @@
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 = ''
) => { ) => {
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
}) })
@@ -38,14 +53,16 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
export const getRequiredSignatures = async ( export const getRequiredSignatures = async (
contract: Contract, contract: Contract,
blockNumber: number, blockNumber: number,
snapshotProvider: SnapshotProvider snapshotProvider: SnapshotProvider,
web3: Web3 | null = null,
api: string = ''
) => { ) => {
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
}) })
@@ -59,7 +76,13 @@ export const getRequiredSignatures = async (
return parseInt(requiredSignatures) return parseInt(requiredSignatures)
} }
export const getValidatorList = async (contract: Contract, blockNumber: number, snapshotProvider: SnapshotProvider) => { export const getValidatorList = async (
contract: Contract,
blockNumber: number,
snapshotProvider: SnapshotProvider,
web3: Web3 | null = null,
api: string = ''
) => {
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber) const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber) const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
@@ -67,10 +90,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,58 +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) {
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
})
setWaitingBlocksForExecutionResolved(true)
setWaitingBlocksForExecution(false)
blockProvider.stop()
} else {
let nextInterval = interval
if (!currentBlock) {
nextInterval = 500
} else {
setWaitingBlocksForExecution(true)
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
})
}
const timeoutId = setTimeout(
() =>
checkWaitingBlocksForExecution(
blockProvider,
interval,
targetBlock,
collectedSignaturesEvent,
setWaitingBlocksForExecution,
setWaitingBlocksForExecutionResolved,
setExecutionData,
subscriptions
),
nextInterval
)
subscriptions.push(timeoutId)
}
}

View File

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

View File

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

View File

@@ -1,82 +1,91 @@
import Web3 from 'web3' 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 { import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
GetFailedTransactionParams, import { getAffirmationsSigned, getMessagesSigned } from './contract'
APITransaction,
APIPendingTransaction,
GetPendingTransactionParams
} from './explorer'
import { import {
getValidatorConfirmation, getValidatorConfirmation,
getValidatorFailedTransaction, getValidatorFailedTransaction,
getValidatorPendingTransaction, getValidatorPendingTransaction,
getValidatorSuccessTransaction getSuccessExecutionTransaction
} from './validatorConfirmationHelpers' } from './validatorConfirmationHelpers'
import { ConfirmationParam } from '../hooks/useMessageConfirmations' import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => {
const confirmations = [...oldConfirmations]
newConfirmations.forEach(validatorData => {
const index = confirmations.findIndex(e => e.validator === validatorData.validator)
const currentStatus = confirmations[index].status
const newStatus = validatorData.status
if (
(validatorData as ConfirmationParam).txHash ||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
) {
confirmations[index] = validatorData
}
})
return confirmations
}
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,
confirmationContractMethod: Function, fromHome: boolean,
setResult: Function, setResult: Function,
requiredSignatures: number, requiredSignatures: number,
setSignatureCollected: Function, setSignatureCollected: Function,
waitingBlocksResolved: boolean, setTimeoutId: (timeoutId: number) => void,
subscriptions: number[], isCancelled: () => boolean,
timestamp: number, startBlock: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>, getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>,
setFailedConfirmations: Function, setFailedConfirmations: Function,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>, getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingConfirmations: Function, setPendingConfirmations: Function,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => { ) => {
if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
// If all the information was not collected, then it should retry
let shouldRetry = false
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, confirmationContractMethod))
) )
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) const updateConfirmations = (confirmations: BasicConfirmationParam[]) => {
if (confirmations.length === 0) {
setResult((prevConfirmations: ConfirmationParam[]) => { return
if (prevConfirmations && prevConfirmations.length) {
successConfirmations.forEach(validatorData => {
const index = prevConfirmations.findIndex(e => e.validator === validatorData.validator)
validatorConfirmations[index] = validatorData
})
return prevConfirmations
} else {
return validatorConfirmations
} }
}) validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
setResult((currentConfirmations: BasicConfirmationParam[]) => {
if (currentConfirmations && currentConfirmations.length) {
return mergeConfirmations(currentConfirmations, confirmations)
}
return confirmations
})
}
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS) const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
const hasEnoughSignatures = successConfirmations.length === requiredSignatures
updateConfirmations(validatorConfirmations)
setSignatureCollected(hasEnoughSignatures)
// If signatures not collected, look for pending transactions // If signatures not collected, look for pending transactions
let pendingConfirmationsResult = false if (!hasEnoughSignatures) {
if (successConfirmations.length !== requiredSignatures) {
// Check if confirmation is pending // Check if confirmation is pending
const validatorPendingConfirmationsChecks = await Promise.all( const validatorPendingConfirmationsChecks = await Promise.all(
notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions)) notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions))
) )
const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter( const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
) )
updateConfirmations(validatorPendingConfirmations)
validatorPendingConfirmations.forEach(validatorData => { setPendingConfirmations(validatorPendingConfirmations.length > 0)
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) } else {
validatorConfirmations[index] = validatorData setPendingConfirmations(false)
})
if (validatorPendingConfirmations.length > 0) {
pendingConfirmationsResult = true
}
} }
const undefinedConfirmations = validatorConfirmations.filter( const undefinedConfirmations = validatorConfirmations.filter(
@@ -84,99 +93,79 @@ export const getConfirmationsForTx = async (
) )
// Check if confirmation failed // Check if confirmation failed
let failedConfirmationsResult = false
const validatorFailedConfirmationsChecks = await Promise.all( const validatorFailedConfirmationsChecks = await Promise.all(
undefinedConfirmations.map( undefinedConfirmations.map(
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions)
) )
) )
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
) )
validatorFailedConfirmations.forEach(validatorData => { setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures)
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) updateConfirmations(validatorFailedConfirmations)
validatorConfirmations[index] = validatorData
})
const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures
if (messageConfirmationsFailed) {
failedConfirmationsResult = true
}
const missingConfirmations = validatorConfirmations.filter( const missingConfirmations = validatorConfirmations.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
) )
if (successConfirmations.length !== requiredSignatures && missingConfirmations.length > 0) { if (hasEnoughSignatures) {
shouldRetry = true
}
let signatureCollectedResult = false
if (successConfirmations.length === requiredSignatures) {
// If signatures collected, it should set other signatures not found as not required // If signatures collected, it should set other signatures not found as not required
const notRequiredConfirmations = missingConfirmations.map(c => ({ const notRequiredConfirmations = missingConfirmations.map(c => ({
validator: c.validator, validator: c.validator,
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
})) }))
updateConfirmations(notRequiredConfirmations)
notRequiredConfirmations.forEach(validatorData => { if (fromHome) {
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) // fetch collected signatures for possible manual processing
validatorConfirmations[index] = validatorData setSignatureCollected(
}) await Promise.all(
signatureCollectedResult = true Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
)
)
}
} }
// get transactions from success signatures // get transactions from success signatures
const successConfirmationWithData = await Promise.all( const successConfirmationWithData = await Promise.all(
validatorConfirmations successConfirmations.map(
.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
.map(getValidatorSuccessTransaction(bridgeContract, messageData, timestamp, getSuccessTransactions)) )
) )
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '') const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
updateConfirmations(successConfirmationWithTxFound)
const updatedValidatorConfirmations = [...validatorConfirmations] // retry if not all signatures are collected and some confirmations are still missing
// or some success transactions were not fetched successfully
if (successConfirmationWithTxFound.length > 0) { if (
successConfirmationWithTxFound.forEach(validatorData => { (!hasEnoughSignatures && missingConfirmations.length > 0) ||
const index = updatedValidatorConfirmations.findIndex(e => e.validator === validatorData.validator) successConfirmationWithTxFound.length < successConfirmationWithData.length
updatedValidatorConfirmations[index] = validatorData ) {
}) if (!isCancelled()) {
} const timeoutId = setTimeout(
() =>
// Set results getConfirmationsForTx(
setResult(updatedValidatorConfirmations) messageData,
setFailedConfirmations(failedConfirmationsResult) web3,
setPendingConfirmations(pendingConfirmationsResult) validatorList,
setSignatureCollected(signatureCollectedResult) bridgeContract,
fromHome,
// Retry if not all transaction were found for validator confirmations setResult,
if (successConfirmationWithTxFound.length < successConfirmationWithData.length) { requiredSignatures,
shouldRetry = true setSignatureCollected,
} setTimeoutId,
isCancelled,
if (shouldRetry) { startBlock,
const timeoutId = setTimeout( getFailedTransactions,
() => setFailedConfirmations,
getConfirmationsForTx( getPendingTransactions,
messageData, setPendingConfirmations,
web3, getSuccessTransactions
validatorList, ),
bridgeContract, HOME_RPC_POLLING_INTERVAL
confirmationContractMethod, )
setResult, setTimeoutId(timeoutId)
requiredSignatures, }
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
timestamp,
getFailedTransactions,
setFailedConfirmations,
getPendingTransactions,
setPendingConfirmations,
getSuccessTransactions
),
HOME_RPC_POLLING_INTERVAL
)
subscriptions.push(timeoutId)
} }
} }

View File

@@ -1,40 +1,51 @@
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, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import {
CACHE_KEY_EXECUTION_FAILED,
FOREIGN_EXPLORER_API,
FOREIGN_RPC_POLLING_INTERVAL,
HOME_EXPLORER_API,
HOME_RPC_POLLING_INTERVAL,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import { ExecutionData } from '../hooks/useMessageConfirmations' import { ExecutionData } from '../hooks/useMessageConfirmations'
import { import {
APIPendingTransaction, APIPendingTransaction,
APITransaction, APITransaction,
GetFailedTransactionParams, GetTransactionParams,
GetPendingTransactionParams GetPendingTransactionParams,
getLogs
} from './explorer' } 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'
export const getFinalizationEvent = async ( const getPastEventsWithFallback = (api: string, web3: Web3, contract: Contract, eventName: string, options: any) =>
contract: Maybe<Contract>, 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, eventName: string,
web3: Maybe<Web3>, web3: Web3,
setResult: React.Dispatch<React.SetStateAction<ExecutionData>>, messageId: string,
waitingBlocksResolved: boolean, api: string = ''
message: MessageObject,
interval: number,
subscriptions: number[],
timestamp: number,
collectedSignaturesEvent: Maybe<EventData>,
getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
setFailedExecution: Function,
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingExecution: Function
) => { ) => {
if (!contract || !web3 || !waitingBlocksResolved) return
// 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: {
messageId: message.id messageId
} }
}) })
if (events.length > 0) { if (events.length > 0) {
@@ -47,14 +58,42 @@ export const getFinalizationEvent = async (
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from) const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
setResult({ return {
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
validator: validatorAddress, validator: validatorAddress,
txHash: event.transactionHash, txHash: event.transactionHash,
timestamp: blockTimestamp, timestamp: blockTimestamp,
executionResult: event.returnValues.status executionResult: event.returnValues.status
}) }
}
return null
}
export const getFinalizationEvent = async (
fromHome: boolean,
contract: Contract,
web3: Web3,
setResult: React.Dispatch<React.SetStateAction<ExecutionData>>,
message: MessageObject,
setTimeoutId: (timeoutId: number) => void,
isCancelled: () => boolean,
startBlock: number,
collectedSignaturesEvent: Maybe<EventData>,
getFailedExecution: (args: GetTransactionParams) => Promise<APITransaction[]>,
setFailedExecution: Function,
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingExecution: Function,
setExecutionEventsFetched: Function
) => {
const eventName = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
const api = fromHome ? FOREIGN_EXPLORER_API : HOME_EXPLORER_API
const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id, api)
if (successExecutionData) {
setResult(successExecutionData)
} else { } else {
setExecutionEventsFetched(true)
// If event is defined, it means it is a message from Home to Foreign // If event is defined, it means it is a message from Home to Foreign
if (collectedSignaturesEvent) { if (collectedSignaturesEvent) {
const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay
@@ -82,14 +121,15 @@ export const getFinalizationEvent = async (
} else { } else {
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}` const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}`
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey) const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
const blockProvider = fromHome ? foreignBlockNumberProvider : homeBlockNumberProvider
if (!failedFromCache) { if (!failedFromCache) {
const failedTransactions = await getFailedExecution({ const failedTransactions = await getFailedExecution({
account: validator, account: validator,
to: contract.options.address, to: contract.options.address,
messageData: message.data, messageData: message.data,
startTimestamp: timestamp, startBlock,
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP endBlock: blockProvider.get() || 0
}) })
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
@@ -112,26 +152,28 @@ export const getFinalizationEvent = async (
} }
} }
const timeoutId = setTimeout( if (!isCancelled()) {
() => const timeoutId = setTimeout(
getFinalizationEvent( () =>
contract, getFinalizationEvent(
eventName, fromHome,
web3, contract,
setResult, web3,
waitingBlocksResolved, setResult,
message, message,
interval, setTimeoutId,
subscriptions, isCancelled,
timestamp, startBlock,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution, setFailedExecution,
getPendingExecution, getPendingExecution,
setPendingExecution setPendingExecution,
), setExecutionEventsFetched
interval ),
) fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL
subscriptions.push(timeoutId) )
setTimeoutId(timeoutId)
}
} }
} }

View File

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

View File

@@ -0,0 +1,26 @@
import Web3 from 'web3'
function strip0x(s: string) {
return Web3.utils.isHexStrict(s) ? s.substr(2) : s
}
export interface Signature {
v: string
r: string
s: string
}
export function signatureToVRS(rawSignature: string): Signature {
const signature = strip0x(rawSignature)
const v = signature.substr(64 * 2)
const r = signature.substr(0, 32 * 2)
const s = signature.substr(32 * 2, 32 * 2)
return { v, r, s }
}
export function packSignatures(array: Array<Signature>): string {
const length = strip0x(Web3.utils.toHex(array.length))
const msgLength = length.length === 1 ? `0${length}` : length
const [v, r, s] = array.reduce(([vs, rs, ss], { v, r, s }) => [vs + v, rs + r, ss + s], ['', '', ''])
return `0x${msgLength}${v}${r}${s}`
}

View File

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

View File

@@ -56,6 +56,11 @@ const OLD_AMB_USER_REQUEST_FOR_SIGNATURE_ABI = [
{ {
anonymous: false, anonymous: false,
inputs: [ inputs: [
{
indexed: true,
name: 'messageId',
type: 'bytes32'
},
{ {
indexed: false, indexed: false,
name: 'encodedData', name: 'encodedData',
@@ -71,6 +76,11 @@ const OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI = [
{ {
anonymous: false, anonymous: false,
inputs: [ inputs: [
{
indexed: true,
name: 'messageId',
type: 'bytes32'
},
{ {
indexed: false, indexed: false,
name: 'encodedData', name: 'encodedData',

View File

@@ -2,10 +2,6 @@ function strip0x(input) {
return input.replace(/^0x/, '') return input.replace(/^0x/, '')
} }
function addTxHashToData({ encodedData, transactionHash }) {
return encodedData.slice(0, 2) + strip0x(transactionHash) + encodedData.slice(2)
}
/** /**
* Decodes the datatype byte from the AMB message. * Decodes the datatype byte from the AMB message.
* First (the most significant bit) denotes if the message should be forwarded to the manual lane. * First (the most significant bit) denotes if the message should be forwarded to the manual lane.
@@ -33,8 +29,18 @@ function parseAMBMessage(message) {
} }
} }
module.exports = { const normalizeAMBMessageEvent = e => {
addTxHashToData, let msgData = e.returnValues.encodedData
parseAMBMessage, if (!e.returnValues.messageId) {
strip0x // append tx hash to an old message, where message id was not used
// for old messages, e.messageId is a corresponding transactionHash
msgData = e.transactionHash + msgData.slice(2)
}
return parseAMBMessage(msgData)
}
module.exports = {
strip0x,
parseAMBMessage,
normalizeAMBMessageEvent
} }

View File

@@ -1,6 +1,6 @@
const { BN } = require('web3-utils') const { BN } = require('web3-utils')
const { expect } = require('chai').use(require('bn-chai')(BN)) const { expect } = require('chai').use(require('bn-chai')(BN))
const { parseAMBMessage, strip0x, addTxHashToData } = require('../message') const { parseAMBMessage, strip0x } = require('../message')
describe('strip0x', () => { describe('strip0x', () => {
it('should remove 0x from input', () => { it('should remove 0x from input', () => {
@@ -24,28 +24,6 @@ describe('strip0x', () => {
expect(result).to.be.equal(input) expect(result).to.be.equal(input)
}) })
}) })
describe('addTxHashToData', () => {
it('should add txHash to encoded data at position 2', () => {
// Given
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const encodedData = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
const transactionHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const message = `0x${strip0x(transactionHash)}${strip0x(msgSender)}${strip0x(
msgExecutor
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
// When
const result = addTxHashToData({ encodedData, transactionHash })
// Then
expect(result).to.be.equal(message)
})
})
describe('parseAMBMessage', () => { describe('parseAMBMessage', () => {
it('should parse data type 00', () => { it('should parse data type 00', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e' const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'

View File

@@ -32,8 +32,6 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
oracle-amb-host: oracle-amb-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-amb-stake-erc-to-erc-host: ui-amb-stake-erc-to-erc-host:

View File

@@ -21,8 +21,6 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
oracle-amb-host: oracle-amb-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
verifier: verifier:

View File

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

View File

@@ -1,26 +0,0 @@
---
- name: Slurp docker compose file
slurp:
src: "/home/poadocker/bridge/oracle/{{ file }}.yml"
register: docker_compose_slurp
- name: Parse docker compose file
set_fact:
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
- name: Add the external network used to connect to Parity nodes
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
- name: Add all Oracle containers to the network
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': docker_compose_parsed.services[item].networks | union(['ultimate'])}}}, recursive=True) }}"
with_items: "{{ docker_compose_parsed.services }}"
- name: Expose Redis port to allow connecting from redis-cli
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {'redis': {'ports': ['6379:6379']}}}, recursive=True) }}"
- name: Write updated docker file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "/home/poadocker/bridge/oracle/{{ file }}.yml"

View File

@@ -1,33 +1,22 @@
--- ---
- name: Overwrite Oracle the docker-compose - name: Prepare Oracle for ultimate tests
hosts: oracle hosts: oracle
become: true become: true
tasks: tasks:
- name: stop the service - name: Connect parity to oracle networks
shell: service poabridge stop shell: "docker network create {{ item }} && docker network connect {{ item }} parity1 && docker network connect {{ item }} parity2"
with_items:
- name: ReTag current oracle image - oracle_net_db_bridge_request
shell: docker tag $(docker images --format '{{ '{{' }}.Repository{{ '}}' }}:{{ '{{' }}.Tag{{ '}}' }}' | grep -m 1 tokenbridge-e2e-oracle) oracle:ultimate-testing - oracle_net_db_bridge_collected
- oracle_net_db_bridge_affirmation
- oracle_net_db_bridge_transfer
- oracle_net_db_bridge_senderhome
- oracle_net_db_bridge_senderforeign
- oracle_net_rabbit_bridge_request
- oracle_net_rabbit_bridge_collected
- oracle_net_rabbit_bridge_affirmation
- oracle_net_rabbit_bridge_transfer
- oracle_net_rabbit_bridge_senderhome
- oracle_net_rabbit_bridge_senderforeign
delegate_to: 127.0.0.1 delegate_to: 127.0.0.1
become: false become: false
- name: Replace oracle image
replace:
path: "/home/poadocker/bridge/oracle/{{ item }}.yml"
regexp: 'poanetwork/tokenbridge-oracle:latest'
replace: "oracle:ultimate-testing"
with_items:
- docker-compose
- docker-compose-transfer
- docker-compose-erc-native
- include_tasks: oracle-add-docker-external-network.yml
with_items:
- docker-compose
- docker-compose-transfer
- docker-compose-erc-native
loop_control:
loop_var: file
- name: start the service
shell: service poabridge start

View File

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

View File

@@ -32,8 +32,6 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
oracle-erc-to-erc-host: oracle-erc-to-erc-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-erc-to-erc-host: ui-erc-to-erc-host:

View File

@@ -32,8 +32,6 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
oracle-erc-to-native-host: oracle-erc-to-native-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ORACLE_HOME_START_BLOCK: 1 ORACLE_HOME_START_BLOCK: 1

View File

@@ -32,8 +32,6 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
oracle-native-to-erc-host: oracle-native-to-erc-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-native-to-erc-host: ui-native-to-erc-host:

View File

@@ -5,13 +5,13 @@ ORACLE_ALLOW_HTTP_FOR_RPC: yes
ORACLE_LOG_LEVEL: debug ORACLE_LOG_LEVEL: debug
## Home contract ## Home contract
COMMON_HOME_RPC_URL: "https://sokol.poa.network" COMMON_HOME_RPC_URL: "http://parity1:8545"
UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol" UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol"
UI_HOME_WITHOUT_EVENTS: false 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: "https://sokol.poa.network" COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan" UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan"
UI_FOREIGN_WITHOUT_EVENTS: false UI_FOREIGN_WITHOUT_EVENTS: false
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000 ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
@@ -52,3 +52,7 @@ MONITOR_FOREIGN_START_BLOCK: 0
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
# disable building and pulling of docker images from the Docker Hub
skip_pull: true
skip_build: true

View File

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

View File

@@ -3,3 +3,4 @@
shell: docker-compose pull shell: docker-compose pull
args: args:
chdir: "{{ bridge_path }}/monitor" chdir: "{{ bridge_path }}/monitor"
when: skip_pull is undefined

View File

@@ -3,3 +3,4 @@
shell: docker-compose pull shell: docker-compose pull
args: args:
chdir: "{{ bridge_path }}/oracle" chdir: "{{ bridge_path }}/oracle"
when: skip_pull is undefined

View File

@@ -3,3 +3,4 @@
shell: docker-compose build shell: docker-compose build
args: args:
chdir: "{{ bridge_path }}/ui" chdir: "{{ bridge_path }}/ui"
when: skip_build is undefined

View File

@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000 COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000 UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1 COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000 UI_PORT=3003
UI_STYLES=stake UI_STYLES=stake

View File

@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000 COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000 UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1 COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000 UI_PORT=3002
UI_STYLES=core UI_STYLES=core

View File

@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000 COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000 UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1 COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000 UI_PORT=3001
UI_STYLES=core UI_STYLES=core

View File

@@ -4,12 +4,14 @@ networks:
external: true external: true
services: services:
parity1: parity1:
container_name: parity1
build: ../parity build: ../parity
ports: ports:
- "8541:8545" - "8541:8545"
networks: networks:
- ultimate - ultimate
parity2: parity2:
container_name: parity2
build: build:
context: ../parity context: ../parity
dockerfile: Dockerfile-foreign dockerfile: Dockerfile-foreign

View File

@@ -80,9 +80,9 @@ while [ "$1" != "" ]; do
docker-compose up -d ui ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20 docker-compose up -d ui ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20
docker-compose run -d -p 3000:3000 ui yarn start docker-compose run -d -p 3000:3000 ui yarn start
docker-compose run -d -p 3001:3000 ui-erc20 yarn start docker-compose run -d -p 3001:3001 ui-erc20 yarn start
docker-compose run -d -p 3002:3000 ui-erc20-native yarn start docker-compose run -d -p 3002:3002 ui-erc20-native yarn start
docker-compose run -d -p 3003:3000 ui-amb-stake-erc20-erc20 yarn start docker-compose run -d -p 3003:3003 ui-amb-stake-erc20-erc20 yarn start
fi fi
if [ "$1" == "alm" ]; then if [ "$1" == "alm" ]; then

View File

@@ -25,3 +25,9 @@ MONITOR_CACHE_EVENTS=true
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST= MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST=
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST= MONITOR_HOME_TO_FOREIGN_BLOCK_LIST=
# MONITOR_HOME_VALIDATORS_BALANCE_ENABLE=0x... 0x... 0x...
# MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE=0x... 0x... 0x...
# MONITOR_HOME_EXPLORER_API=
MONITOR_FOREIGN_EXPLORER_API=https://api.bscscan.com/api?apikey=YourApiKeyToken

View File

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

View File

@@ -12,10 +12,6 @@ const { web3Home } = require('./utils/web3')
const { COMMON_HOME_BRIDGE_ADDRESS, MONITOR_BRIDGE_NAME } = process.env const { COMMON_HOME_BRIDGE_ADDRESS, MONITOR_BRIDGE_NAME } = process.env
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
const { HOME_ERC_TO_ERC_ABI } = require('../commons') const { HOME_ERC_TO_ERC_ABI } = require('../commons')
async function checkWorker() { async function checkWorker() {
@@ -45,27 +41,6 @@ async function checkWorker() {
const vBalances = await validators(bridgeMode) const vBalances = await validators(bridgeMode)
if (!vBalances) throw new Error('vBalances is empty: ' + JSON.stringify(vBalances)) if (!vBalances) throw new Error('vBalances is empty: ' + JSON.stringify(vBalances))
vBalances.homeOk = true
vBalances.foreignOk = true
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
for (const hv in vBalances.home.validators) {
if (vBalances.home.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
vBalances.homeOk = false
break
}
}
}
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
for (const hv in vBalances.foreign.validators) {
if (vBalances.foreign.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
vBalances.foreignOk = false
break
}
}
}
vBalances.ok = vBalances.homeOk && vBalances.foreignOk vBalances.ok = vBalances.homeOk && vBalances.foreignOk
vBalances.health = true vBalances.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/validators.json`, vBalances) writeFile(`/responses/${MONITOR_BRIDGE_NAME}/validators.json`, vBalances)

View File

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

View File

@@ -1,8 +1,11 @@
require('dotenv').config() require('dotenv').config()
const logger = require('./logger')('checkWorker3') const logger = require('./logger')('checkWorker3')
const stuckTransfers = require('./stuckTransfers') const stuckTransfers = require('./stuckTransfers')
const detectMediators = require('./detectMediators')
const detectFailures = require('./detectFailures')
const { writeFile, createDir } = require('./utils/file') const { writeFile, createDir } = require('./utils/file')
const { web3Home } = require('./utils/web3') const { web3Home } = require('./utils/web3')
const { saveCache } = require('./utils/web3Cache')
const { MONITOR_BRIDGE_NAME, COMMON_HOME_BRIDGE_ADDRESS } = process.env const { MONITOR_BRIDGE_NAME, COMMON_HOME_BRIDGE_ADDRESS } = process.env
const { getBridgeMode, HOME_NATIVE_TO_ERC_ABI, BRIDGE_MODES } = require('../commons') const { getBridgeMode, HOME_NATIVE_TO_ERC_ABI, BRIDGE_MODES } = require('../commons')
@@ -20,6 +23,23 @@ async function checkWorker3() {
transfers.health = true transfers.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/stuckTransfers.json`, transfers) writeFile(`/responses/${MONITOR_BRIDGE_NAME}/stuckTransfers.json`, transfers)
logger.debug('Done') logger.debug('Done')
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
logger.debug('calling detectMediators()')
const mediators = await detectMediators(bridgeMode)
mediators.ok = true
mediators.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/mediators.json`, mediators)
logger.debug('calling detectFailures()')
const failures = await detectFailures(bridgeMode)
failures.ok = true
failures.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/failures.json`, failures)
saveCache()
logger.debug('Done')
} }
} catch (e) { } catch (e) {
logger.error('checkWorker3.js', e) logger.error('checkWorker3.js', e)

86
monitor/detectFailures.js Normal file
View File

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

148
monitor/detectMediators.js Normal file
View File

@@ -0,0 +1,148 @@
require('dotenv').config()
const logger = require('./logger')('stuckTransfers.js')
const { isHomeContract, isForeignContract } = require('./utils/web3Cache')
const eventsInfo = require('./utils/events')
const { getHomeTxSender, getForeignTxSender } = require('./utils/web3Cache')
const { addExecutionStatus } = require('./utils/message')
const { normalizeAMBMessageEvent } = require('../commons')
function countInteractions(requests) {
const stats = {}
requests.forEach(msg => {
if (!stats[msg.sender]) {
stats[msg.sender] = {}
}
if (!stats[msg.sender][msg.executor]) {
stats[msg.sender][msg.executor] = 0
}
stats[msg.sender][msg.executor] += 1
})
return stats
}
const normalize = event => ({
...normalizeAMBMessageEvent(event),
txHash: event.transactionHash,
logIndex: event.transactionLogIndex
})
const flat = arrays => Array.prototype.concat.apply([], arrays)
function findPermanentMediators(homeToForeignC2C, foreignToHomeC2C) {
return flat(
Object.entries(homeToForeignC2C).map(([homeMediator, homeStats]) =>
Object.entries(foreignToHomeC2C)
.map(([foreignMediator, foreignStats]) => ({
homeMediator,
foreignMediator,
homeToForeignRequests: homeStats[foreignMediator],
foreignToHomeRequests: foreignStats[homeMediator]
}))
.filter(stats => stats.homeToForeignRequests && stats.foreignToHomeRequests)
)
)
}
function findFloatingMediators(homeToForeignC2C, foreignToHomeC2C) {
return Object.entries(homeToForeignC2C)
.map(([homeMediator, homeStats]) => {
const noResponses = ([executor]) => !foreignToHomeC2C[executor] || !foreignToHomeC2C[executor][homeMediator]
const executorRequestPairs = Object.entries(homeStats).filter(noResponses)
return {
mediator: homeMediator,
executors: executorRequestPairs.map(pair => pair[0]),
requests: executorRequestPairs.map(pair => pair[1])
}
})
.filter(stats => stats.executors.length > 0)
}
function findRemotelyControlledMediators(statsU2C) {
return Object.entries(statsU2C).map(([user, stats]) => ({
user,
executors: Object.keys(stats),
requests: Object.values(stats)
}))
}
function findUnknown(statsA2U) {
return Object.entries(statsA2U).map(([sender, stats]) => ({
sender,
executors: Object.keys(stats),
requests: Object.values(stats)
}))
}
async function main(mode) {
const {
homeToForeignRequests,
foreignToHomeRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations
} = await eventsInfo(mode)
const homeToForeign = homeToForeignRequests
.map(normalize)
.map(addExecutionStatus(homeToForeignConfirmations))
.filter(x => typeof x.status === 'boolean')
const foreignToHome = foreignToHomeRequests
.map(normalize)
.map(addExecutionStatus(foreignToHomeConfirmations))
.filter(x => typeof x.status === 'boolean')
for (const event of homeToForeign) {
// AMB contract emits a single UserRequestForSignature event for every home->foreign request.
// If index of such event in logs is not equal to 0x0, then some other events occurred before it,
// meaning that the sender was a contract.
// Alternatively, the sender is a contract, if the message sender is not equal to tx.origin.
event.isSenderAContract = event.logIndex !== '0x0' || (await getHomeTxSender(event.txHash)) !== event.sender
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
event.isExecutorAContract = !event.status || (await isForeignContract(event.executor))
}
for (const event of foreignToHome) {
// AMB contract emits a single UserRequestForAffirmation event for every foreign->home request.
// If index of such event in logs is not equal to 0x0, then some other events occurred before it,
// meaning that the sender was a contract.
// Alternatively, the sender is a contract, if the message sender is not equal to tx.origin.
event.isSenderAContract = event.logIndex !== '0x0' || (await getForeignTxSender(event.txHash)) !== event.sender
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
event.isExecutorAContract = !event.status || (await isHomeContract(event.executor))
}
const C2C = event => event.isSenderAContract && event.isExecutorAContract
const U2C = event => !event.isSenderAContract && event.isExecutorAContract
const A2U = event => !event.isExecutorAContract
const homeToForeignC2C = countInteractions(homeToForeign.filter(C2C))
const foreignToHomeC2C = countInteractions(foreignToHome.filter(C2C))
const homeToForeignU2C = countInteractions(homeToForeign.filter(U2C))
const foreignToHomeU2C = countInteractions(foreignToHome.filter(U2C))
const homeToForeignA2U = countInteractions(homeToForeign.filter(A2U))
const foreignToHomeA2U = countInteractions(foreignToHome.filter(A2U))
const permanentMediators = findPermanentMediators(homeToForeignC2C, foreignToHomeC2C)
const floatingMediators = {
home: findFloatingMediators(homeToForeignC2C, foreignToHomeC2C),
foreign: findFloatingMediators(foreignToHomeC2C, homeToForeignC2C)
}
const remotelyControlledMediators = {
home: findRemotelyControlledMediators(homeToForeignU2C),
foreign: findRemotelyControlledMediators(foreignToHomeU2C)
}
const unknown = {
home: findUnknown(homeToForeignA2U),
foreign: findUnknown(foreignToHomeA2U)
}
logger.debug('Done')
return {
permanentMediators,
floatingMediators,
remotelyControlledMediators,
unknown,
lastChecked: Math.floor(Date.now() / 1000)
}
}
module.exports = main

View File

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

View File

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

View File

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

20
monitor/metricsWorker.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,30 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
async function readFile(filePath) { function readFile(filePath, parseJson = true) {
try { try {
const content = await fs.readFileSync(filePath) const content = fs.readFileSync(filePath)
if (!parseJson) return content
const json = JSON.parse(content) const json = JSON.parse(content)
const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked
return Object.assign({}, json, { timeDiff }) return Object.assign({}, json, { timeDiff })
} catch (e) { } catch (e) {
console.error(e) console.error('readFlle', e)
return { return {
error: 'the bridge statistics are not available' error: 'the bridge statistics are not available'
} }
} }
} }
function writeFile(filePath, object, useCwd = true) { function writeFile(filePath, object, paramOptions = {}) {
const defaultOptions = {
useCwd: true,
stringify: true
}
const { useCwd, stringify } = Object.assign({}, defaultOptions, paramOptions)
const fullPath = useCwd ? path.join(process.cwd(), filePath) : filePath const fullPath = useCwd ? path.join(process.cwd(), filePath) : filePath
fs.writeFileSync(fullPath, JSON.stringify(object, null, 4)) fs.writeFileSync(fullPath, stringify ? JSON.stringify(object, null, 4) : object)
} }
function createDir(dirPath) { function createDir(dirPath) {

View File

@@ -1,32 +1,33 @@
const { parseAMBMessage } = require('../../commons') const { normalizeAMBMessageEvent } = require('../../commons')
const { readAccessListFile } = require('./file') const { readAccessListFile } = require('./file')
const { MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST, MONITOR_HOME_TO_FOREIGN_BLOCK_LIST } = process.env const { MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST, MONITOR_HOME_TO_FOREIGN_BLOCK_LIST } = process.env
const keyAMB = e => [e.messageId, e.sender, e.executor].join(',').toLowerCase() const keyAMB = e => [e.messageId, e.sender, e.executor].join(',').toLowerCase()
const normalizeAMBMessage = e => {
let msgData = e.returnValues.encodedData
if (!e.returnValues.messageId) {
// append tx hash to an old message, where message id was not used
// for old messages, e.messageId is a corresponding transactionHash
msgData = e.transactionHash + msgData.slice(2)
}
return parseAMBMessage(msgData)
}
function deliveredMsgNotProcessed(processedList) { function deliveredMsgNotProcessed(processedList) {
const keys = new Set() const keys = new Set()
processedList.forEach(processedMsg => keys.add(keyAMB(processedMsg.returnValues))) processedList.forEach(processedMsg => keys.add(keyAMB(processedMsg.returnValues)))
return deliveredMsg => !keys.has(keyAMB(normalizeAMBMessage(deliveredMsg))) return deliveredMsg => !keys.has(keyAMB(normalizeAMBMessageEvent(deliveredMsg)))
} }
function processedMsgNotDelivered(deliveredList) { function processedMsgNotDelivered(deliveredList) {
const keys = new Set() const keys = new Set()
deliveredList.forEach(deliveredMsg => keys.add(keyAMB(normalizeAMBMessage(deliveredMsg)))) deliveredList.forEach(deliveredMsg => keys.add(keyAMB(normalizeAMBMessageEvent(deliveredMsg))))
return processedMsg => !keys.has(keyAMB(processedMsg.returnValues)) return processedMsg => !keys.has(keyAMB(processedMsg.returnValues))
} }
function addExecutionStatus(processedList) {
const statuses = {}
processedList.forEach(processedMsg => {
statuses[keyAMB(processedMsg.returnValues)] = processedMsg.returnValues.status
})
return deliveredMsg => {
deliveredMsg.status = statuses[keyAMB(deliveredMsg)]
return deliveredMsg
}
}
/** /**
* Normalizes the different event objects to facilitate data processing * Normalizes the different event objects to facilitate data processing
* @param {Object} event * @param {Object} event
@@ -70,23 +71,24 @@ const manuallyProcessedAMBHomeToForeignRequests = () => {
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST) { if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
const allowanceList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST) const allowanceList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST)
return e => { return e => {
const { sender, executor, decodedDataType } = normalizeAMBMessage(e) const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
return (!allowanceList.includes(sender) && !allowanceList.includes(executor)) || decodedDataType.manualLane return (!allowanceList.includes(sender) && !allowanceList.includes(executor)) || decodedDataType.manualLane
} }
} else if (MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) { } else if (MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
const blockList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) const blockList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_BLOCK_LIST)
return e => { return e => {
const { sender, executor, decodedDataType } = normalizeAMBMessage(e) const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
return blockList.includes(sender) || blockList.includes(executor) || decodedDataType.manualLane return blockList.includes(sender) || blockList.includes(executor) || decodedDataType.manualLane
} }
} else { } else {
return e => normalizeAMBMessage(e).decodedDataType.manualLane return e => normalizeAMBMessageEvent(e).decodedDataType.manualLane
} }
} }
module.exports = { module.exports = {
deliveredMsgNotProcessed, deliveredMsgNotProcessed,
processedMsgNotDelivered, processedMsgNotDelivered,
addExecutionStatus,
normalizeEventInformation, normalizeEventInformation,
eventWithoutReference, eventWithoutReference,
unclaimedHomeToForeignRequests, unclaimedHomeToForeignRequests,

View File

@@ -1,24 +1,93 @@
const fetch = require('node-fetch')
const logger = require('../logger')('web3Cache') const logger = require('../logger')('web3Cache')
const { readCacheFile, writeCacheFile } = require('./file') const { readCacheFile, writeCacheFile } = require('./file')
const { web3Home } = require('./web3') const { web3Home, web3Foreign } = require('./web3')
const { getPastEvents: commonGetPastEvents } = require('../../commons') const { getPastEvents: commonGetPastEvents } = require('../../commons')
const { MONITOR_BRIDGE_NAME, MONITOR_CACHE_EVENTS } = process.env const {
MONITOR_BRIDGE_NAME,
MONITOR_CACHE_EVENTS,
MONITOR_FOREIGN_EXPLORER_API,
MONITOR_HOME_EXPLORER_API
} = process.env
let isDirty = false let isDirty = false
const homeTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/txSenders.json` const homeTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/txSenders.json`
const cachedHomeTxSenders = readCacheFile(homeTxSendersCacheFile) || {} const cachedHomeTxSenders = readCacheFile(homeTxSendersCacheFile) || {}
const foreignTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/foreign/txSenders.json`
const cachedForeignTxSenders = readCacheFile(foreignTxSendersCacheFile) || {}
const homeIsContractCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/isContract.json`
const cachedHomeIsContract = readCacheFile(homeIsContractCacheFile) || {}
const foreignIsContractCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/foreign/isContract.json`
const cachedForeignIsContract = readCacheFile(foreignIsContractCacheFile) || {}
async function getHomeTxSender(txHash) { async function getHomeTxSender(txHash) {
if (!cachedHomeTxSenders[txHash]) { if (!cachedHomeTxSenders[txHash]) {
logger.debug(`Fetching sender for tx ${txHash}`) logger.debug(`Fetching sender for home tx ${txHash}`)
cachedHomeTxSenders[txHash] = (await web3Home.eth.getTransaction(txHash)).from.toLowerCase() cachedHomeTxSenders[txHash] = (await web3Home.eth.getTransaction(txHash)).from.toLowerCase()
isDirty = true isDirty = true
} }
return cachedHomeTxSenders[txHash] return cachedHomeTxSenders[txHash]
} }
async function getForeignTxSender(txHash) {
if (!cachedForeignTxSenders[txHash]) {
logger.debug(`Fetching sender for foreign tx ${txHash}`)
cachedForeignTxSenders[txHash] = (await web3Foreign.eth.getTransaction(txHash)).from.toLowerCase()
isDirty = true
}
return cachedForeignTxSenders[txHash]
}
async function isHomeContract(address) {
if (typeof cachedHomeIsContract[address] !== 'boolean') {
logger.debug(`Fetching home contract code size for tx ${address}`)
cachedHomeIsContract[address] = (await web3Home.eth.getCode(address)).length > 2
isDirty = true
}
return cachedHomeIsContract[address]
}
async function isForeignContract(address) {
if (typeof cachedForeignIsContract[address] !== 'boolean') {
logger.debug(`Fetching foreign contract code size for tx ${address}`)
cachedForeignIsContract[address] = (await web3Foreign.eth.getCode(address)).length > 2
isDirty = true
}
return cachedForeignIsContract[address]
}
function getPastEventsWithAPIFallback(contract, options) {
return commonGetPastEvents(contract, options).catch(async e => {
const [api, web3] =
options.chain === 'home' ? [MONITOR_HOME_EXPLORER_API, web3Home] : [MONITOR_FOREIGN_EXPLORER_API, web3Foreign]
if (api && e.message.includes('exceed maximum block range')) {
logger.debug('BLOCK RANGE EXCEED, using fallback to the explorer API')
const abi = contract.options.jsonInterface.find(abi => abi.type === 'event' && abi.name === options.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)
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))
}))
} else {
throw new Error(e)
}
})
}
async function getPastEvents(contract, options) { async function getPastEvents(contract, options) {
if (MONITOR_CACHE_EVENTS !== 'true') { if (MONITOR_CACHE_EVENTS !== 'true') {
return commonGetPastEvents(contract, options) return commonGetPastEvents(contract, options)
@@ -61,14 +130,14 @@ async function getPastEvents(contract, options) {
// requested: A...B // requested: A...B
// cached: C...D // cached: C...D
logger.debug(`Fetching events for blocks ${fromBlock}...${toBlock}`) logger.debug(`Fetching events for blocks ${fromBlock}...${toBlock}`)
result = await commonGetPastEvents(contract, options) result = await getPastEventsWithAPIFallback(contract, options)
} else if (fromBlock < cachedFromBlock && toBlock <= cachedToBlock) { } else if (fromBlock < cachedFromBlock && toBlock <= cachedToBlock) {
// requested: A...B // requested: A...B
// cached: C...D // cached: C...D
logger.debug(`Cache hit for blocks ${cachedFromBlock}...${toBlock}`) logger.debug(`Cache hit for blocks ${cachedFromBlock}...${toBlock}`)
logger.debug(`Fetching events for blocks ${fromBlock}...${cachedFromBlock - 1}`) logger.debug(`Fetching events for blocks ${fromBlock}...${cachedFromBlock - 1}`)
result = [ result = [
...(await commonGetPastEvents(contract, { ...options, toBlock: cachedFromBlock - 1 })), ...(await getPastEventsWithAPIFallback(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...cachedEvents.filter(e => e.blockNumber <= toBlock) ...cachedEvents.filter(e => e.blockNumber <= toBlock)
] ]
} else if (fromBlock < cachedFromBlock && cachedToBlock < toBlock) { } else if (fromBlock < cachedFromBlock && cachedToBlock < toBlock) {
@@ -78,9 +147,9 @@ async function getPastEvents(contract, options) {
logger.debug(`Fetching events for blocks ${fromBlock}...${cachedFromBlock - 1}`) logger.debug(`Fetching events for blocks ${fromBlock}...${cachedFromBlock - 1}`)
logger.debug(`Fetching events for blocks ${cachedToBlock + 1}...${toBlock}`) logger.debug(`Fetching events for blocks ${cachedToBlock + 1}...${toBlock}`)
result = [ result = [
...(await commonGetPastEvents(contract, { ...options, toBlock: cachedFromBlock - 1 })), ...(await getPastEventsWithAPIFallback(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...cachedEvents, ...cachedEvents,
...(await commonGetPastEvents(contract, { ...options, fromBlock: cachedToBlock + 1 })) ...(await getPastEventsWithAPIFallback(contract, { ...options, fromBlock: cachedToBlock + 1 }))
] ]
} else if (cachedFromBlock <= fromBlock && toBlock <= cachedToBlock) { } else if (cachedFromBlock <= fromBlock && toBlock <= cachedToBlock) {
// requested: A.B // requested: A.B
@@ -94,7 +163,7 @@ async function getPastEvents(contract, options) {
logger.debug(`Fetching events for blocks ${cachedToBlock + 1}...${toBlock}`) logger.debug(`Fetching events for blocks ${cachedToBlock + 1}...${toBlock}`)
result = [ result = [
...cachedEvents.filter(e => e.blockNumber >= fromBlock), ...cachedEvents.filter(e => e.blockNumber >= fromBlock),
...(await commonGetPastEvents(contract, { ...options, fromBlock: cachedToBlock + 1 })) ...(await getPastEventsWithAPIFallback(contract, { ...options, fromBlock: cachedToBlock + 1 }))
] ]
} else { } else {
throw new Error( throw new Error(
@@ -123,11 +192,16 @@ function saveCache() {
if (isDirty) { if (isDirty) {
logger.debug('Saving cache on disk') logger.debug('Saving cache on disk')
writeCacheFile(homeTxSendersCacheFile, cachedHomeTxSenders) writeCacheFile(homeTxSendersCacheFile, cachedHomeTxSenders)
writeCacheFile(homeIsContractCacheFile, cachedHomeIsContract)
writeCacheFile(foreignIsContractCacheFile, cachedForeignIsContract)
} }
} }
module.exports = { module.exports = {
getHomeTxSender, getHomeTxSender,
getForeignTxSender,
isHomeContract,
isForeignContract,
getPastEvents, getPastEvents,
saveCache saveCache
} }

View File

@@ -16,10 +16,13 @@ const {
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,
COMMON_FOREIGN_GAS_PRICE_FACTOR COMMON_FOREIGN_GAS_PRICE_FACTOR,
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE,
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE
} = process.env } = process.env
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0 const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0 const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
const homeGasPriceSupplierOpts = { const homeGasPriceSupplierOpts = {
speedType: COMMON_HOME_GAS_PRICE_SPEED_TYPE, speedType: COMMON_HOME_GAS_PRICE_SPEED_TYPE,
@@ -33,12 +36,6 @@ const foreignGasPriceSupplierOpts = {
logger logger
} }
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
async function main(bridgeMode) { async function main(bridgeMode) {
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode) const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode)
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS) const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
@@ -109,53 +106,61 @@ async function main(bridgeMode) {
} }
let validatorsMatch = true let validatorsMatch = true
logger.debug('calling asyncForEach foreignValidators foreignVBalances') const foreignValidatorsWithBalanceCheck =
await asyncForEach(foreignValidators, async v => { typeof MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE === 'string'
const balance = await web3Foreign.eth.getBalance(v) ? MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE.split(' ')
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) { : foreignValidators
const leftTx = Web3Utils.toBN(balance) logger.debug('getting foreignValidators balances')
.div(foreignTxCost) await Promise.all(
.toString(10) foreignValidators.map(async v => {
foreignVBalances[v] = { foreignVBalances[v] = {}
balance: Web3Utils.fromWei(balance), if (foreignValidatorsWithBalanceCheck.includes(v)) {
leftTx: Number(leftTx), const balance = await web3Foreign.eth.getBalance(v)
gasPrice: Number(foreignGasPriceGwei) foreignVBalances[v].balance = Web3Utils.fromWei(balance)
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
foreignVBalances[v].leftTx = Number(
Web3Utils.toBN(balance)
.div(foreignTxCost)
.toString(10)
)
foreignVBalances[v].gasPrice = parseFloat(foreignGasPriceGwei)
}
} }
} else {
foreignVBalances[v] = {
balance: Web3Utils.fromWei(balance)
}
}
if (!homeValidators.includes(v)) { if (!homeValidators.includes(v)) {
validatorsMatch = false validatorsMatch = false
foreignVBalances[v].onlyOnForeign = true foreignVBalances[v].onlyOnForeign = true
}
})
logger.debug('calling asyncForEach homeValidators homeVBalances')
await asyncForEach(homeValidators, async v => {
const balance = await web3Home.eth.getBalance(v)
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
const leftTx = Web3Utils.toBN(balance)
.div(homeTxCost)
.toString(10)
homeVBalances[v] = {
balance: Web3Utils.fromWei(balance),
leftTx: Number(leftTx),
gasPrice: Number(homeGasPriceGwei)
} }
} else { })
homeVBalances[v] = { )
balance: Web3Utils.fromWei(balance)
}
}
if (!foreignValidators.includes(v)) { const homeValidatorsWithBalanceCheck =
validatorsMatch = false typeof MONITOR_HOME_VALIDATORS_BALANCE_ENABLE === 'string'
homeVBalances[v].onlyOnHome = true ? MONITOR_HOME_VALIDATORS_BALANCE_ENABLE.split(' ')
} : homeValidators
}) logger.debug('calling homeValidators balances')
await Promise.all(
homeValidators.map(async v => {
homeVBalances[v] = {}
if (homeValidatorsWithBalanceCheck.includes(v)) {
const balance = await web3Home.eth.getBalance(v)
homeVBalances[v].balance = Web3Utils.fromWei(balance)
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
homeVBalances[v].leftTx = Number(
Web3Utils.toBN(balance)
.div(homeTxCost)
.toString(10)
)
homeVBalances[v].gasPrice = parseFloat(homeGasPriceGwei)
}
}
if (!foreignValidators.includes(v)) {
validatorsMatch = false
homeVBalances[v].onlyOnHome = true
}
})
)
logger.debug('calling homeBridgeValidators.methods.requiredSignatures().call()') logger.debug('calling homeBridgeValidators.methods.requiredSignatures().call()')
const reqSigHome = await homeBridgeValidators.methods.requiredSignatures().call() const reqSigHome = await homeBridgeValidators.methods.requiredSignatures().call()
@@ -164,20 +169,22 @@ async function main(bridgeMode) {
logger.debug('Done') logger.debug('Done')
return { return {
home: { home: {
validators: { validators: homeVBalances,
...homeVBalances
},
requiredSignatures: Number(reqSigHome) requiredSignatures: Number(reqSigHome)
}, },
foreign: { foreign: {
validators: { validators: foreignVBalances,
...foreignVBalances
},
requiredSignatures: Number(reqSigForeign) requiredSignatures: Number(reqSigForeign)
}, },
requiredSignaturesMatch: reqSigHome === reqSigForeign, requiredSignaturesMatch: reqSigHome === reqSigForeign,
validatorsMatch, validatorsMatch,
lastChecked: Math.floor(Date.now() / 1000) lastChecked: Math.floor(Date.now() / 1000),
homeOk: Object.values(homeVBalances)
.filter(vb => typeof vb.leftTx === 'number')
.every(vb => vb.leftTx >= MONITOR_TX_NUMBER_THRESHOLD),
foreignOk: Object.values(foreignVBalances)
.filter(vb => typeof vb.leftTx === 'number')
.every(vb => vb.leftTx >= MONITOR_TX_NUMBER_THRESHOLD)
} }
} }

View File

@@ -142,7 +142,7 @@ describe('arbitrary message bridging', () => {
console.error(e) console.error(e)
}) })
await delay(5000) await delay(10000)
const newSignatures = await homeBridge.getPastEvents('SignedForUserRequest', { const newSignatures = await homeBridge.getPastEvents('SignedForUserRequest', {
fromBlock: 0, fromBlock: 0,

View File

@@ -14,12 +14,14 @@ COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000 COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000 ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000
COMMON_HOME_GAS_PRICE_FACTOR=1 COMMON_HOME_GAS_PRICE_FACTOR=1
ORACLE_HOME_TX_RESEND_INTERVAL=300000
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/ COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000 COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000
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
ORACLE_FOREIGN_TX_RESEND_INTERVAL=1200000
ORACLE_QUEUE_URL=amqp://rabbit ORACLE_QUEUE_URL=amqp://rabbit
ORACLE_REDIS_URL=redis://redis ORACLE_REDIS_URL=redis://redis

View File

@@ -1,6 +1,9 @@
const baseConfig = require('./base.config') const baseConfig = require('./base.config')
const { web3Foreign, web3ForeignRedundant } = require('../src/services/web3') const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
const { web3Foreign, web3ForeignRedundant, web3ForeignFallback } = require('../src/services/web3')
const { ORACLE_FOREIGN_TX_RESEND_INTERVAL } = process.env
module.exports = { module.exports = {
...baseConfig.bridgeConfig, ...baseConfig.bridgeConfig,
@@ -9,5 +12,7 @@ module.exports = {
id: 'foreign', id: 'foreign',
name: 'sender-foreign', name: 'sender-foreign',
web3: web3Foreign, web3: web3Foreign,
web3Redundant: web3ForeignRedundant web3Redundant: web3ForeignRedundant,
web3Fallback: web3ForeignFallback,
resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
} }

View File

@@ -1,6 +1,9 @@
const baseConfig = require('./base.config') const baseConfig = require('./base.config')
const { web3Home, web3HomeRedundant } = require('../src/services/web3') const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
const { web3Home, web3HomeRedundant, web3HomeFallback } = require('../src/services/web3')
const { ORACLE_HOME_TX_RESEND_INTERVAL } = process.env
module.exports = { module.exports = {
...baseConfig.bridgeConfig, ...baseConfig.bridgeConfig,
@@ -9,5 +12,7 @@ module.exports = {
id: 'home', id: 'home',
name: 'sender-home', name: 'sender-home',
web3: web3Home, web3: web3Home,
web3Redundant: web3HomeRedundant web3Redundant: web3HomeRedundant,
web3Fallback: web3HomeFallback,
resendInterval: parseInt(ORACLE_HOME_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
} }

View File

@@ -30,6 +30,8 @@ const config = require(path.join('../config/', process.argv[2]))
const web3Instance = config.web3 const web3Instance = config.web3
const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : config.web3 const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : config.web3
const { web3Fallback } = config
const nonceKey = `${config.id}:nonce` const nonceKey = `${config.id}:nonce`
let chainId = 0 let chainId = 0
@@ -45,6 +47,7 @@ async function initialize() {
connectSenderToQueue({ connectSenderToQueue({
queueName: config.queue, queueName: config.queue,
oldQueueName: config.oldQueue, oldQueueName: config.oldQueue,
resendInterval: config.resendInterval,
cb: options => { cb: options => {
if (config.maxProcessingTime) { if (config.maxProcessingTime) {
return watchdog(() => main(options), config.maxProcessingTime, () => { return watchdog(() => main(options), config.maxProcessingTime, () => {
@@ -128,7 +131,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
try { try {
if (isResend) { if (isResend) {
const tx = await web3Instance.eth.getTransaction(job.txHash) const tx = await web3Fallback.eth.getTransaction(job.txHash)
if (tx && tx.blockNumber !== null) { if (tx && tx.blockNumber !== null) {
logger.debug(`Transaction ${job.txHash} was successfully mined`) logger.debug(`Transaction ${job.txHash} was successfully mined`)
@@ -171,15 +174,18 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
`Tx Failed for event Tx ${job.transactionReference}.`, `Tx Failed for event Tx ${job.transactionReference}.`,
e.message e.message
) )
if (!e.message.toLowerCase().includes('transaction with the same hash was already imported')) {
if (isResend) { const message = e.message.toLowerCase()
resendJobs.push(job) if (isResend || message.includes('transaction with the same hash was already imported')) {
} else { resendJobs.push(job)
failedTx.push(job) } else {
} // if initial transaction sending has failed not due to the same hash error
// send it to the failed tx queue
// this will result in the sooner resend attempt than if using resendJobs
failedTx.push(job)
} }
if (e.message.toLowerCase().includes('insufficient funds')) { if (message.includes('insufficient funds')) {
insufficientFunds = true insufficientFunds = true
const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS) const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
minimumBalance = gasLimit.multipliedBy(gasPrice) minimumBalance = gasLimit.multipliedBy(gasPrice)
@@ -202,7 +208,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
await scheduleForRetry(failedTx, msg.properties.headers['x-retries']) await scheduleForRetry(failedTx, msg.properties.headers['x-retries'])
} }
if (resendJobs.length) { if (resendJobs.length) {
logger.info(`Sending ${resendJobs.length} Tx Delayed Resend Requests to Queue`) logger.info({ delay: config.resendInterval }, `Sending ${resendJobs.length} Tx Delayed Resend Requests to Queue`)
await scheduleTransactionResend(resendJobs) await scheduleTransactionResend(resendJobs)
} }
ackMsg(msg) ackMsg(msg)

View File

@@ -1,7 +1,12 @@
const fetch = require('node-fetch') const fetch = require('node-fetch')
const promiseRetry = require('promise-retry') const promiseRetry = require('promise-retry')
const { FALLBACK_RPC_URL_SWITCH_TIMEOUT } = require('../utils/constants')
// From EIP-1474 and Infura documentation
const JSONRPC_ERROR_CODES = [-32603, -32002, -32005]
const defaultOptions = { const defaultOptions = {
name: 'main',
requestTimeout: 0, requestTimeout: 0,
retry: { retry: {
retries: 0 retries: 0
@@ -27,23 +32,74 @@ function HttpListProvider(urls, options = {}) {
this.urls = urls this.urls = urls
this.options = { ...defaultOptions, ...options } this.options = { ...defaultOptions, ...options }
this.currentIndex = 0 this.currentIndex = 0
this.lastTimeUsedPrimary = 0
this.logger = {
debug: () => {},
info: () => {}
}
}
HttpListProvider.prototype.setLogger = function(logger) {
this.logger = logger.child({ module: `HttpListProvider:${this.options.name}` })
} }
HttpListProvider.prototype.send = async function send(payload, callback) { HttpListProvider.prototype.send = async function send(payload, callback) {
// if fallback URL is being used for too long, switch back to the primary URL
if (this.currentIndex > 0 && Date.now() - this.lastTimeUsedPrimary > FALLBACK_RPC_URL_SWITCH_TIMEOUT) {
this.logger.info(
{ oldURL: this.urls[this.currentIndex], newURL: this.urls[0] },
'Switching back to the primary JSON-RPC URL'
)
this.currentIndex = 0
}
// save the currentIndex to avoid race condition // save the currentIndex to avoid race condition
const { currentIndex } = this const { currentIndex } = this
try { try {
const [result, index] = await promiseRetry(retry => { const [result, index] = await promiseRetry(
return trySend(payload, this.urls, currentIndex, this.options).catch(retry) retry => this.trySend(payload, currentIndex).catch(retry),
}, this.options.retry) this.options.retry
this.currentIndex = index )
// if some of URLs failed to respond, current URL index is updated to the first URL that responded
if (currentIndex !== index) {
this.logger.info(
{ index, oldURL: this.urls[currentIndex], newURL: this.urls[index] },
'Switching to fallback JSON-RPC URL'
)
this.currentIndex = index
}
callback(null, result) callback(null, result)
} catch (e) { } catch (e) {
callback(e) callback(e)
} }
} }
HttpListProvider.prototype.trySend = async function(payload, initialIndex) {
const errors = []
for (let count = 0; count < this.urls.length; count++) {
const index = (initialIndex + count) % this.urls.length
// when request is being sent to the primary URL, the corresponding time marker is updated
if (index === 0) {
this.lastTimeUsedPrimary = Date.now()
}
const url = this.urls[index]
try {
const result = await send(url, payload, this.options)
return [result, index]
} catch (e) {
this.logger.debug({ index, url, method: payload.method, error: e.message }, `JSON-RPC has failed to respond`)
errors.push(e)
}
}
throw new HttpListProviderError('Request failed for all urls', errors)
}
function send(url, payload, options) { function send(url, payload, options) {
return fetch(url, { return fetch(url, {
headers: { headers: {
@@ -61,24 +117,15 @@ function send(url, payload, options) {
} }
}) })
.then(response => response.json()) .then(response => response.json())
} .then(response => {
if (
async function trySend(payload, urls, initialIndex, options) { response.error &&
const errors = [] (JSONRPC_ERROR_CODES.includes(response.error.code) || response.error.message.includes('ancient block'))
) {
let index = initialIndex throw new Error(response.error.message)
for (let count = 0; count < urls.length; count++) { }
const url = urls[index] return response
try { })
const result = await send(url, payload, options)
return [result, index]
} catch (e) {
errors.push(e)
}
index = (index + 1) % urls.length
}
throw new HttpListProviderError('Request failed for all urls', errors)
} }
module.exports = { module.exports = {

View File

@@ -13,7 +13,10 @@ function RedundantHttpListProvider(urls, options = {}) {
this.urls = urls this.urls = urls
this.options = { ...defaultOptions, ...options } this.options = { ...defaultOptions, ...options }
this.currentIndex = 0 }
RedundantHttpListProvider.prototype.setLogger = function(logger) {
this.logger = logger.child({ module: `RedundantHttpListProvider:${this.options.name}` })
} }
RedundantHttpListProvider.prototype.send = async function send(payload, callback) { RedundantHttpListProvider.prototype.send = async function send(payload, callback) {

View File

@@ -5,7 +5,6 @@ const connection = require('amqp-connection-manager').connect(process.env.ORACLE
const logger = require('./logger') const logger = require('./logger')
const { getRetrySequence } = require('../utils/utils') const { getRetrySequence } = require('../utils/utils')
const { const {
TRANSACTION_RESEND_TIMEOUT,
SENDER_QUEUE_MAX_PRIORITY, SENDER_QUEUE_MAX_PRIORITY,
SENDER_QUEUE_SEND_PRIORITY, SENDER_QUEUE_SEND_PRIORITY,
SENDER_QUEUE_CHECK_STATUS_PRIORITY SENDER_QUEUE_CHECK_STATUS_PRIORITY
@@ -48,7 +47,7 @@ function connectWatcherToQueue({ queueName, workerQueue, cb }) {
cb({ sendToQueue, sendToWorker, channel: channelWrapper }) cb({ sendToQueue, sendToWorker, channel: channelWrapper })
} }
function connectSenderToQueue({ queueName, oldQueueName, cb }) { function connectSenderToQueue({ queueName, oldQueueName, cb, resendInterval }) {
const deadLetterExchange = `${queueName}-retry` const deadLetterExchange = `${queueName}-retry`
async function resendMessagesToNewQueue(channel) { async function resendMessagesToNewQueue(channel) {
@@ -97,7 +96,8 @@ function connectSenderToQueue({ queueName, oldQueueName, cb }) {
channelWrapper, channelWrapper,
channel, channel,
queueName, queueName,
deadLetterExchange deadLetterExchange,
delay: resendInterval
}) })
} }
}) })
@@ -164,13 +164,13 @@ async function generateRetry({ data, msgRetries, channelWrapper, channel, queueN
}) })
} }
async function generateTransactionResend({ data, channelWrapper, channel, queueName, deadLetterExchange }) { async function generateTransactionResend({ data, channelWrapper, channel, queueName, deadLetterExchange, delay }) {
const retryQueue = `${queueName}-check-tx-status` const retryQueue = `${queueName}-check-tx-status-${delay}`
await channel.assertQueue(retryQueue, { await channel.assertQueue(retryQueue, {
durable: true, durable: true,
deadLetterExchange, deadLetterExchange,
messageTtl: TRANSACTION_RESEND_TIMEOUT, messageTtl: delay,
expires: TRANSACTION_RESEND_TIMEOUT * 10, expires: delay * 10,
maxPriority: SENDER_QUEUE_MAX_PRIORITY maxPriority: SENDER_QUEUE_MAX_PRIORITY
}) })
await channelWrapper.sendToQueue(retryQueue, data, { await channelWrapper.sendToQueue(retryQueue, data, {

View File

@@ -1,5 +1,13 @@
const pino = require('pino') const pino = require('pino')
const path = require('path') const path = require('path')
const {
web3Home,
web3Foreign,
web3HomeFallback,
web3ForeignFallback,
web3HomeRedundant,
web3ForeignRedundant
} = require('./web3')
const config = process.env.NODE_ENV !== 'test' ? require(path.join('../../config/', process.argv[2])) : {} const config = process.env.NODE_ENV !== 'test' ? require(path.join('../../config/', process.argv[2])) : {}
@@ -15,4 +23,11 @@ const logger = pino({
: {} : {}
}) })
web3Home.currentProvider.setLogger(logger)
web3Foreign.currentProvider.setLogger(logger)
web3HomeFallback.currentProvider.setLogger(logger)
web3ForeignFallback.currentProvider.setLogger(logger)
web3HomeRedundant.currentProvider.setLogger(logger)
web3ForeignRedundant.currentProvider.setLogger(logger)
module.exports = logger module.exports = logger

View File

@@ -41,15 +41,37 @@ const web3Home = new Web3(homeProvider)
const foreignProvider = new HttpListProvider(foreignUrls, foreignOptions) const foreignProvider = new HttpListProvider(foreignUrls, foreignOptions)
const web3Foreign = new Web3(foreignProvider) const web3Foreign = new Web3(foreignProvider)
const redundantHomeProvider = new RedundantHttpListProvider(homeUrls, homeOptions) // secondary fallback providers are intended to be used in places where
const web3HomeRedundant = new Web3(redundantHomeProvider) // it is more likely that RPC calls to the local non-archive nodes can fail
// e.g. for checking status of the old transaction via eth_getTransactionByHash
let web3HomeFallback = web3Home
let web3ForeignFallback = web3Foreign
const redundantForeignProvider = new RedundantHttpListProvider(foreignUrls, foreignOptions) // secondary redundant providers are intended to be used in places where
const web3ForeignRedundant = new Web3(redundantForeignProvider) // the result of a single RPC request can be lost
// e.g. for sending transactions eth_sendRawTransaction
let web3HomeRedundant = web3Home
let web3ForeignRedundant = web3Foreign
if (homeUrls.length > 1) {
const provider = new HttpListProvider(homeUrls, { ...homeOptions, name: 'fallback' })
web3HomeFallback = new Web3(provider)
const redundantProvider = new RedundantHttpListProvider(homeUrls, { ...homeOptions, name: 'redundant' })
web3HomeRedundant = new Web3(redundantProvider)
}
if (foreignUrls.length > 1) {
const provider = new HttpListProvider(foreignUrls, { ...foreignOptions, name: 'fallback' })
web3ForeignFallback = new Web3(provider)
const redundantProvider = new RedundantHttpListProvider(foreignUrls, { ...foreignOptions, name: 'redundant' })
web3ForeignRedundant = new Web3(redundantProvider)
}
module.exports = { module.exports = {
web3Home, web3Home,
web3Foreign, web3Foreign,
web3HomeRedundant, web3HomeRedundant,
web3ForeignRedundant web3ForeignRedundant,
web3HomeFallback,
web3ForeignFallback
} }

View File

@@ -23,7 +23,8 @@ module.exports = {
MIN: 1, MIN: 1,
MAX: 1000 MAX: 1000
}, },
TRANSACTION_RESEND_TIMEOUT: 20 * 60 * 1000, DEFAULT_TRANSACTION_RESEND_INTERVAL: 20 * 60 * 1000,
FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000,
SENDER_QUEUE_MAX_PRIORITY: 10, SENDER_QUEUE_MAX_PRIORITY: 10,
SENDER_QUEUE_SEND_PRIORITY: 5, SENDER_QUEUE_SEND_PRIORITY: 5,
SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1 SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1

View File

@@ -8,7 +8,7 @@ services:
ports: ports:
- "${UI_PORT}:${UI_PORT}" - "${UI_PORT}:${UI_PORT}"
env_file: ./.env env_file: ./.env
environment: environment:
- NODE_ENV=production - NODE_ENV=production
restart: unless-stopped restart: unless-stopped
entrypoint: yarn start entrypoint: yarn start

623
yarn.lock
View File

@@ -2,58 +2,65 @@
# yarn lockfile v1 # yarn lockfile v1
"@0x/assert@^2.1.6": "@0x/assert@^3.0.17":
version "2.1.6" version "3.0.17"
resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-2.1.6.tgz#61c5854b555bca1f1f0503754f2fd0169bee0ef1" resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-3.0.17.tgz#dc15d038ed085744cb375044218285368f0cbfa8"
integrity sha512-Gu8eBnFdEuIAH2GubWYOSVz/BIoRccKof68AziduYDHxh4nSPM6NUH52xtfUGk4nXljiOXU1XHZJhcjTObI+8Q== integrity sha512-NlII5893GQ9FZ+Lr+N2bSK3bQyxTBl8JUd7zeNltRPIsGco609bzG4Zat4zhlL3e/zhWY6zmzfaXK6spj4WsdQ==
dependencies: dependencies:
"@0x/json-schemas" "^4.0.2" "@0x/json-schemas" "^5.3.3"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^4.5.2" "@0x/utils" "^6.1.0"
"@types/node" "12.12.54"
lodash "^4.17.11" lodash "^4.17.11"
valid-url "^1.0.9" valid-url "^1.0.9"
"@0x/dev-utils@^2.3.3": "@0x/dev-utils@^4.0.2":
version "2.3.3" version "4.0.2"
resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-2.3.3.tgz#9b6df00fea357fa6da02b35ca93fc89d100e1992" resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-4.0.2.tgz#04c925c811d30ef7fb11e2b96ede4a0f9b74aab1"
integrity sha512-Pi664W/jj1U6WU+kHEPyKpflBnmKRsclB69RaL7wpnvOOFjAPhFV2/0FvIZ25w62rMzKEpfD1/1NcZ7NjAk7OQ== integrity sha512-d1T6rOlpgPFC9fyAayJLTWZFs4xXmVOzemZ2Pvym3JHj/Jpo2LuTnK5sOlRzBFGA0IG40bwy6WQofDVVeWk1bw==
dependencies: dependencies:
"@0x/subproviders" "^5.0.4" "@0x/subproviders" "^6.2.0"
"@0x/types" "^2.4.3" "@0x/types" "^3.3.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^4.5.2" "@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^6.0.13" "@0x/web3-wrapper" "^7.2.8"
"@types/node" "12.12.54"
"@types/web3-provider-engine" "^14.0.0" "@types/web3-provider-engine" "^14.0.0"
chai "^4.0.1" chai "^4.0.1"
ethereum-types "^2.1.6" chai-as-promised "^7.1.0"
chai-bignumber "^3.0.0"
dirty-chai "^2.0.1"
ethereum-types "^3.3.3"
lodash "^4.17.11" lodash "^4.17.11"
web3-provider-engine "14.0.6"
"@0x/json-schemas@^4.0.2": "@0x/json-schemas@^5.3.3":
version "4.0.2" version "5.3.3"
resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-4.0.2.tgz#6f7c1dcde04d3acc3e8ca2f24177b9705c10e772" resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-5.3.3.tgz#4b9de100385ca23b0cd58a454165df2e9758e453"
integrity sha512-JHOwESZeWKAzT5Z42ZNvOvQUQ5vuRIFQWS0FNjYwV8Cv4/dRlLHd7kwxxsvlm9NxgXnOW0ddEDBbVGxhVSYNIg== integrity sha512-u2DTXn4KRqOhlJUmFkIAqeUYIUKbYx2E+Hx6t7LhV+eIPrU5FSkk6Hv3UwYASARlMyqaWucBbGMBNCYyiVShGA==
dependencies: dependencies:
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@types/node" "*" "@types/node" "12.12.54"
jsonschema "^1.2.0" jsonschema "^1.2.0"
lodash.values "^4.3.0" lodash.values "^4.3.0"
"@0x/sol-compiler@^3.1.15": "@0x/sol-compiler@^4.3.1":
version "3.1.15" version "4.3.1"
resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-3.1.15.tgz#aaaad55008dddd69ad1e3226aa4a2832e0dd13b3" resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-4.3.1.tgz#dce7dea760c6032946783c2411dbb57a2c74dcca"
integrity sha512-IobhcQ/whFRL942/ykKc0fV6/YstHhvnQJ0noUZ9GabMDtaBlW6k5vAerSkXZU/YyOd8sD9nw7QSm295D6HoTA== integrity sha512-YXoNkGG17fZKu/SKs/E8eNedTbptORnbMHhyCwt5Ok3xvTDW83GCVkhi+T+ohuzpUBU12kcCbDbUqYxiakLKwQ==
dependencies: dependencies:
"@0x/assert" "^2.1.6" "@0x/assert" "^3.0.17"
"@0x/json-schemas" "^4.0.2" "@0x/json-schemas" "^5.3.3"
"@0x/sol-resolver" "^2.0.11" "@0x/sol-resolver" "^3.1.5"
"@0x/types" "^2.4.3" "@0x/types" "^3.3.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^4.5.2" "@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^6.0.13" "@0x/web3-wrapper" "^7.2.8"
"@types/node" "12.12.54"
"@types/yargs" "^11.0.0" "@types/yargs" "^11.0.0"
chalk "^2.3.0" chalk "^2.3.0"
chokidar "^3.0.2" chokidar "^3.0.2"
ethereum-types "^2.1.6" ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1" ethereumjs-util "^5.1.1"
lodash "^4.17.11" lodash "^4.17.11"
mkdirp "^0.5.1" mkdirp "^0.5.1"
@@ -65,59 +72,63 @@
web3-eth-abi "^1.0.0-beta.24" web3-eth-abi "^1.0.0-beta.24"
yargs "^10.0.3" yargs "^10.0.3"
"@0x/sol-coverage@^3.0.11": "@0x/sol-coverage@^4.0.10":
version "3.0.12" version "4.0.20"
resolved "https://registry.yarnpkg.com/@0x/sol-coverage/-/sol-coverage-3.0.12.tgz#4ca602495f7bcc4f045854850cd7f20fdf64e984" resolved "https://registry.yarnpkg.com/@0x/sol-coverage/-/sol-coverage-4.0.20.tgz#1633ac9331fbc2cb46beb17ada20d3f7ba525c7b"
integrity sha512-V4Iq8LU8HWNctVCt+gMHKcZ0moiawpna7bd3SWQuiQIig0iNpXBBCQl9SEIwpboTcv4Xhzo4cZuCGsWwKAQSpw== integrity sha512-VJn/cr1be9jHFGUlJPiZ7MxDGwh6DdmOYKK7+PSbzATW9NcpKuR0uatIcWNrrD3TJfeA2qAdd6bpcqf91/5MgA==
dependencies: dependencies:
"@0x/sol-tracing-utils" "^6.0.19" "@0x/sol-tracing-utils" "^7.1.10"
"@0x/subproviders" "^5.0.4" "@0x/subproviders" "^6.2.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@types/minimatch" "^3.0.3" "@types/minimatch" "^3.0.3"
ethereum-types "^2.1.6" "@types/node" "12.12.54"
ethereum-types "^3.3.3"
lodash "^4.17.11" lodash "^4.17.11"
minimatch "^3.0.4" minimatch "^3.0.4"
web3-provider-engine "14.0.6" web3-provider-engine "14.0.6"
"@0x/sol-resolver@^2.0.11": "@0x/sol-resolver@^3.1.5":
version "2.0.11" version "3.1.5"
resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-2.0.11.tgz#282d545a423baf0d478c498fd3adf607339d5504" resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-3.1.5.tgz#f3c94bd6d522f72091f059f60af255d7135e5e6c"
integrity sha512-TGvkuWoEMghPB4OZaPz49OJ/J0cw/I3yV7VLbE/OeN0K/J2P8uOkzhNgWLOVSi8EK3hcJ1JVc0iDSkCnzGj4xQ== integrity sha512-QO6kGPmwdtOX7LSTHVhYXWKFam2AqGGOg4CJH0AlOF7dTuwc7dDNlmeSymIkoIeOUoFu9nk+IC0s9spL0gAaZg==
dependencies: dependencies:
"@0x/types" "^2.4.3" "@0x/types" "^3.3.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@types/node" "12.12.54"
lodash "^4.17.11" lodash "^4.17.11"
"@0x/sol-trace@^2.0.19": "@0x/sol-trace@^3.0.10":
version "2.0.20" version "3.0.20"
resolved "https://registry.yarnpkg.com/@0x/sol-trace/-/sol-trace-2.0.20.tgz#4c14af3f5c30ab50882e9667926ca9533446711d" resolved "https://registry.yarnpkg.com/@0x/sol-trace/-/sol-trace-3.0.20.tgz#917fe73f0ace99112591776726db5880dff66ce5"
integrity sha512-lkw+8l+InqXKoyVFPCxdLA0oZnLLPvmpFm+lIJwEkPjrbwIyhvQbwSwyzPW/1XUWj0wIwWOpq6PSyofWZgt4KA== integrity sha512-wDHJrY5r/xf9YCbjjY4Xdl6Ie0HXzNofI2vVDGq4e9NM6aXcbN2dpsHk2jMhX+lWt0z21atpzFxaD7M9y5B4jA==
dependencies: dependencies:
"@0x/sol-tracing-utils" "^6.0.19" "@0x/sol-tracing-utils" "^7.1.10"
"@0x/subproviders" "^5.0.4" "@0x/subproviders" "^6.2.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@types/node" "12.12.54"
chalk "^2.3.0" chalk "^2.3.0"
ethereum-types "^2.1.6" ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1" ethereumjs-util "^5.1.1"
lodash "^4.17.11" lodash "^4.17.11"
loglevel "^1.6.1" loglevel "^1.6.1"
web3-provider-engine "14.0.6" web3-provider-engine "14.0.6"
"@0x/sol-tracing-utils@^6.0.19": "@0x/sol-tracing-utils@^7.1.10":
version "6.0.19" version "7.1.10"
resolved "https://registry.yarnpkg.com/@0x/sol-tracing-utils/-/sol-tracing-utils-6.0.19.tgz#3c119c7e5b6d2bd1c94663985b7641aec85ef625" resolved "https://registry.yarnpkg.com/@0x/sol-tracing-utils/-/sol-tracing-utils-7.1.10.tgz#2c0604235c924372580f92c5afd403b49fb108f1"
integrity sha512-5tQOEo+dUYWiclT7UDy5IRm/EPEwDzCqAY3J8l0ecIsssBk0r2YLKKf/ugWkdwASGeAxiepjYpC+mxcxOT6yDA== integrity sha512-i/YLYWplzsA5MTrvCsf5GawQsSVMQY2pKlmLqN+IQAWmthd3tj6jzh5AorJzgfc7DVYyC7/8WBJcL8yHRXyrxA==
dependencies: dependencies:
"@0x/dev-utils" "^2.3.3" "@0x/dev-utils" "^4.0.2"
"@0x/sol-compiler" "^3.1.15" "@0x/sol-compiler" "^4.3.1"
"@0x/sol-resolver" "^2.0.11" "@0x/sol-resolver" "^3.1.5"
"@0x/subproviders" "^5.0.4" "@0x/subproviders" "^6.2.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^4.5.2" "@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^6.0.13" "@0x/web3-wrapper" "^7.2.8"
"@types/node" "12.12.54"
"@types/solidity-parser-antlr" "^0.2.3" "@types/solidity-parser-antlr" "^0.2.3"
chalk "^2.3.0" chalk "^2.3.0"
ethereum-types "^2.1.6" ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1" ethereumjs-util "^5.1.1"
ethers "~4.0.4" ethers "~4.0.4"
glob "^7.1.2" glob "^7.1.2"
@@ -130,26 +141,27 @@
solc "^0.5.5" solc "^0.5.5"
solidity-parser-antlr "^0.4.2" solidity-parser-antlr "^0.4.2"
"@0x/subproviders@^5.0.3", "@0x/subproviders@^5.0.4": "@0x/subproviders@^6.1.1", "@0x/subproviders@^6.2.0":
version "5.0.4" version "6.2.0"
resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-5.0.4.tgz#e4b165634ef6a50c4bd41baacf0dbd2a9390c2f8" resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-6.2.0.tgz#75ad3cb8b835aa0c0e1d2a7e150f955ec6b32658"
integrity sha512-1LiGcOXkP5eUOl/0JRqkrqYtCvIL4NJj1GbbLIRq4TvkfqrRbF7zJM2SaayxPo3Z48zVsqk0ZE5+RrNAdK/Rrg== integrity sha512-ugux7Lzy4zmoVKlfHcj7/G6dWL3V7jMJmFj0WsmvrEIK/+9R1VyBTrP4IRm7O22ps319ZJvUYDlZc4fjKmQMEw==
dependencies: dependencies:
"@0x/assert" "^2.1.6" "@0x/assert" "^3.0.17"
"@0x/types" "^2.4.3" "@0x/types" "^3.3.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^4.5.2" "@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^6.0.13" "@0x/web3-wrapper" "^7.2.8"
"@ledgerhq/hw-app-eth" "^4.3.0" "@ledgerhq/hw-app-eth" "^4.3.0"
"@ledgerhq/hw-transport-u2f" "4.24.0" "@ledgerhq/hw-transport-u2f" "4.24.0"
"@types/hdkey" "^0.7.0" "@types/hdkey" "^0.7.0"
"@types/node" "12.12.54"
"@types/web3-provider-engine" "^14.0.0" "@types/web3-provider-engine" "^14.0.0"
bip39 "^2.5.0" bip39 "^2.5.0"
bn.js "^4.11.8" bn.js "^4.11.8"
ethereum-types "^2.1.6" ethereum-types "^3.3.3"
ethereumjs-tx "^1.3.5" ethereumjs-tx "^1.3.5"
ethereumjs-util "^5.1.1" ethereumjs-util "^5.1.1"
ganache-core "^2.6.0" ganache-core "^2.10.2"
hdkey "^0.7.1" hdkey "^0.7.1"
json-rpc-error "2.0.0" json-rpc-error "2.0.0"
lodash "^4.17.11" lodash "^4.17.11"
@@ -158,55 +170,57 @@
optionalDependencies: optionalDependencies:
"@ledgerhq/hw-transport-node-hid" "^4.3.0" "@ledgerhq/hw-transport-node-hid" "^4.3.0"
"@0x/types@^2.4.3": "@0x/types@^3.3.0":
version "2.4.3" version "3.3.0"
resolved "https://registry.yarnpkg.com/@0x/types/-/types-2.4.3.tgz#ea014889789e9013fdf48ce97b79f2c016e10fb3" resolved "https://registry.yarnpkg.com/@0x/types/-/types-3.3.0.tgz#98c5ee91b66c7cc1719cfece6c3e5477c90bf9c5"
integrity sha512-3z4ca9fb9pyTu9lJhTSll5EuEthkA3tLAayyZixCoCnwi4ok6PJ83PnMMsSxlRY2iXr7QGbrQr6nU64YWk2WjA== integrity sha512-vne28SbgFB/x5yrTqPsSn38IIv8SQWkQ5oZzzq3/d/13kxE64tTNE+UBOd3Jhax6q1w0birF4XW1iiJlz2yPuQ==
dependencies: dependencies:
"@types/node" "*" "@types/node" "12.12.54"
bignumber.js "~8.0.2" bignumber.js "~9.0.0"
ethereum-types "^2.1.6" ethereum-types "^3.3.3"
"@0x/typescript-typings@^4.3.0": "@0x/typescript-typings@^5.1.5":
version "4.3.0" version "5.1.5"
resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-4.3.0.tgz#4813a996ac5101841d1c22f4aa1738ab56168857" resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-5.1.5.tgz#dd0ad20ef42dad9d054886fd1da72839145b5863"
integrity sha512-6IH2JyKyl33+40tJ5rEhaMPTS2mVuRvoNmoXlCd/F0GPYSsDHMGObIXOkx+Qsw5SyCmqNs/3CTLeeCCqiSUdaw== integrity sha512-I55QfQNJPo8tG6j/PsTTgbeaIMbkGs5vdwVVfFkxSE8rXIEh4Qsra3JXke/7EpFZvhoUFngX4qdQyK2kI4V3sw==
dependencies: dependencies:
"@types/bn.js" "^4.11.0" "@types/bn.js" "^4.11.0"
"@types/node" "12.12.54"
"@types/react" "*" "@types/react" "*"
bignumber.js "~8.0.2" bignumber.js "~9.0.0"
ethereum-types "^2.1.6" ethereum-types "^3.3.3"
popper.js "1.14.3" popper.js "1.14.3"
"@0x/utils@^4.5.2": "@0x/utils@^6.1.0":
version "4.5.2" version "6.1.0"
resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-4.5.2.tgz#6cc89f2d0dda341e0fb4e76049a35abfb67a4ac5" resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-6.1.0.tgz#df5750927d8e2b82f6709d666e7fcfca0f33f66e"
integrity sha512-NWfNcvyiOhouk662AWxX0ZVe4ednBZJS9WZT/by3DBCY/WvN7WHMpEy9M5rBCxO+JJndLYeB5eBztDp7W+Ytkw== integrity sha512-Yrp4eonmFPgJcP1Bnl/xiCMEfiLVrYg3WxKJqMN6AmuW9VZeBhSJDqTMv5Et3qhFTBaY5YMWiISRYeJoVdMfDw==
dependencies: dependencies:
"@0x/types" "^2.4.3" "@0x/types" "^3.3.0"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@types/node" "*" "@types/node" "12.12.54"
abortcontroller-polyfill "^1.1.9" abortcontroller-polyfill "^1.1.9"
bignumber.js "~8.0.2" bignumber.js "~9.0.0"
chalk "^2.3.0" chalk "^2.3.0"
detect-node "2.0.3" detect-node "2.0.3"
ethereum-types "^2.1.6" ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1" ethereumjs-util "^5.1.1"
ethers "~4.0.4" ethers "~4.0.4"
isomorphic-fetch "2.2.1" isomorphic-fetch "2.2.1"
js-sha3 "^0.7.0" js-sha3 "^0.7.0"
lodash "^4.17.11" lodash "^4.17.11"
"@0x/web3-wrapper@^6.0.13": "@0x/web3-wrapper@^7.2.8":
version "6.0.13" version "7.2.8"
resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-6.0.13.tgz#2e666221bd44ceebe02762028214d4aa41ad7247" resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-7.2.8.tgz#7df4c52e358594338f8dbe76b1490a5c4c423633"
integrity sha512-LQjKBCCNdkJuhcJld+/sy+G0+sJu5qp9VDNNwJGLDxWIJpgoshhUpBPi7vUnZ79UY4SYuNcC4yM9yI61cs7ZiA== integrity sha512-T0HxSm3Kzs+fzCZI7Yk7MAjfp5vi+AMAHBbuXxJfzr3L6SFObSyHy6TPQ9NMNGrrbJi6UIOLPOX8W0P7hm0XtA==
dependencies: dependencies:
"@0x/assert" "^2.1.6" "@0x/assert" "^3.0.17"
"@0x/json-schemas" "^4.0.2" "@0x/json-schemas" "^5.3.3"
"@0x/typescript-typings" "^4.3.0" "@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^4.5.2" "@0x/utils" "^6.1.0"
ethereum-types "^2.1.6" "@types/node" "12.12.54"
ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1" ethereumjs-util "^5.1.1"
ethers "~4.0.4" ethers "~4.0.4"
lodash "^4.17.11" lodash "^4.17.11"
@@ -1391,7 +1405,7 @@
"@ethersproject/bytes" ">=5.0.0-beta.129" "@ethersproject/bytes" ">=5.0.0-beta.129"
js-sha3 "0.5.7" js-sha3 "0.5.7"
"@ethersproject/keccak256@^5.0.3": "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.3":
version "5.0.4" version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.4.tgz#36ca0a7d1ae2a272da5654cb886776d0c680ef3a" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.4.tgz#36ca0a7d1ae2a272da5654cb886776d0c680ef3a"
integrity sha512-GNpiOUm9PGUxFNqOxYKDQBM0u68bG9XC9iOulEQ8I0tOx/4qUpgVzvgXL6ugxr0RY554Gz/NQsVqknqPzUcxpQ== integrity sha512-GNpiOUm9PGUxFNqOxYKDQBM0u68bG9XC9iOulEQ8I0tOx/4qUpgVzvgXL6ugxr0RY554Gz/NQsVqknqPzUcxpQ==
@@ -2914,7 +2928,7 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/bn.js@^4.11.0", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.4", "@types/bn.js@^4.11.5": "@types/bn.js@^4.11.0", "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.5":
version "4.11.6" version "4.11.6"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
@@ -3042,12 +3056,17 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40"
integrity sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA== integrity sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==
"@types/node@12.12.54":
version "12.12.54"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.54.tgz#a4b58d8df3a4677b6c08bfbc94b7ad7a7a5f82d1"
integrity sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==
"@types/node@>= 8": "@types/node@>= 8":
version "13.11.0" version "13.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ== integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==
"@types/node@^10.0.3", "@types/node@^10.12.18", "@types/node@^10.3.2": "@types/node@^10.0.3", "@types/node@^10.3.2":
version "10.17.18" version "10.17.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.18.tgz#ae364d97382aacdebf583fa4e7132af2dfe56a0c" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.18.tgz#ae364d97382aacdebf583fa4e7132af2dfe56a0c"
integrity sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg== integrity sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg==
@@ -3058,14 +3077,9 @@
integrity sha512-KUyZdkGCnVPuXfsKmDUu2XLui65LZIJ2s0M57noy5e+ixUT2oK33ep7zlvgzI8LElcWqbf8AR+o/3GqAPac2zA== integrity sha512-KUyZdkGCnVPuXfsKmDUu2XLui65LZIJ2s0M57noy5e+ixUT2oK33ep7zlvgzI8LElcWqbf8AR+o/3GqAPac2zA==
"@types/node@^12.12.6": "@types/node@^12.12.6":
version "12.12.69" version "12.12.67"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.69.tgz#7cb6a3aa0d16664bf2dcd1450ccb8477464fbd79" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.67.tgz#4f86badb292e822e3b13730a1f9713ed2377f789"
integrity sha512-2F2VQRSFmzqgUEXw75L51MgnnZqc6bKWVSUPfrDPzp6mzGGibeVwyQcpvZvBr5RnsoMRHmC8EcBQiobSeqeJxg== integrity sha512-R48tgL2izApf+9rYNH+3RBMbRpPeW3N8f0I9HMhggeq4UXwBDqumJ14SDs4ctTMhG11pIOduZ4z3QWGOiMc9Vg==
"@types/node@^12.6.1":
version "12.12.34"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.34.tgz#0a5d6ae5d22612f0cf5f10320e1fc5d2a745dcb8"
integrity sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==
"@types/node@^8.0.0": "@types/node@^8.0.0":
version "8.10.59" version "8.10.59"
@@ -3361,24 +3375,37 @@
resolved "https://registry.yarnpkg.com/@use-it/interval/-/interval-0.1.3.tgz#5d1096b2295d7a5dda8e8022f3abb5f9d9ef27f8" resolved "https://registry.yarnpkg.com/@use-it/interval/-/interval-0.1.3.tgz#5d1096b2295d7a5dda8e8022f3abb5f9d9ef27f8"
integrity sha512-chshdtDZTFoWA9aszBz1Cc04Ca9NBD2JTi/GMjdJ+HGm4q7Vy1v71+2mm22r7Kfb2nYW+lTRsPcEHdB/VFVHsQ== integrity sha512-chshdtDZTFoWA9aszBz1Cc04Ca9NBD2JTi/GMjdJ+HGm4q7Vy1v71+2mm22r7Kfb2nYW+lTRsPcEHdB/VFVHsQ==
"@web3-js/scrypt-shim@^0.1.0": "@web3-react/abstract-connector@^6.0.7":
version "0.1.0" version "6.0.7"
resolved "https://registry.yarnpkg.com/@web3-js/scrypt-shim/-/scrypt-shim-0.1.0.tgz#0bf7529ab6788311d3e07586f7d89107c3bea2cc" resolved "https://registry.yarnpkg.com/@web3-react/abstract-connector/-/abstract-connector-6.0.7.tgz#401b3c045f1e0fab04256311be49d5144e9badc6"
integrity sha512-ZtZeWCc/s0nMcdx/+rZwY1EcuRdemOK9ag21ty9UsHkFxsNb/AaoucUz0iPuyGe0Ku+PFuRmWZG7Z7462p9xPw== integrity sha512-RhQasA4Ox8CxUC0OENc1AJJm8UTybu/oOCM61Zjg6y0iF7Z0sqv1Ai1VdhC33hrQpA8qSBgoXN9PaP8jKmtdqg==
dependencies: dependencies:
scryptsy "^2.1.0" "@web3-react/types" "^6.0.7"
semver "^6.3.0"
"@web3-js/websocket@^1.0.29": "@web3-react/core@^6.1.1":
version "1.0.30" version "6.1.1"
resolved "https://registry.yarnpkg.com/@web3-js/websocket/-/websocket-1.0.30.tgz#9ea15b7b582cf3bf3e8bc1f4d3d54c0731a87f87" resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-6.1.1.tgz#06c853890723f600b387b738a4b71ef41d5cccb7"
integrity sha512-fDwrD47MiDrzcJdSeTLF75aCcxVVt8B1N74rA+vh2XCAvFy4tEWJjtnUtj2QG7/zlQ6g9cQ88bZFBxwd9/FmtA== integrity sha512-HKXOgPNCmFvrVsed+aW/HlVhwzs8t3b+nzg3BoxgJQo/5yLiJXSumHRBdUrPxhBQiHkHRZiVPAvzf/8JMnm74Q==
dependencies: dependencies:
debug "^2.2.0" "@ethersproject/keccak256" "^5.0.0-beta.130"
es5-ext "^0.10.50" "@web3-react/abstract-connector" "^6.0.7"
nan "^2.14.0" "@web3-react/types" "^6.0.7"
typedarray-to-buffer "^3.1.5" tiny-invariant "^1.0.6"
yaeti "^0.0.6" tiny-warning "^1.0.3"
"@web3-react/injected-connector@^6.0.7":
version "6.0.7"
resolved "https://registry.yarnpkg.com/@web3-react/injected-connector/-/injected-connector-6.0.7.tgz#1e0be23f51fa07fe6547fe986768a46b74c3a426"
integrity sha512-Y7aJSz6pg+MWKtvdyuqyy6LWuH+4Tqtph1LWfiyVms9II9ar/9B/de4R8wh4wjg91wmHkU+D75yP09E/Soh2RA==
dependencies:
"@web3-react/abstract-connector" "^6.0.7"
"@web3-react/types" "^6.0.7"
tiny-warning "^1.0.3"
"@web3-react/types@^6.0.7":
version "6.0.7"
resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-6.0.7.tgz#34a6204224467eedc6123abaf55fbb6baeb2809f"
integrity sha512-ofGmfDhxmNT1/P/MgVa8IKSkCStFiyvXe+U5tyZurKdrtTDFU+wJ/LxClPDtFerWpczNFPUSrKcuhfPX1sI6+A==
"@webassemblyjs/ast@1.8.5": "@webassemblyjs/ast@1.8.5":
version "1.8.5" version "1.8.5"
@@ -4979,16 +5006,11 @@ bignumber.js@^9.0.0:
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075"
integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
bignumber.js@^9.0.1: bignumber.js@^9.0.1, bignumber.js@~9.0.0:
version "9.0.1" version "9.0.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==
bignumber.js@~8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.2.tgz#d8c4e1874359573b1ef03011a2d861214aeef137"
integrity sha512-EiuvFrnbv0jFixEQ9f58jo7X0qI2lNGIr/MxntmVzQc5JUweDSh8y8hbTCAomFtqwUPIOWcLXP0VEOSZTG7FFw==
binary-extensions@^1.0.0: binary-extensions@^1.0.0:
version "1.13.1" version "1.13.1"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
@@ -5705,13 +5727,18 @@ ccount@^1.0.3:
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386"
integrity sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w== integrity sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==
chai-as-promised@^7.1.1: chai-as-promised@^7.1.0, chai-as-promised@^7.1.1:
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0"
integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==
dependencies: dependencies:
check-error "^1.0.2" check-error "^1.0.2"
chai-bignumber@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chai-bignumber/-/chai-bignumber-3.0.0.tgz#e90cf1f468355bbb11a9acd051222586cd2648a9"
integrity sha512-SubOtaSI2AILWTWe2j0c6i2yFT/f9J6UBjeVGDuwDiPLkF/U5+/eTWUE3sbCZ1KgcPF6UJsDVYbIxaYA097MQA==
chai-bn@^0.1.1: chai-bn@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/chai-bn/-/chai-bn-0.1.1.tgz#a8904b2dc878e5c094881f327c0029579ff2062b" resolved "https://registry.yarnpkg.com/chai-bn/-/chai-bn-0.1.1.tgz#a8904b2dc878e5c094881f327c0029579ff2062b"
@@ -6525,17 +6552,6 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.0.7, cosmiconfig@^5.1.0, cosmiconfig@^5.2.0:
js-yaml "^3.13.1" js-yaml "^3.13.1"
parse-json "^4.0.0" parse-json "^4.0.0"
coveralls@^3.0.6:
version "3.1.0"
resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.1.0.tgz#13c754d5e7a2dd8b44fe5269e21ca394fb4d615b"
integrity sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==
dependencies:
js-yaml "^3.13.1"
lcov-parse "^1.0.0"
log-driver "^1.2.7"
minimist "^1.2.5"
request "^2.88.2"
create-ecdh@^4.0.0: create-ecdh@^4.0.0:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
@@ -7381,6 +7397,11 @@ dir-to-object@^2.0.0:
resolved "https://registry.yarnpkg.com/dir-to-object/-/dir-to-object-2.0.0.tgz#29723e9bd1c3e58e4f307bd04ff634c0370c8f8a" resolved "https://registry.yarnpkg.com/dir-to-object/-/dir-to-object-2.0.0.tgz#29723e9bd1c3e58e4f307bd04ff634c0370c8f8a"
integrity sha512-sXs0JKIhymON7T1UZuO2Ud6VTNAx/VTBXIl4+3mjb2RgfOpt+hectX0x04YqPOPdkeOAKoJuKqwqnXXURNPNEA== integrity sha512-sXs0JKIhymON7T1UZuO2Ud6VTNAx/VTBXIl4+3mjb2RgfOpt+hectX0x04YqPOPdkeOAKoJuKqwqnXXURNPNEA==
dirty-chai@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3"
integrity sha512-ys79pWKvDMowIDEPC6Fig8d5THiC0DJ2gmTeGzVAoEH18J8OzLud0Jh7I9IWg3NSk8x2UocznUuFmfHCXYZx9w==
dns-equal@^1.0.0: dns-equal@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -8444,7 +8465,7 @@ eth-lib@0.2.7:
elliptic "^6.4.0" elliptic "^6.4.0"
xhr-request-promise "^0.1.2" xhr-request-promise "^0.1.2"
eth-lib@0.2.8, eth-lib@^0.2.8: eth-lib@0.2.8:
version "0.2.8" version "0.2.8"
resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8"
integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==
@@ -8545,13 +8566,13 @@ ethereum-cryptography@^0.1.3:
secp256k1 "^4.0.1" secp256k1 "^4.0.1"
setimmediate "^1.0.5" setimmediate "^1.0.5"
ethereum-types@^2.1.6: ethereum-types@^3.3.3:
version "2.1.6" version "3.3.3"
resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-2.1.6.tgz#57d9d515fad86ab987c0f6962c4203be37da8579" resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-3.3.3.tgz#b9328185034ee52efa32176eb6fd9f3e741cb231"
integrity sha512-xaN5TxLvkdFCGjGfUQ5wV00GHzDHStozP1j+K/YdmUeQXVGiD15cogYPhBVWG3pQJM/aBjtYrpMrjywvKkNC4A== integrity sha512-FWW7ajHqgoqVHhPMX4sY2ycARpPFL8p/64rpToo8awNrJY7rBDnSC8esQYlAPeaiawf9fTM/xAgEm9VKY7J5kg==
dependencies: dependencies:
"@types/node" "*" "@types/node" "12.12.54"
bignumber.js "~8.0.2" bignumber.js "~9.0.0"
ethereumjs-abi@0.6.5: ethereumjs-abi@0.6.5:
version "0.6.5" version "0.6.5"
@@ -8826,16 +8847,16 @@ eventemitter3@1.1.1:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.1.1.tgz#47786bdaa087caf7b1b75e73abc5c7d540158cd0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.1.1.tgz#47786bdaa087caf7b1b75e73abc5c7d540158cd0"
integrity sha1-R3hr2qCHyvext15zq8XH1UAVjNA= integrity sha1-R3hr2qCHyvext15zq8XH1UAVjNA=
eventemitter3@3.1.2, eventemitter3@^3.0.0, eventemitter3@^3.1.0: eventemitter3@4.0.4:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
eventemitter3@4.0.4, eventemitter3@^4.0.0:
version "4.0.4" version "4.0.4"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==
eventemitter3@^3.0.0, eventemitter3@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
events@^3.0.0: events@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
@@ -9673,7 +9694,7 @@ ganache-cli@^6.6.0:
source-map-support "0.5.12" source-map-support "0.5.12"
yargs "13.2.4" yargs "13.2.4"
ganache-core@^2.6.0: ganache-core@^2.10.2:
version "2.13.1" version "2.13.1"
resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.13.1.tgz#bf60399a2dd084e1090db91cbbc7ed3885dc01e4" resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.13.1.tgz#bf60399a2dd084e1090db91cbbc7ed3885dc01e4"
integrity sha512-Ewg+kNcDqXtOohe7jCcP+ZUv9EMzOx2MoqOYYP3BCfxrDh3KjBXXaKK+Let7li0TghAs9lxmBgevZku35j5YzA== integrity sha512-Ewg+kNcDqXtOohe7jCcP+ZUv9EMzOx2MoqOYYP3BCfxrDh3KjBXXaKK+Let7li0TghAs9lxmBgevZku35j5YzA==
@@ -12523,11 +12544,6 @@ lcid@^2.0.0:
dependencies: dependencies:
invert-kv "^2.0.0" invert-kv "^2.0.0"
lcov-parse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0"
integrity sha1-6w1GtUER68VhrLTECO+TY73I9+A=
left-pad@^1.3.0: left-pad@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
@@ -12969,11 +12985,6 @@ lodash@4.17.20:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
log-driver@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
loglevel@^1.4.1: loglevel@^1.4.1:
version "1.6.1" version "1.6.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
@@ -13942,6 +13953,11 @@ node-fetch@^2.1.2, node-fetch@^2.3.0, node-fetch@^2.5.0:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-forge@0.7.5: node-forge@0.7.5:
version "0.7.5" version "0.7.5"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
@@ -17179,7 +17195,7 @@ request-promise@^4.2.2:
stealthy-require "^1.1.1" stealthy-require "^1.1.1"
tough-cookie "^2.3.3" tough-cookie "^2.3.3"
request@^2.67.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: request@^2.67.0, request@^2.85.0, request@^2.87.0, request@^2.88.0:
version "2.88.2" version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -17634,11 +17650,6 @@ scryptsy@^1.2.1:
dependencies: dependencies:
pbkdf2 "^3.0.3" pbkdf2 "^3.0.3"
scryptsy@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.1.0.tgz#8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790"
integrity sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==
scss-tokenizer@^0.2.3: scss-tokenizer@^0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@@ -19175,6 +19186,11 @@ tiny-invariant@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463"
integrity sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g== integrity sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==
tiny-invariant@^1.0.6:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.0: tiny-warning@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28"
@@ -20127,16 +20143,6 @@ web3-bzz@1.2.11:
swarm-js "^0.1.40" swarm-js "^0.1.40"
underscore "1.9.1" underscore "1.9.1"
web3-bzz@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.7.tgz#aa0f3d162f0777a5f35367dc5b70012dd1e129d0"
integrity sha512-iTIWBR+Z+Bn09WprtKm46LmyNOasg2lUn++AjXkBTB8UNxlUybxtza84yl2ETTZUs0zuFzdSSAEgbjhygG+9oA==
dependencies:
"@types/node" "^10.12.18"
got "9.6.0"
swarm-js "^0.1.40"
underscore "1.9.1"
web3-bzz@1.3.0: web3-bzz@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.3.0.tgz#83dfd77fa8a64bbb660462dffd0fee2a02ef1051" resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.3.0.tgz#83dfd77fa8a64bbb660462dffd0fee2a02ef1051"
@@ -20174,15 +20180,6 @@ web3-core-helpers@1.2.11:
web3-eth-iban "1.2.11" web3-eth-iban "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-core-helpers@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.7.tgz#522f859775ea0d15e7e40359c46d4efc5da92aee"
integrity sha512-bdU++9QATGeCetVrMp8pV97aQtVkN5oLBf/TWu/qumC6jK/YqrvLlBJLdwbz0QveU8zOSap6GCvJbqKvmmbV2A==
dependencies:
underscore "1.9.1"
web3-eth-iban "1.2.7"
web3-utils "1.2.7"
web3-core-helpers@1.3.0: web3-core-helpers@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.3.0.tgz#697cc3246a7eaaaac64ea506828d861c981c3f31" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.3.0.tgz#697cc3246a7eaaaac64ea506828d861c981c3f31"
@@ -20226,17 +20223,6 @@ web3-core-method@1.2.11:
web3-core-subscriptions "1.2.11" web3-core-subscriptions "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-core-method@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.7.tgz#73fd80d2bf0765ff6efc454db49ac83d1769a45e"
integrity sha512-e1TI0QUnByDMbQ8QHwnjxfjKw0LIgVRY4TYrlPijET9ebqUJU1HCayn/BHIMpV6LKyR1fQj9EldWyT64wZQXkg==
dependencies:
underscore "1.9.1"
web3-core-helpers "1.2.7"
web3-core-promievent "1.2.7"
web3-core-subscriptions "1.2.7"
web3-utils "1.2.7"
web3-core-method@1.3.0: web3-core-method@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.3.0.tgz#a71387af842aec7dbad5dbbd1130c14cc6c8beb3" resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.3.0.tgz#a71387af842aec7dbad5dbbd1130c14cc6c8beb3"
@@ -20272,13 +20258,6 @@ web3-core-promievent@1.2.11:
dependencies: dependencies:
eventemitter3 "4.0.4" eventemitter3 "4.0.4"
web3-core-promievent@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.7.tgz#fc7fa489f4cf76a040800f3dfd4b45c51bd3a39f"
integrity sha512-jNmsM/czCeMGQqKKwM9/HZVTJVIF96hdMVNN/V9TGvp+EEE7vDhB4pUocDnc/QF9Z/5QFBCVmvNWttlRgZmU0A==
dependencies:
eventemitter3 "3.1.2"
web3-core-promievent@1.3.0: web3-core-promievent@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.3.0.tgz#e0442dd0a8989b6bdce09293976cee6d9237a484" resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.3.0.tgz#e0442dd0a8989b6bdce09293976cee6d9237a484"
@@ -20319,17 +20298,6 @@ web3-core-requestmanager@1.2.11:
web3-providers-ipc "1.2.11" web3-providers-ipc "1.2.11"
web3-providers-ws "1.2.11" web3-providers-ws "1.2.11"
web3-core-requestmanager@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.7.tgz#9da0efce898ead7004d4ac50f748f5131cfe4d79"
integrity sha512-HJb/txjHixu1dxIebiZQKBoJCaNu4gsh7mq/uj6Z/w6tIHbybL90s/7ADyMED353yyJ2tDWtYJqeMVAR+KtdaA==
dependencies:
underscore "1.9.1"
web3-core-helpers "1.2.7"
web3-providers-http "1.2.7"
web3-providers-ipc "1.2.7"
web3-providers-ws "1.2.7"
web3-core-requestmanager@1.3.0: web3-core-requestmanager@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.3.0.tgz#c5b9a0304504c0e6cce6c90bc1a3bff82732aa1f" resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.3.0.tgz#c5b9a0304504c0e6cce6c90bc1a3bff82732aa1f"
@@ -20368,15 +20336,6 @@ web3-core-subscriptions@1.2.11:
underscore "1.9.1" underscore "1.9.1"
web3-core-helpers "1.2.11" web3-core-helpers "1.2.11"
web3-core-subscriptions@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.7.tgz#30c64aede03182832883b17c77e21cbb0933c86e"
integrity sha512-W/CzQYOUawdMDvkgA/fmLsnG5aMpbjrs78LZMbc0MFXLpH3ofqAgO2by4QZrrTShUUTeWS0ZuEkFFL/iFrSObw==
dependencies:
eventemitter3 "3.1.2"
underscore "1.9.1"
web3-core-helpers "1.2.7"
web3-core-subscriptions@1.3.0: web3-core-subscriptions@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.3.0.tgz#c2622ccd2b84f4687475398ff966b579dba0847e" resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.3.0.tgz#c2622ccd2b84f4687475398ff966b579dba0847e"
@@ -20419,19 +20378,6 @@ web3-core@1.2.11:
web3-core-requestmanager "1.2.11" web3-core-requestmanager "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-core@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.7.tgz#9248b04331e458c76263d758c51b0cc612953900"
integrity sha512-QA0MTae0gXcr3KHe3cQ4x56+Wh43ZKWfMwg1gfCc3NNxPRM1jJ8qudzyptCAUcxUGXWpDG8syLIn1APDz5J8BQ==
dependencies:
"@types/bn.js" "^4.11.4"
"@types/node" "^12.6.1"
bignumber.js "^9.0.0"
web3-core-helpers "1.2.7"
web3-core-method "1.2.7"
web3-core-requestmanager "1.2.7"
web3-utils "1.2.7"
web3-core@1.3.0: web3-core@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.3.0.tgz#b818903738461c1cca0163339e1d6d3fa51242cf" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.3.0.tgz#b818903738461c1cca0163339e1d6d3fa51242cf"
@@ -20474,15 +20420,6 @@ web3-eth-abi@1.2.11:
underscore "1.9.1" underscore "1.9.1"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-eth-abi@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.7.tgz#6f3471b578649fddd844a14d397a3dd430fc44a5"
integrity sha512-4FnlT1q+D0XBkxSMXlIb/eG337uQeMaUdtVQ4PZ3XzxqpcoDuMgXm4o+3NRxnWmr4AMm6QKjM+hcC7c0mBKcyg==
dependencies:
ethers "4.0.0-beta.3"
underscore "1.9.1"
web3-utils "1.2.7"
web3-eth-abi@1.3.0: web3-eth-abi@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.3.0.tgz#387b7ea9b38be69ad8856bc7b4e9a6a69bb4d22b" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.3.0.tgz#387b7ea9b38be69ad8856bc7b4e9a6a69bb4d22b"
@@ -20550,23 +20487,6 @@ web3-eth-accounts@1.2.11:
web3-core-method "1.2.11" web3-core-method "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-eth-accounts@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.7.tgz#087f55d04a01b815b93151aac2fc1677436b9c59"
integrity sha512-AE7QWi/iIQIjXwlAPtlMabm/OPFF0a1PhxT1EiTckpYNP8fYs6jW7lYxEtJPPJIKqfMjoi1xkEqTVR1YZQ88lg==
dependencies:
"@web3-js/scrypt-shim" "^0.1.0"
crypto-browserify "3.12.0"
eth-lib "^0.2.8"
ethereumjs-common "^1.3.2"
ethereumjs-tx "^2.1.1"
underscore "1.9.1"
uuid "3.3.2"
web3-core "1.2.7"
web3-core-helpers "1.2.7"
web3-core-method "1.2.7"
web3-utils "1.2.7"
web3-eth-accounts@1.3.0: web3-eth-accounts@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.3.0.tgz#010acf389b2bee6d5e1aecb2fe78bfa5c8f26c7a" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.3.0.tgz#010acf389b2bee6d5e1aecb2fe78bfa5c8f26c7a"
@@ -20627,21 +20547,6 @@ web3-eth-contract@1.2.11:
web3-eth-abi "1.2.11" web3-eth-abi "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-eth-contract@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.7.tgz#13d7f6003d6221f9a5fd61c2d3b5d039477c9674"
integrity sha512-uW23Y0iL7XroRNbf9fWZ1N6OYhEYTJX8gTuYASuRnpYrISN5QGiQML6pq/NCzqypR1bl5E0fuINZQSK/xefIVw==
dependencies:
"@types/bn.js" "^4.11.4"
underscore "1.9.1"
web3-core "1.2.7"
web3-core-helpers "1.2.7"
web3-core-method "1.2.7"
web3-core-promievent "1.2.7"
web3-core-subscriptions "1.2.7"
web3-eth-abi "1.2.7"
web3-utils "1.2.7"
web3-eth-contract@1.3.0: web3-eth-contract@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.3.0.tgz#c758340ac800788e29fa29edc8b0c0ac957b741c" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.3.0.tgz#c758340ac800788e29fa29edc8b0c0ac957b741c"
@@ -20672,20 +20577,6 @@ web3-eth-ens@1.2.11:
web3-eth-contract "1.2.11" web3-eth-contract "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-eth-ens@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.7.tgz#0bfa7d4b6c7753abbb31a2eb01a364b538f4c860"
integrity sha512-SPRnvUNWQ0CnnTDBteGIJkvFWEizJcAHlVsrFLICwcwFZu+appjX1UOaoGu2h3GXWtc/XZlu7B451Gi+Os2cTg==
dependencies:
eth-ens-namehash "2.0.8"
underscore "1.9.1"
web3-core "1.2.7"
web3-core-helpers "1.2.7"
web3-core-promievent "1.2.7"
web3-eth-abi "1.2.7"
web3-eth-contract "1.2.7"
web3-utils "1.2.7"
web3-eth-ens@1.3.0: web3-eth-ens@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.3.0.tgz#0887ba38473c104cf5fb8a715828b3b354fa02a2" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.3.0.tgz#0887ba38473c104cf5fb8a715828b3b354fa02a2"
@@ -20725,14 +20616,6 @@ web3-eth-iban@1.2.11:
bn.js "^4.11.9" bn.js "^4.11.9"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-eth-iban@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.7.tgz#832809c28586be3c667a713b77a2bcba11b7970f"
integrity sha512-2NrClz1PoQ3nSJBd+91ylCOVga9qbTxjRofq/oSCoHVAEvz3WZyttx9k5DC+0rWqwJF1h69ufFvdHAAlmN/4lg==
dependencies:
bn.js "4.11.8"
web3-utils "1.2.7"
web3-eth-iban@1.3.0: web3-eth-iban@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.3.0.tgz#15b782dfaf273ebc4e3f389f1367f4e88ddce4a5" resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.3.0.tgz#15b782dfaf273ebc4e3f389f1367f4e88ddce4a5"
@@ -20775,18 +20658,6 @@ web3-eth-personal@1.2.11:
web3-net "1.2.11" web3-net "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-eth-personal@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.7.tgz#322cc2b14c37737b21772a53e4185686a04bf9be"
integrity sha512-2OAa1Spz0uB29dwCM8+1y0So7E47A4gKznjBEwXIYEcUIsvwT5X7ofFhC2XxyRpqlIWZSQAxRSSJFyupRRXzyw==
dependencies:
"@types/node" "^12.6.1"
web3-core "1.2.7"
web3-core-helpers "1.2.7"
web3-core-method "1.2.7"
web3-net "1.2.7"
web3-utils "1.2.7"
web3-eth-personal@1.3.0: web3-eth-personal@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.3.0.tgz#d376e03dc737d961ff1f8d1aca866efad8477135" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.3.0.tgz#d376e03dc737d961ff1f8d1aca866efad8477135"
@@ -20854,25 +20725,6 @@ web3-eth@1.2.11:
web3-net "1.2.11" web3-net "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-eth@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.7.tgz#9427daefd3641200679c2946f77fc184dbfb5b4c"
integrity sha512-ljLd0oB4IjWkzFGVan4HkYhJXhSXgn9iaSaxdJixKGntZPgWMJfxeA+uLwTrlxrWzhvy4f+39WnT7wCh5e9TGg==
dependencies:
underscore "1.9.1"
web3-core "1.2.7"
web3-core-helpers "1.2.7"
web3-core-method "1.2.7"
web3-core-subscriptions "1.2.7"
web3-eth-abi "1.2.7"
web3-eth-accounts "1.2.7"
web3-eth-contract "1.2.7"
web3-eth-ens "1.2.7"
web3-eth-iban "1.2.7"
web3-eth-personal "1.2.7"
web3-net "1.2.7"
web3-utils "1.2.7"
web3-eth@1.3.0: web3-eth@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.3.0.tgz#898e5f5a8827f9bc6844e267a52eb388916a6771" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.3.0.tgz#898e5f5a8827f9bc6844e267a52eb388916a6771"
@@ -20919,15 +20771,6 @@ web3-net@1.2.11:
web3-core-method "1.2.11" web3-core-method "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3-net@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.7.tgz#c355621a8769c9c1a967c801e7db90c92a0e3808"
integrity sha512-j9qeZrS1FNyCeA0BfdLojkxOZQz3FKa1DJI+Dw9fEVhZS68vLOFANu2RB96gR9BoPHo5+k5D3NsKOoxt1gw3Gg==
dependencies:
web3-core "1.2.7"
web3-core-method "1.2.7"
web3-utils "1.2.7"
web3-net@1.3.0: web3-net@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.3.0.tgz#b69068cccffab58911c2f08ca4abfbefb0f948c6" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.3.0.tgz#b69068cccffab58911c2f08ca4abfbefb0f948c6"
@@ -21014,14 +20857,6 @@ web3-providers-http@1.2.11:
web3-core-helpers "1.2.11" web3-core-helpers "1.2.11"
xhr2-cookies "1.1.0" xhr2-cookies "1.1.0"
web3-providers-http@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.7.tgz#31eb15390c103169b3d7d31bdb1ccae9e3f1629d"
integrity sha512-vazGx5onuH/zogrwkUaLFJwFcJ6CckP65VFSHoiV+GTQdkOqgoDIha7StKkslvDz4XJ2FuY/zOZHbtuOYeltXQ==
dependencies:
web3-core-helpers "1.2.7"
xhr2-cookies "1.1.0"
web3-providers-http@1.3.0: web3-providers-http@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.3.0.tgz#88227f64c88b32abed4359383c2663616e0dc531" resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.3.0.tgz#88227f64c88b32abed4359383c2663616e0dc531"
@@ -21057,15 +20892,6 @@ web3-providers-ipc@1.2.11:
underscore "1.9.1" underscore "1.9.1"
web3-core-helpers "1.2.11" web3-core-helpers "1.2.11"
web3-providers-ipc@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.7.tgz#4e6716e8723d431df3d6bfa1acd2f7c04e7071ad"
integrity sha512-/zc0y724H2zbkV4UbGGMhsEiLfafjagIzfrsWZnyTZUlSB0OGRmmFm2EkLJAgtXrLiodaHHyXKM0vB8S24bxdA==
dependencies:
oboe "2.1.4"
underscore "1.9.1"
web3-core-helpers "1.2.7"
web3-providers-ipc@1.3.0: web3-providers-ipc@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.3.0.tgz#d7c2b203733b46f7b4e7b15633d891648cf9a293" resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.3.0.tgz#d7c2b203733b46f7b4e7b15633d891648cf9a293"
@@ -21103,16 +20929,6 @@ web3-providers-ws@1.2.11:
web3-core-helpers "1.2.11" web3-core-helpers "1.2.11"
websocket "^1.0.31" websocket "^1.0.31"
web3-providers-ws@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.7.tgz#95b1cc5dc25e9b9d6630d6754f9354313b62f532"
integrity sha512-b5XzqDpRkNVe6MFs5K6iqOEyjQikHtg3KuU2/ClCDV37hm0WN4xCRVMC0LwegulbDXZej3zT9+1CYzGaGFREzA==
dependencies:
"@web3-js/websocket" "^1.0.29"
eventemitter3 "^4.0.0"
underscore "1.9.1"
web3-core-helpers "1.2.7"
web3-providers-ws@1.3.0: web3-providers-ws@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.3.0.tgz#84adeff65acd4624d7f5bb43c5b2b22d8f0f63a4" resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.3.0.tgz#84adeff65acd4624d7f5bb43c5b2b22d8f0f63a4"
@@ -21153,16 +20969,6 @@ web3-shh@1.2.11:
web3-core-subscriptions "1.2.11" web3-core-subscriptions "1.2.11"
web3-net "1.2.11" web3-net "1.2.11"
web3-shh@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.7.tgz#5382c7bc2f39539eb2841c4576d23ade25720461"
integrity sha512-f6PAgcpG0ZAo98KqCmeHoDEx5qzm3d5plet18DkT4U6WIeYowKdec8vZaLPRR7c2XreXFJ2gQf45CB7oqR7U/w==
dependencies:
web3-core "1.2.7"
web3-core-method "1.2.7"
web3-core-subscriptions "1.2.7"
web3-net "1.2.7"
web3-shh@1.3.0: web3-shh@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.3.0.tgz#62d15297da8fb5f733dd1b98f9ade300590f4d49" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.3.0.tgz#62d15297da8fb5f733dd1b98f9ade300590f4d49"
@@ -21227,20 +21033,6 @@ web3-utils@1.2.6:
underscore "1.9.1" underscore "1.9.1"
utf8 "3.0.0" utf8 "3.0.0"
web3-utils@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.7.tgz#b68e232917e4376f81cf38ef79878e5903d18e93"
integrity sha512-FBh/CPJND+eiPeUF9KVbTyTZtXNWxPWtByBaWS6e2x4ACazPX711EeNaZaChIOGSLGe6se2n7kg6wnawe/MjuQ==
dependencies:
bn.js "4.11.8"
eth-lib "0.2.7"
ethereum-bloom-filters "^1.0.6"
ethjs-unit "0.1.6"
number-to-bn "1.7.0"
randombytes "^2.1.0"
underscore "1.9.1"
utf8 "3.0.0"
web3-utils@1.3.0: web3-utils@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.3.0.tgz#5bac16e5e0ec9fe7bdcfadb621655e8aa3cf14e1" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.3.0.tgz#5bac16e5e0ec9fe7bdcfadb621655e8aa3cf14e1"
@@ -21294,19 +21086,6 @@ web3@1.2.11:
web3-shh "1.2.11" web3-shh "1.2.11"
web3-utils "1.2.11" web3-utils "1.2.11"
web3@1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.7.tgz#fcb83571036c1c6f475bc984785982a444e8d78e"
integrity sha512-jAAJHMfUlTps+jH2li1ckDFEpPrEEriU/ubegSTGRl3KRdNhEqT93+3kd7FHJTn3NgjcyURo2+f7Da1YcZL8Mw==
dependencies:
web3-bzz "1.2.7"
web3-core "1.2.7"
web3-eth "1.2.7"
web3-eth-personal "1.2.7"
web3-net "1.2.7"
web3-shh "1.2.7"
web3-utils "1.2.7"
web3@^1.3.0: web3@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/web3/-/web3-1.3.0.tgz#8fe4cd6e2a21c91904f343ba75717ee4c76bb349" resolved "https://registry.yarnpkg.com/web3/-/web3-1.3.0.tgz#8fe4cd6e2a21c91904f343ba75717ee4c76bb349"