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:
runs-on: ubuntu-latest
needs:
- build-e2e-images
- build-molecule-runner
strategy:
fail-fast: false
@@ -197,11 +198,11 @@ jobs:
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- run: deployment-e2e/molecule.sh ${{ matrix.task }}
ultimate:
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags') || contains(github.event.head_commit.message, 'ultimate')
runs-on: ubuntu-latest
needs:
- initialize
- build-e2e-images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags')
strategy:
fail-fast: false
matrix:
@@ -209,12 +210,16 @@ jobs:
include:
- task: erc-to-erc
ui-e2e-grep: 'ERC TO ERC'
ui-config: 'e2e-commons/components-envs/ui-erc20.env'
- task: erc-to-native
ui-e2e-grep: 'ERC TO NATIVE'
ui-config: 'e2e-commons/components-envs/ui-erc20-native.env'
- task: native-to-erc
ui-e2e-grep: 'NATIVE TO ERC'
ui-config: 'e2e-commons/components-envs/ui.env'
- task: amb-stake-erc-to-erc
ui-e2e-grep: 'AMB-STAKE-ERC-TO-ERC'
ui-config: 'e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env'
steps:
- uses: actions/checkout@v2
with:
@@ -240,7 +245,20 @@ jobs:
- name: Deploy contracts
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks
- name: Pull e2e oracle image
run: docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
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
run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
- name: Reset docker socket permissions

3
.gitignore vendored
View File

@@ -49,3 +49,6 @@ monitor/responses/*
monitor/cache/*
!monitor/cache/.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_ALWAYS_RELAY_SIGNATURES | If set to `true`, the oracle will always relay signatures even if it was not the last who finilized the signatures collecting process. The default is `false`. | `true` / `false`
ORACLE_RPC_REQUEST_TIMEOUT | Timeout in milliseconds for a single RPC request. Default value is `ORACLE_*_RPC_POLLING_INTERVAL * 2`. | integer
ORACLE_HOME_TX_RESEND_INTERVAL | Interval in milliseconds for automatic resending of stuck transactions for Home sender service. Defaults to 20 minutes. | integer
ORACLE_FOREIGN_TX_RESEND_INTERVAL | Interval in milliseconds for automatic resending of stuck transactions for Foreign sender service. Defaults to 20 minutes. | integer
## 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_BLOCK_LIST | File with a list of addresses, separated by newlines. If set, determines the set of accounts whose requests should be marked as unclaimed. Has a lower priority than the `MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST`. | string
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE | If set, defines the list of home validator addresses for which balance should be checked. | `string`
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE | If set, defines the list of foreign validator addresses for which balance should be checked. | `string`
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 alm/package.json ./alm/
COPY yarn.lock .
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile
COPY ./commons ./commons
COPY ./alm ./alm

View File

@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ethersproject/bignumber": ">=5.0.0-beta.130",
"@react-hook/window-size": "^3.0.6",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
@@ -15,6 +16,8 @@
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.0",
"@use-it/interval": "^0.1.3",
"@web3-react/core": "^6.1.1",
"@web3-react/injected-connector": "^6.0.7",
"customize-cra": "^1.0.0",
"date-fns": "^2.14.0",
"dotenv": "^8.2.0",
@@ -27,8 +30,9 @@
"react-scripts": "3.0.1",
"styled-components": "^5.1.1",
"typescript": "^3.5.2",
"web3": "1.2.7",
"web3-eth-contract": "1.2.7"
"web3": "1.2.11",
"web3-eth-contract": "1.2.11",
"web3-utils": "1.2.11"
},
"scripts": {
"start": "yarn createSnapshots && ./load-env.sh react-app-rewired start",
@@ -54,6 +58,7 @@
]
},
"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')
require('dotenv').config()
const Web3 = require('web3')
const fetch = require('node-fetch')
const { URL } = require('url')
const fs = require('fs')
@@ -10,7 +12,9 @@ const {
COMMON_HOME_RPC_URL,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_RPC_URL,
COMMON_FOREIGN_BRIDGE_ADDRESS
COMMON_FOREIGN_BRIDGE_ADDRESS,
ALM_FOREIGN_EXPLORER_API,
ALM_HOME_EXPLORER_API
} = process.env
const generateSnapshot = async (side, url, bridgeAddress) => {
@@ -19,6 +23,31 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const snapshot = {}
const web3 = new Web3(new Web3.providers.HttpProvider(url))
const api = side === 'home' ? ALM_HOME_EXPLORER_API : ALM_FOREIGN_EXPLORER_API
const getPastEventsWithFallback = (contract, eventName, options) =>
contract.getPastEvents(eventName, options).catch(async e => {
if (e.message.includes('exceed maximum block range')) {
const abi = contract.options.jsonInterface.find(abi => abi.type === 'event' && abi.name === eventName)
const url = new URL(api)
url.searchParams.append('module', 'logs')
url.searchParams.append('action', 'getLogs')
url.searchParams.append('address', contract.options.address)
url.searchParams.append('fromBlock', options.fromBlock)
url.searchParams.append('toBlock', options.toBlock || 'latest')
url.searchParams.append('topic0', web3.eth.abi.encodeEventSignature(abi))
const logs = await fetch(url).then(res => res.json())
return logs.result.map(log => ({
transactionHash: log.transactionHash,
blockNumber: parseInt(log.blockNumber.slice(2), 16),
returnValues: web3.eth.abi.decodeLog(abi.inputs, log.data, log.topics.slice(1))
}))
}
throw e
})
const currentBlockNumber = await web3.eth.getBlockNumber()
snapshot.snapshotBlockNumber = currentBlockNumber
@@ -29,10 +58,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress)
// Save RequiredBlockConfirmationChanged events
let requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('RequiredBlockConfirmationChanged', {
fromBlock: 0,
toBlock: currentBlockNumber
})
let requiredBlockConfirmationChangedEvents = await getPastEventsWithFallback(
bridgeContract,
'RequiredBlockConfirmationChanged',
{
fromBlock: 0,
toBlock: currentBlockNumber
}
)
// In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
// manually generate an event for this. Example Sokol - Kovan bridge
@@ -59,10 +92,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress)
// Save RequiredSignaturesChanged events
const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', {
fromBlock: 0,
toBlock: currentBlockNumber
})
const RequiredSignaturesChangedEvents = await getPastEventsWithFallback(
validatorContract,
'RequiredSignaturesChanged',
{
fromBlock: 0,
toBlock: currentBlockNumber
}
)
snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({
blockNumber: e.blockNumber,
returnValues: {
@@ -71,7 +108,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
}))
// Save ValidatorAdded events
const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', {
const validatorAddedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorAdded', {
fromBlock: 0,
toBlock: currentBlockNumber
})
@@ -85,7 +122,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
}))
// Save ValidatorRemoved events
const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', {
const validatorRemovedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorRemoved', {
fromBlock: 0,
toBlock: currentBlockNumber
})

View File

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

View File

@@ -39,21 +39,38 @@ export interface ConfirmationsContainerParams {
message: MessageObject
receipt: Maybe<TransactionReceipt>
fromHome: boolean
timestamp: number
homeStartBlock: Maybe<number>
foreignStartBlock: Maybe<number>
}
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => {
export const ConfirmationsContainer = ({
message,
receipt,
fromHome,
homeStartBlock,
foreignStartBlock
}: ConfirmationsContainerParams) => {
const {
home: { name: homeName },
foreign: { name: foreignName }
} = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ 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,
receipt,
fromHome,
timestamp,
homeStartBlock,
foreignStartBlock,
requiredSignatures,
validatorList,
blockConfirmations
@@ -102,7 +119,17 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
validatorList={validatorList}
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>
</div>
)

View File

@@ -1,24 +1,47 @@
import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
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 styled from 'styled-components'
import { ExecutionData } from '../hooks/useMessageConfirmations'
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton'
const StyledExecutionConfirmation = styled.div`
margin-top: 30px;
`
export interface ExecutionConfirmationParams {
messageData: string
executionData: ExecutionData
setExecutionData: Function
signatureCollected: boolean | string[]
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 txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
@@ -48,26 +71,47 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
<table>
<Thead>
<tr>
<th>Executed by</th>
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</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>
</Thead>
<tbody>
<tr>
<td>{formattedValidator ? formattedValidator : <SimpleLoading />}</td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
<AgeTd className="text-center">
{executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank">
{formatTimestamp(executionData.timestamp)}
</ExplorerTxLink>
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
''
<td>
{requiredManualExecution ? (
'Manual user action is required to complete the operation'
) : formattedValidator ? (
formattedValidator
) : (
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>
</tbody>
</table>

View File

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

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

View File

@@ -1,6 +1,6 @@
import React from 'react'
export const CloseIcon = () => (
export const CloseIcon = ({ color }: { color?: string }) => (
<svg
aria-hidden="true"
focusable="false"
@@ -10,12 +10,9 @@ export const CloseIcon = () => (
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 352 512"
fill="#1890ff"
fill={color || '#1890ff'}
height="1em"
>
<path
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"
/>
<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" />
</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'
export const InfoIcon = () => (
export const InfoIcon = ({ color }: { color?: string }) => (
<svg
className="col-1 is-left"
viewBox="64 64 896 896"
@@ -8,7 +8,7 @@ export const InfoIcon = () => (
data-icon="info-circle"
width="1em"
height="1em"
fill="#1890ff"
fill={color || '#1890ff'}
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" />

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

View File

@@ -1,4 +1,6 @@
// %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 } = {
SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:',
SUCCESS_ONE_MESSAGE: 'was initiated %t',
@@ -24,7 +26,7 @@ export const CONFIRMATIONS_STATUS_LABEL_HOME: { [key: string]: string } = {
SUCCESS_MESSAGE_FAILED: 'Success',
EXECUTION_FAILED: 'Execution failed',
EXECUTION_PENDING: 'Execution pending',
EXECUTION_WAITING: 'Execution waiting',
EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION ? 'Manual execution waiting' : 'Execution waiting',
FAILED: 'Confirmation Failed',
PENDING: 'Confirmation Pending',
WAITING_VALIDATORS: 'Confirmation Waiting',
@@ -55,11 +57,12 @@ export const CONFIRMATIONS_STATUS_DESCRIPTION_HOME: { [key: string]: string } =
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.',
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:
'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.',
EXECUTION_WAITING:
'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. The\n transaction with collected signatures was\nsent but is not yet added to a block.',
EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
? '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:
'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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations'
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 getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock<any>
const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock<any>
@@ -24,10 +24,17 @@ const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
const validatorList = [validator1, validator2, validator3]
const bridgeContract = {} as Contract
const confirmationContractMethod = () => {}
const signature =
'0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4'
const bridgeContract = {
methods: {
signature: () => ({
call: () => signature
})
}
} as Contract
const requiredSignatures = 2
const waitingBlocksResolved = true
const isCancelled = () => false
let subscriptions: Array<number> = []
const timestamp = 1594045859
const getFailedTransactions = (): Promise<APITransaction[]> => Promise.resolve([])
@@ -42,7 +49,7 @@ const unsubscribe = () => {
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
getValidatorSuccessTransaction.mockClear()
getSuccessExecutionTransaction.mockClear()
getValidatorConfirmation.mockClear()
getValidatorFailedTransaction.mockClear()
getValidatorPendingTransaction.mockClear()
@@ -54,7 +61,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '',
@@ -83,12 +90,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -102,9 +109,10 @@ describe('getConfirmationsForTx', () => {
expect(subscriptions.length).toEqual(1)
expect(setResult).toBeCalledTimes(2)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(2)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1)
@@ -114,14 +122,16 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
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([
{ validator: validator1, 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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
@@ -134,7 +144,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '',
@@ -163,12 +173,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -179,9 +189,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(1)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -198,7 +208,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 ? '0x123' : '',
@@ -227,12 +237,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -244,11 +254,12 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(2)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1)
@@ -258,14 +269,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
])
)
expect(setResult.mock.calls[1][0]).toEqual(
expect(res3).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
@@ -283,7 +304,7 @@ describe('getConfirmationsForTx', () => {
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
@@ -315,12 +336,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -332,11 +353,12 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(2)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1)
@@ -346,7 +368,27 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
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([
{ validator: validator1, 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 }
])
)
expect(setResult.mock.calls[1][0]).toEqual(
expect(res4).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
@@ -372,7 +414,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '',
@@ -407,12 +449,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -423,9 +465,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -437,14 +479,32 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, 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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
@@ -461,7 +521,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '',
@@ -493,12 +553,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -510,9 +570,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -524,14 +584,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, 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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
@@ -555,7 +625,7 @@ describe('getConfirmationsForTx', () => {
status:
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction
getSuccessExecutionTransaction
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
@@ -604,12 +674,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -620,9 +690,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -634,14 +704,32 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, 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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
@@ -654,12 +742,12 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -670,12 +758,13 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(setResult).toBeCalledTimes(4)
expect(setResult).toBeCalledTimes(7)
expect(getValidatorConfirmation).toBeCalledTimes(2)
expect(getValidatorSuccessTransaction).toBeCalledTimes(2)
expect(setSignatureCollected).toBeCalledTimes(2)
expect(getSuccessExecutionTransaction).toBeCalledTimes(2)
expect(setSignatureCollected).toBeCalledTimes(3)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
expect(setSignatureCollected.mock.calls[1][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[2][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(2)
expect(setFailedConfirmations).toBeCalledTimes(2)
@@ -687,14 +776,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
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([
{ 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: 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([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, 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 { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
const eventName = 'RelayedMessage'
const timestamp = 1594045859
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const txHash = '0xdab36c9210e7e45fb82af10ffe4960461e41661dce0c9cd36b2843adaa1df156'
@@ -20,12 +19,11 @@ const web3 = ({
toChecksumAddress: (a: string) => a
}
} as unknown) as Web3
const waitingBlocksResolved = true
const message = {
id: '0x123',
data: '0x123456789'
}
const interval = 10000
const isCancelled = () => false
let subscriptions: Array<number> = []
const event = {
@@ -50,7 +48,7 @@ beforeEach(() => {
describe('getFinalizationEvent', () => {
test('should get finalization event and not try to get failed or pending transactions', async () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return [event]
}
} as unknown) as Contract
@@ -61,22 +59,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn()
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
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 () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
}
} as unknown) as Contract
@@ -110,22 +109,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn()
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
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 () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
},
options: {
@@ -159,22 +159,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([])
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
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 () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
},
options: {
@@ -208,22 +209,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([{ hash: txHash }])
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
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 () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
},
options: {
@@ -264,22 +266,23 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([])
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
unsubscribe()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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: ./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
become: true
tasks:
- name: stop the service
shell: service poabridge stop
- name: ReTag current oracle image
shell: docker tag $(docker images --format '{{ '{{' }}.Repository{{ '}}' }}:{{ '{{' }}.Tag{{ '}}' }}' | grep -m 1 tokenbridge-e2e-oracle) oracle:ultimate-testing
- name: Connect parity to oracle networks
shell: "docker network create {{ item }} && docker network connect {{ item }} parity1 && docker network connect {{ item }} parity2"
with_items:
- oracle_net_db_bridge_request
- oracle_net_db_bridge_collected
- oracle_net_db_bridge_affirmation
- oracle_net_db_bridge_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
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:
host_vars:
oracle-erc-to-erc-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-erc-to-erc-host:

View File

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

View File

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

View File

@@ -5,13 +5,13 @@ ORACLE_ALLOW_HTTP_FOR_RPC: yes
ORACLE_LOG_LEVEL: debug
## 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_WITHOUT_EVENTS: false
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## 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_WITHOUT_EVENTS: false
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
@@ -52,3 +52,7 @@ MONITOR_FOREIGN_START_BLOCK: 0
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_TX_NUMBER_THRESHOLD: 100
# disable building and pulling of docker images from the Docker Hub
skip_pull: true
skip_build: true

View File

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

View File

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

View File

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

View File

@@ -3,3 +3,4 @@
shell: docker-compose build
args:
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
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_PORT=3003
UI_STYLES=stake

View File

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

View File

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

View File

@@ -4,12 +4,14 @@ networks:
external: true
services:
parity1:
container_name: parity1
build: ../parity
ports:
- "8541:8545"
networks:
- ultimate
parity2:
container_name: parity2
build:
context: ../parity
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 run -d -p 3000:3000 ui yarn start
docker-compose run -d -p 3001:3000 ui-erc20 yarn start
docker-compose run -d -p 3002:3000 ui-erc20-native yarn start
docker-compose run -d -p 3003:3000 ui-amb-stake-erc20-erc20 yarn start
docker-compose run -d -p 3001:3001 ui-erc20 yarn start
docker-compose run -d -p 3002:3002 ui-erc20-native yarn start
docker-compose run -d -p 3003:3003 ui-amb-stake-erc20-erc20 yarn start
fi
if [ "$1" == "alm" ]; then

View File

@@ -25,3 +25,9 @@ MONITOR_CACHE_EVENTS=true
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_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()
const logger = require('./logger')('alerts')
const eventsInfo = require('./utils/events')
const { processedMsgNotDelivered, eventWithoutReference } = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./utils/web3')
const { web3Home, web3Foreign } = require('./utils/web3')
async function main() {
async function main(eventsInfo) {
const {
homeBlockNumber,
foreignBlockNumber,
homeToForeignRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests,
bridgeMode
} = await eventsInfo()
} = eventsInfo
let xSignatures
let xAffirmations
@@ -24,8 +25,6 @@ async function main() {
xAffirmations = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
}
logger.debug('building misbehavior blocks')
const homeBlockNumber = await getHomeBlockNumber()
const foreignBlockNumber = await getForeignBlockNumber()
const baseRange = [false, false, false, false, false]
const xSignaturesMisbehavior = buildRangesObject(

View File

@@ -12,10 +12,6 @@ const { web3Home } = require('./utils/web3')
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')
async function checkWorker() {
@@ -45,27 +41,6 @@ async function checkWorker() {
const vBalances = await validators(bridgeMode)
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.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/validators.json`, vBalances)

View File

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

View File

@@ -1,8 +1,11 @@
require('dotenv').config()
const logger = require('./logger')('checkWorker3')
const stuckTransfers = require('./stuckTransfers')
const detectMediators = require('./detectMediators')
const detectFailures = require('./detectFailures')
const { writeFile, createDir } = require('./utils/file')
const { web3Home } = require('./utils/web3')
const { saveCache } = require('./utils/web3Cache')
const { MONITOR_BRIDGE_NAME, COMMON_HOME_BRIDGE_ADDRESS } = process.env
const { getBridgeMode, HOME_NATIVE_TO_ERC_ABI, BRIDGE_MODES } = require('../commons')
@@ -20,6 +23,23 @@ async function checkWorker3() {
transfers.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/stuckTransfers.json`, transfers)
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) {
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()
const eventsInfo = require('./utils/events')
const {
processedMsgNotDelivered,
deliveredMsgNotProcessed,
@@ -15,14 +14,14 @@ const {
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER
} = process.env
async function main() {
async function main(eventsInfo) {
const {
homeToForeignRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests,
bridgeMode
} = await eventsInfo()
} = eventsInfo
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
return {

View File

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

View File

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

20
monitor/metricsWorker.js Normal file
View File

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

View File

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

View File

@@ -0,0 +1,137 @@
require('dotenv').config()
const logger = require('./logger')('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,
isExternalErc20,
bridgeMode,
homeBlockNumber,
foreignBlockNumber,
homeDelayedBlockNumber,
foreignDelayedBlockNumber
}
if (MONITOR_CACHE_EVENTS === 'true') {
logger.debug('saving obtained events into cache file')
writeFile(cacheFilePath, result, false)
writeFile(cacheFilePath, result, { useCwd: false })
}
return result
}

View File

@@ -1,23 +1,30 @@
const fs = require('fs')
const path = require('path')
async function readFile(filePath) {
function readFile(filePath, parseJson = true) {
try {
const content = await fs.readFileSync(filePath)
const content = fs.readFileSync(filePath)
if (!parseJson) return content
const json = JSON.parse(content)
const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked
return Object.assign({}, json, { timeDiff })
} catch (e) {
console.error(e)
console.error('readFlle', e)
return {
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
fs.writeFileSync(fullPath, JSON.stringify(object, null, 4))
fs.writeFileSync(fullPath, stringify ? JSON.stringify(object, null, 4) : object)
}
function createDir(dirPath) {

View File

@@ -1,32 +1,33 @@
const { parseAMBMessage } = require('../../commons')
const { normalizeAMBMessageEvent } = require('../../commons')
const { readAccessListFile } = require('./file')
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 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) {
const keys = new Set()
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) {
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))
}
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
* @param {Object} event
@@ -70,23 +71,24 @@ const manuallyProcessedAMBHomeToForeignRequests = () => {
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
const allowanceList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST)
return e => {
const { sender, executor, decodedDataType } = normalizeAMBMessage(e)
const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
return (!allowanceList.includes(sender) && !allowanceList.includes(executor)) || decodedDataType.manualLane
}
} else if (MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
const blockList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_BLOCK_LIST)
return e => {
const { sender, executor, decodedDataType } = normalizeAMBMessage(e)
const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
return blockList.includes(sender) || blockList.includes(executor) || decodedDataType.manualLane
}
} else {
return e => normalizeAMBMessage(e).decodedDataType.manualLane
return e => normalizeAMBMessageEvent(e).decodedDataType.manualLane
}
}
module.exports = {
deliveredMsgNotProcessed,
processedMsgNotDelivered,
addExecutionStatus,
normalizeEventInformation,
eventWithoutReference,
unclaimedHomeToForeignRequests,

View File

@@ -1,24 +1,93 @@
const fetch = require('node-fetch')
const logger = require('../logger')('web3Cache')
const { readCacheFile, writeCacheFile } = require('./file')
const { web3Home } = require('./web3')
const { web3Home, web3Foreign } = require('./web3')
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
const homeTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/txSenders.json`
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) {
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()
isDirty = true
}
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) {
if (MONITOR_CACHE_EVENTS !== 'true') {
return commonGetPastEvents(contract, options)
@@ -61,14 +130,14 @@ async function getPastEvents(contract, options) {
// requested: A...B
// cached: C...D
logger.debug(`Fetching events for blocks ${fromBlock}...${toBlock}`)
result = await commonGetPastEvents(contract, options)
result = await getPastEventsWithAPIFallback(contract, options)
} else if (fromBlock < cachedFromBlock && toBlock <= cachedToBlock) {
// requested: A...B
// cached: C...D
logger.debug(`Cache hit for blocks ${cachedFromBlock}...${toBlock}`)
logger.debug(`Fetching events for blocks ${fromBlock}...${cachedFromBlock - 1}`)
result = [
...(await commonGetPastEvents(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...(await getPastEventsWithAPIFallback(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...cachedEvents.filter(e => e.blockNumber <= 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 ${cachedToBlock + 1}...${toBlock}`)
result = [
...(await commonGetPastEvents(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...(await getPastEventsWithAPIFallback(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...cachedEvents,
...(await commonGetPastEvents(contract, { ...options, fromBlock: cachedToBlock + 1 }))
...(await getPastEventsWithAPIFallback(contract, { ...options, fromBlock: cachedToBlock + 1 }))
]
} else if (cachedFromBlock <= fromBlock && toBlock <= cachedToBlock) {
// requested: A.B
@@ -94,7 +163,7 @@ async function getPastEvents(contract, options) {
logger.debug(`Fetching events for blocks ${cachedToBlock + 1}...${toBlock}`)
result = [
...cachedEvents.filter(e => e.blockNumber >= fromBlock),
...(await commonGetPastEvents(contract, { ...options, fromBlock: cachedToBlock + 1 }))
...(await getPastEventsWithAPIFallback(contract, { ...options, fromBlock: cachedToBlock + 1 }))
]
} else {
throw new Error(
@@ -123,11 +192,16 @@ function saveCache() {
if (isDirty) {
logger.debug('Saving cache on disk')
writeCacheFile(homeTxSendersCacheFile, cachedHomeTxSenders)
writeCacheFile(homeIsContractCacheFile, cachedHomeIsContract)
writeCacheFile(foreignIsContractCacheFile, cachedForeignIsContract)
}
}
module.exports = {
getHomeTxSender,
getForeignTxSender,
isHomeContract,
isForeignContract,
getPastEvents,
saveCache
}

View File

@@ -16,10 +16,13 @@ const {
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL,
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE,
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
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 homeGasPriceSupplierOpts = {
speedType: COMMON_HOME_GAS_PRICE_SPEED_TYPE,
@@ -33,12 +36,6 @@ const foreignGasPriceSupplierOpts = {
logger
}
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
async function main(bridgeMode) {
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode)
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
@@ -109,53 +106,61 @@ async function main(bridgeMode) {
}
let validatorsMatch = true
logger.debug('calling asyncForEach foreignValidators foreignVBalances')
await asyncForEach(foreignValidators, async v => {
const balance = await web3Foreign.eth.getBalance(v)
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
const leftTx = Web3Utils.toBN(balance)
.div(foreignTxCost)
.toString(10)
foreignVBalances[v] = {
balance: Web3Utils.fromWei(balance),
leftTx: Number(leftTx),
gasPrice: Number(foreignGasPriceGwei)
const foreignValidatorsWithBalanceCheck =
typeof MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE === 'string'
? MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE.split(' ')
: foreignValidators
logger.debug('getting foreignValidators balances')
await Promise.all(
foreignValidators.map(async v => {
foreignVBalances[v] = {}
if (foreignValidatorsWithBalanceCheck.includes(v)) {
const balance = await web3Foreign.eth.getBalance(v)
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)) {
validatorsMatch = false
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)
if (!homeValidators.includes(v)) {
validatorsMatch = false
foreignVBalances[v].onlyOnForeign = true
}
} else {
homeVBalances[v] = {
balance: Web3Utils.fromWei(balance)
}
}
})
)
if (!foreignValidators.includes(v)) {
validatorsMatch = false
homeVBalances[v].onlyOnHome = true
}
})
const homeValidatorsWithBalanceCheck =
typeof MONITOR_HOME_VALIDATORS_BALANCE_ENABLE === 'string'
? 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()')
const reqSigHome = await homeBridgeValidators.methods.requiredSignatures().call()
@@ -164,20 +169,22 @@ async function main(bridgeMode) {
logger.debug('Done')
return {
home: {
validators: {
...homeVBalances
},
validators: homeVBalances,
requiredSignatures: Number(reqSigHome)
},
foreign: {
validators: {
...foreignVBalances
},
validators: foreignVBalances,
requiredSignatures: Number(reqSigForeign)
},
requiredSignaturesMatch: reqSigHome === reqSigForeign,
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)
})
await delay(5000)
await delay(10000)
const newSignatures = await homeBridge.getPastEvents('SignedForUserRequest', {
fromBlock: 0,

View File

@@ -14,12 +14,14 @@ COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000
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_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
ORACLE_FOREIGN_TX_RESEND_INTERVAL=1200000
ORACLE_QUEUE_URL=amqp://rabbit
ORACLE_REDIS_URL=redis://redis

View File

@@ -1,6 +1,9 @@
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 = {
...baseConfig.bridgeConfig,
@@ -9,5 +12,7 @@ module.exports = {
id: 'foreign',
name: 'sender-foreign',
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 { 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 = {
...baseConfig.bridgeConfig,
@@ -9,5 +12,7 @@ module.exports = {
id: 'home',
name: 'sender-home',
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 web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : config.web3
const { web3Fallback } = config
const nonceKey = `${config.id}:nonce`
let chainId = 0
@@ -45,6 +47,7 @@ async function initialize() {
connectSenderToQueue({
queueName: config.queue,
oldQueueName: config.oldQueue,
resendInterval: config.resendInterval,
cb: options => {
if (config.maxProcessingTime) {
return watchdog(() => main(options), config.maxProcessingTime, () => {
@@ -128,7 +131,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
try {
if (isResend) {
const tx = await web3Instance.eth.getTransaction(job.txHash)
const tx = await web3Fallback.eth.getTransaction(job.txHash)
if (tx && tx.blockNumber !== null) {
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}.`,
e.message
)
if (!e.message.toLowerCase().includes('transaction with the same hash was already imported')) {
if (isResend) {
resendJobs.push(job)
} else {
failedTx.push(job)
}
const message = e.message.toLowerCase()
if (isResend || message.includes('transaction with the same hash was already imported')) {
resendJobs.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
const currentBalance = await web3Instance.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
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'])
}
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)
}
ackMsg(msg)

View File

@@ -1,7 +1,12 @@
const fetch = require('node-fetch')
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 = {
name: 'main',
requestTimeout: 0,
retry: {
retries: 0
@@ -27,23 +32,74 @@ function HttpListProvider(urls, options = {}) {
this.urls = urls
this.options = { ...defaultOptions, ...options }
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) {
// 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
const { currentIndex } = this
try {
const [result, index] = await promiseRetry(retry => {
return trySend(payload, this.urls, currentIndex, this.options).catch(retry)
}, this.options.retry)
this.currentIndex = index
const [result, index] = await promiseRetry(
retry => this.trySend(payload, currentIndex).catch(retry),
this.options.retry
)
// 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)
} catch (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) {
return fetch(url, {
headers: {
@@ -61,24 +117,15 @@ function send(url, payload, options) {
}
})
.then(response => response.json())
}
async function trySend(payload, urls, initialIndex, options) {
const errors = []
let index = initialIndex
for (let count = 0; count < urls.length; count++) {
const url = urls[index]
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)
.then(response => {
if (
response.error &&
(JSONRPC_ERROR_CODES.includes(response.error.code) || response.error.message.includes('ancient block'))
) {
throw new Error(response.error.message)
}
return response
})
}
module.exports = {

View File

@@ -13,7 +13,10 @@ function RedundantHttpListProvider(urls, options = {}) {
this.urls = urls
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) {

View File

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

View File

@@ -1,5 +1,13 @@
const pino = require('pino')
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])) : {}
@@ -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

View File

@@ -41,15 +41,37 @@ const web3Home = new Web3(homeProvider)
const foreignProvider = new HttpListProvider(foreignUrls, foreignOptions)
const web3Foreign = new Web3(foreignProvider)
const redundantHomeProvider = new RedundantHttpListProvider(homeUrls, homeOptions)
const web3HomeRedundant = new Web3(redundantHomeProvider)
// secondary fallback providers are intended to be used in places where
// 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)
const web3ForeignRedundant = new Web3(redundantForeignProvider)
// secondary redundant providers are intended to be used in places where
// 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 = {
web3Home,
web3Foreign,
web3HomeRedundant,
web3ForeignRedundant
web3ForeignRedundant,
web3HomeFallback,
web3ForeignFallback
}

View File

@@ -23,7 +23,8 @@ module.exports = {
MIN: 1,
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_SEND_PRIORITY: 5,
SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1

View File

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

623
yarn.lock
View File

@@ -2,58 +2,65 @@
# yarn lockfile v1
"@0x/assert@^2.1.6":
version "2.1.6"
resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-2.1.6.tgz#61c5854b555bca1f1f0503754f2fd0169bee0ef1"
integrity sha512-Gu8eBnFdEuIAH2GubWYOSVz/BIoRccKof68AziduYDHxh4nSPM6NUH52xtfUGk4nXljiOXU1XHZJhcjTObI+8Q==
"@0x/assert@^3.0.17":
version "3.0.17"
resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-3.0.17.tgz#dc15d038ed085744cb375044218285368f0cbfa8"
integrity sha512-NlII5893GQ9FZ+Lr+N2bSK3bQyxTBl8JUd7zeNltRPIsGco609bzG4Zat4zhlL3e/zhWY6zmzfaXK6spj4WsdQ==
dependencies:
"@0x/json-schemas" "^4.0.2"
"@0x/typescript-typings" "^4.3.0"
"@0x/utils" "^4.5.2"
"@0x/json-schemas" "^5.3.3"
"@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^6.1.0"
"@types/node" "12.12.54"
lodash "^4.17.11"
valid-url "^1.0.9"
"@0x/dev-utils@^2.3.3":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-2.3.3.tgz#9b6df00fea357fa6da02b35ca93fc89d100e1992"
integrity sha512-Pi664W/jj1U6WU+kHEPyKpflBnmKRsclB69RaL7wpnvOOFjAPhFV2/0FvIZ25w62rMzKEpfD1/1NcZ7NjAk7OQ==
"@0x/dev-utils@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-4.0.2.tgz#04c925c811d30ef7fb11e2b96ede4a0f9b74aab1"
integrity sha512-d1T6rOlpgPFC9fyAayJLTWZFs4xXmVOzemZ2Pvym3JHj/Jpo2LuTnK5sOlRzBFGA0IG40bwy6WQofDVVeWk1bw==
dependencies:
"@0x/subproviders" "^5.0.4"
"@0x/types" "^2.4.3"
"@0x/typescript-typings" "^4.3.0"
"@0x/utils" "^4.5.2"
"@0x/web3-wrapper" "^6.0.13"
"@0x/subproviders" "^6.2.0"
"@0x/types" "^3.3.0"
"@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^7.2.8"
"@types/node" "12.12.54"
"@types/web3-provider-engine" "^14.0.0"
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"
web3-provider-engine "14.0.6"
"@0x/json-schemas@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-4.0.2.tgz#6f7c1dcde04d3acc3e8ca2f24177b9705c10e772"
integrity sha512-JHOwESZeWKAzT5Z42ZNvOvQUQ5vuRIFQWS0FNjYwV8Cv4/dRlLHd7kwxxsvlm9NxgXnOW0ddEDBbVGxhVSYNIg==
"@0x/json-schemas@^5.3.3":
version "5.3.3"
resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-5.3.3.tgz#4b9de100385ca23b0cd58a454165df2e9758e453"
integrity sha512-u2DTXn4KRqOhlJUmFkIAqeUYIUKbYx2E+Hx6t7LhV+eIPrU5FSkk6Hv3UwYASARlMyqaWucBbGMBNCYyiVShGA==
dependencies:
"@0x/typescript-typings" "^4.3.0"
"@types/node" "*"
"@0x/typescript-typings" "^5.1.5"
"@types/node" "12.12.54"
jsonschema "^1.2.0"
lodash.values "^4.3.0"
"@0x/sol-compiler@^3.1.15":
version "3.1.15"
resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-3.1.15.tgz#aaaad55008dddd69ad1e3226aa4a2832e0dd13b3"
integrity sha512-IobhcQ/whFRL942/ykKc0fV6/YstHhvnQJ0noUZ9GabMDtaBlW6k5vAerSkXZU/YyOd8sD9nw7QSm295D6HoTA==
"@0x/sol-compiler@^4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-4.3.1.tgz#dce7dea760c6032946783c2411dbb57a2c74dcca"
integrity sha512-YXoNkGG17fZKu/SKs/E8eNedTbptORnbMHhyCwt5Ok3xvTDW83GCVkhi+T+ohuzpUBU12kcCbDbUqYxiakLKwQ==
dependencies:
"@0x/assert" "^2.1.6"
"@0x/json-schemas" "^4.0.2"
"@0x/sol-resolver" "^2.0.11"
"@0x/types" "^2.4.3"
"@0x/typescript-typings" "^4.3.0"
"@0x/utils" "^4.5.2"
"@0x/web3-wrapper" "^6.0.13"
"@0x/assert" "^3.0.17"
"@0x/json-schemas" "^5.3.3"
"@0x/sol-resolver" "^3.1.5"
"@0x/types" "^3.3.0"
"@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^7.2.8"
"@types/node" "12.12.54"
"@types/yargs" "^11.0.0"
chalk "^2.3.0"
chokidar "^3.0.2"
ethereum-types "^2.1.6"
ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1"
lodash "^4.17.11"
mkdirp "^0.5.1"
@@ -65,59 +72,63 @@
web3-eth-abi "^1.0.0-beta.24"
yargs "^10.0.3"
"@0x/sol-coverage@^3.0.11":
version "3.0.12"
resolved "https://registry.yarnpkg.com/@0x/sol-coverage/-/sol-coverage-3.0.12.tgz#4ca602495f7bcc4f045854850cd7f20fdf64e984"
integrity sha512-V4Iq8LU8HWNctVCt+gMHKcZ0moiawpna7bd3SWQuiQIig0iNpXBBCQl9SEIwpboTcv4Xhzo4cZuCGsWwKAQSpw==
"@0x/sol-coverage@^4.0.10":
version "4.0.20"
resolved "https://registry.yarnpkg.com/@0x/sol-coverage/-/sol-coverage-4.0.20.tgz#1633ac9331fbc2cb46beb17ada20d3f7ba525c7b"
integrity sha512-VJn/cr1be9jHFGUlJPiZ7MxDGwh6DdmOYKK7+PSbzATW9NcpKuR0uatIcWNrrD3TJfeA2qAdd6bpcqf91/5MgA==
dependencies:
"@0x/sol-tracing-utils" "^6.0.19"
"@0x/subproviders" "^5.0.4"
"@0x/typescript-typings" "^4.3.0"
"@0x/sol-tracing-utils" "^7.1.10"
"@0x/subproviders" "^6.2.0"
"@0x/typescript-typings" "^5.1.5"
"@types/minimatch" "^3.0.3"
ethereum-types "^2.1.6"
"@types/node" "12.12.54"
ethereum-types "^3.3.3"
lodash "^4.17.11"
minimatch "^3.0.4"
web3-provider-engine "14.0.6"
"@0x/sol-resolver@^2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-2.0.11.tgz#282d545a423baf0d478c498fd3adf607339d5504"
integrity sha512-TGvkuWoEMghPB4OZaPz49OJ/J0cw/I3yV7VLbE/OeN0K/J2P8uOkzhNgWLOVSi8EK3hcJ1JVc0iDSkCnzGj4xQ==
"@0x/sol-resolver@^3.1.5":
version "3.1.5"
resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-3.1.5.tgz#f3c94bd6d522f72091f059f60af255d7135e5e6c"
integrity sha512-QO6kGPmwdtOX7LSTHVhYXWKFam2AqGGOg4CJH0AlOF7dTuwc7dDNlmeSymIkoIeOUoFu9nk+IC0s9spL0gAaZg==
dependencies:
"@0x/types" "^2.4.3"
"@0x/typescript-typings" "^4.3.0"
"@0x/types" "^3.3.0"
"@0x/typescript-typings" "^5.1.5"
"@types/node" "12.12.54"
lodash "^4.17.11"
"@0x/sol-trace@^2.0.19":
version "2.0.20"
resolved "https://registry.yarnpkg.com/@0x/sol-trace/-/sol-trace-2.0.20.tgz#4c14af3f5c30ab50882e9667926ca9533446711d"
integrity sha512-lkw+8l+InqXKoyVFPCxdLA0oZnLLPvmpFm+lIJwEkPjrbwIyhvQbwSwyzPW/1XUWj0wIwWOpq6PSyofWZgt4KA==
"@0x/sol-trace@^3.0.10":
version "3.0.20"
resolved "https://registry.yarnpkg.com/@0x/sol-trace/-/sol-trace-3.0.20.tgz#917fe73f0ace99112591776726db5880dff66ce5"
integrity sha512-wDHJrY5r/xf9YCbjjY4Xdl6Ie0HXzNofI2vVDGq4e9NM6aXcbN2dpsHk2jMhX+lWt0z21atpzFxaD7M9y5B4jA==
dependencies:
"@0x/sol-tracing-utils" "^6.0.19"
"@0x/subproviders" "^5.0.4"
"@0x/typescript-typings" "^4.3.0"
"@0x/sol-tracing-utils" "^7.1.10"
"@0x/subproviders" "^6.2.0"
"@0x/typescript-typings" "^5.1.5"
"@types/node" "12.12.54"
chalk "^2.3.0"
ethereum-types "^2.1.6"
ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1"
lodash "^4.17.11"
loglevel "^1.6.1"
web3-provider-engine "14.0.6"
"@0x/sol-tracing-utils@^6.0.19":
version "6.0.19"
resolved "https://registry.yarnpkg.com/@0x/sol-tracing-utils/-/sol-tracing-utils-6.0.19.tgz#3c119c7e5b6d2bd1c94663985b7641aec85ef625"
integrity sha512-5tQOEo+dUYWiclT7UDy5IRm/EPEwDzCqAY3J8l0ecIsssBk0r2YLKKf/ugWkdwASGeAxiepjYpC+mxcxOT6yDA==
"@0x/sol-tracing-utils@^7.1.10":
version "7.1.10"
resolved "https://registry.yarnpkg.com/@0x/sol-tracing-utils/-/sol-tracing-utils-7.1.10.tgz#2c0604235c924372580f92c5afd403b49fb108f1"
integrity sha512-i/YLYWplzsA5MTrvCsf5GawQsSVMQY2pKlmLqN+IQAWmthd3tj6jzh5AorJzgfc7DVYyC7/8WBJcL8yHRXyrxA==
dependencies:
"@0x/dev-utils" "^2.3.3"
"@0x/sol-compiler" "^3.1.15"
"@0x/sol-resolver" "^2.0.11"
"@0x/subproviders" "^5.0.4"
"@0x/typescript-typings" "^4.3.0"
"@0x/utils" "^4.5.2"
"@0x/web3-wrapper" "^6.0.13"
"@0x/dev-utils" "^4.0.2"
"@0x/sol-compiler" "^4.3.1"
"@0x/sol-resolver" "^3.1.5"
"@0x/subproviders" "^6.2.0"
"@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^7.2.8"
"@types/node" "12.12.54"
"@types/solidity-parser-antlr" "^0.2.3"
chalk "^2.3.0"
ethereum-types "^2.1.6"
ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1"
ethers "~4.0.4"
glob "^7.1.2"
@@ -130,26 +141,27 @@
solc "^0.5.5"
solidity-parser-antlr "^0.4.2"
"@0x/subproviders@^5.0.3", "@0x/subproviders@^5.0.4":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-5.0.4.tgz#e4b165634ef6a50c4bd41baacf0dbd2a9390c2f8"
integrity sha512-1LiGcOXkP5eUOl/0JRqkrqYtCvIL4NJj1GbbLIRq4TvkfqrRbF7zJM2SaayxPo3Z48zVsqk0ZE5+RrNAdK/Rrg==
"@0x/subproviders@^6.1.1", "@0x/subproviders@^6.2.0":
version "6.2.0"
resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-6.2.0.tgz#75ad3cb8b835aa0c0e1d2a7e150f955ec6b32658"
integrity sha512-ugux7Lzy4zmoVKlfHcj7/G6dWL3V7jMJmFj0WsmvrEIK/+9R1VyBTrP4IRm7O22ps319ZJvUYDlZc4fjKmQMEw==
dependencies:
"@0x/assert" "^2.1.6"
"@0x/types" "^2.4.3"
"@0x/typescript-typings" "^4.3.0"
"@0x/utils" "^4.5.2"
"@0x/web3-wrapper" "^6.0.13"
"@0x/assert" "^3.0.17"
"@0x/types" "^3.3.0"
"@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^6.1.0"
"@0x/web3-wrapper" "^7.2.8"
"@ledgerhq/hw-app-eth" "^4.3.0"
"@ledgerhq/hw-transport-u2f" "4.24.0"
"@types/hdkey" "^0.7.0"
"@types/node" "12.12.54"
"@types/web3-provider-engine" "^14.0.0"
bip39 "^2.5.0"
bn.js "^4.11.8"
ethereum-types "^2.1.6"
ethereum-types "^3.3.3"
ethereumjs-tx "^1.3.5"
ethereumjs-util "^5.1.1"
ganache-core "^2.6.0"
ganache-core "^2.10.2"
hdkey "^0.7.1"
json-rpc-error "2.0.0"
lodash "^4.17.11"
@@ -158,55 +170,57 @@
optionalDependencies:
"@ledgerhq/hw-transport-node-hid" "^4.3.0"
"@0x/types@^2.4.3":
version "2.4.3"
resolved "https://registry.yarnpkg.com/@0x/types/-/types-2.4.3.tgz#ea014889789e9013fdf48ce97b79f2c016e10fb3"
integrity sha512-3z4ca9fb9pyTu9lJhTSll5EuEthkA3tLAayyZixCoCnwi4ok6PJ83PnMMsSxlRY2iXr7QGbrQr6nU64YWk2WjA==
"@0x/types@^3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@0x/types/-/types-3.3.0.tgz#98c5ee91b66c7cc1719cfece6c3e5477c90bf9c5"
integrity sha512-vne28SbgFB/x5yrTqPsSn38IIv8SQWkQ5oZzzq3/d/13kxE64tTNE+UBOd3Jhax6q1w0birF4XW1iiJlz2yPuQ==
dependencies:
"@types/node" "*"
bignumber.js "~8.0.2"
ethereum-types "^2.1.6"
"@types/node" "12.12.54"
bignumber.js "~9.0.0"
ethereum-types "^3.3.3"
"@0x/typescript-typings@^4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-4.3.0.tgz#4813a996ac5101841d1c22f4aa1738ab56168857"
integrity sha512-6IH2JyKyl33+40tJ5rEhaMPTS2mVuRvoNmoXlCd/F0GPYSsDHMGObIXOkx+Qsw5SyCmqNs/3CTLeeCCqiSUdaw==
"@0x/typescript-typings@^5.1.5":
version "5.1.5"
resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-5.1.5.tgz#dd0ad20ef42dad9d054886fd1da72839145b5863"
integrity sha512-I55QfQNJPo8tG6j/PsTTgbeaIMbkGs5vdwVVfFkxSE8rXIEh4Qsra3JXke/7EpFZvhoUFngX4qdQyK2kI4V3sw==
dependencies:
"@types/bn.js" "^4.11.0"
"@types/node" "12.12.54"
"@types/react" "*"
bignumber.js "~8.0.2"
ethereum-types "^2.1.6"
bignumber.js "~9.0.0"
ethereum-types "^3.3.3"
popper.js "1.14.3"
"@0x/utils@^4.5.2":
version "4.5.2"
resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-4.5.2.tgz#6cc89f2d0dda341e0fb4e76049a35abfb67a4ac5"
integrity sha512-NWfNcvyiOhouk662AWxX0ZVe4ednBZJS9WZT/by3DBCY/WvN7WHMpEy9M5rBCxO+JJndLYeB5eBztDp7W+Ytkw==
"@0x/utils@^6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-6.1.0.tgz#df5750927d8e2b82f6709d666e7fcfca0f33f66e"
integrity sha512-Yrp4eonmFPgJcP1Bnl/xiCMEfiLVrYg3WxKJqMN6AmuW9VZeBhSJDqTMv5Et3qhFTBaY5YMWiISRYeJoVdMfDw==
dependencies:
"@0x/types" "^2.4.3"
"@0x/typescript-typings" "^4.3.0"
"@types/node" "*"
"@0x/types" "^3.3.0"
"@0x/typescript-typings" "^5.1.5"
"@types/node" "12.12.54"
abortcontroller-polyfill "^1.1.9"
bignumber.js "~8.0.2"
bignumber.js "~9.0.0"
chalk "^2.3.0"
detect-node "2.0.3"
ethereum-types "^2.1.6"
ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1"
ethers "~4.0.4"
isomorphic-fetch "2.2.1"
js-sha3 "^0.7.0"
lodash "^4.17.11"
"@0x/web3-wrapper@^6.0.13":
version "6.0.13"
resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-6.0.13.tgz#2e666221bd44ceebe02762028214d4aa41ad7247"
integrity sha512-LQjKBCCNdkJuhcJld+/sy+G0+sJu5qp9VDNNwJGLDxWIJpgoshhUpBPi7vUnZ79UY4SYuNcC4yM9yI61cs7ZiA==
"@0x/web3-wrapper@^7.2.8":
version "7.2.8"
resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-7.2.8.tgz#7df4c52e358594338f8dbe76b1490a5c4c423633"
integrity sha512-T0HxSm3Kzs+fzCZI7Yk7MAjfp5vi+AMAHBbuXxJfzr3L6SFObSyHy6TPQ9NMNGrrbJi6UIOLPOX8W0P7hm0XtA==
dependencies:
"@0x/assert" "^2.1.6"
"@0x/json-schemas" "^4.0.2"
"@0x/typescript-typings" "^4.3.0"
"@0x/utils" "^4.5.2"
ethereum-types "^2.1.6"
"@0x/assert" "^3.0.17"
"@0x/json-schemas" "^5.3.3"
"@0x/typescript-typings" "^5.1.5"
"@0x/utils" "^6.1.0"
"@types/node" "12.12.54"
ethereum-types "^3.3.3"
ethereumjs-util "^5.1.1"
ethers "~4.0.4"
lodash "^4.17.11"
@@ -1391,7 +1405,7 @@
"@ethersproject/bytes" ">=5.0.0-beta.129"
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"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.4.tgz#36ca0a7d1ae2a272da5654cb886776d0c680ef3a"
integrity sha512-GNpiOUm9PGUxFNqOxYKDQBM0u68bG9XC9iOulEQ8I0tOx/4qUpgVzvgXL6ugxr0RY554Gz/NQsVqknqPzUcxpQ==
@@ -2914,7 +2928,7 @@
dependencies:
"@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"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
@@ -3042,12 +3056,17 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40"
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":
version "13.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
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"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.18.tgz#ae364d97382aacdebf583fa4e7132af2dfe56a0c"
integrity sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg==
@@ -3058,14 +3077,9 @@
integrity sha512-KUyZdkGCnVPuXfsKmDUu2XLui65LZIJ2s0M57noy5e+ixUT2oK33ep7zlvgzI8LElcWqbf8AR+o/3GqAPac2zA==
"@types/node@^12.12.6":
version "12.12.69"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.69.tgz#7cb6a3aa0d16664bf2dcd1450ccb8477464fbd79"
integrity sha512-2F2VQRSFmzqgUEXw75L51MgnnZqc6bKWVSUPfrDPzp6mzGGibeVwyQcpvZvBr5RnsoMRHmC8EcBQiobSeqeJxg==
"@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==
version "12.12.67"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.67.tgz#4f86badb292e822e3b13730a1f9713ed2377f789"
integrity sha512-R48tgL2izApf+9rYNH+3RBMbRpPeW3N8f0I9HMhggeq4UXwBDqumJ14SDs4ctTMhG11pIOduZ4z3QWGOiMc9Vg==
"@types/node@^8.0.0":
version "8.10.59"
@@ -3361,24 +3375,37 @@
resolved "https://registry.yarnpkg.com/@use-it/interval/-/interval-0.1.3.tgz#5d1096b2295d7a5dda8e8022f3abb5f9d9ef27f8"
integrity sha512-chshdtDZTFoWA9aszBz1Cc04Ca9NBD2JTi/GMjdJ+HGm4q7Vy1v71+2mm22r7Kfb2nYW+lTRsPcEHdB/VFVHsQ==
"@web3-js/scrypt-shim@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@web3-js/scrypt-shim/-/scrypt-shim-0.1.0.tgz#0bf7529ab6788311d3e07586f7d89107c3bea2cc"
integrity sha512-ZtZeWCc/s0nMcdx/+rZwY1EcuRdemOK9ag21ty9UsHkFxsNb/AaoucUz0iPuyGe0Ku+PFuRmWZG7Z7462p9xPw==
"@web3-react/abstract-connector@^6.0.7":
version "6.0.7"
resolved "https://registry.yarnpkg.com/@web3-react/abstract-connector/-/abstract-connector-6.0.7.tgz#401b3c045f1e0fab04256311be49d5144e9badc6"
integrity sha512-RhQasA4Ox8CxUC0OENc1AJJm8UTybu/oOCM61Zjg6y0iF7Z0sqv1Ai1VdhC33hrQpA8qSBgoXN9PaP8jKmtdqg==
dependencies:
scryptsy "^2.1.0"
semver "^6.3.0"
"@web3-react/types" "^6.0.7"
"@web3-js/websocket@^1.0.29":
version "1.0.30"
resolved "https://registry.yarnpkg.com/@web3-js/websocket/-/websocket-1.0.30.tgz#9ea15b7b582cf3bf3e8bc1f4d3d54c0731a87f87"
integrity sha512-fDwrD47MiDrzcJdSeTLF75aCcxVVt8B1N74rA+vh2XCAvFy4tEWJjtnUtj2QG7/zlQ6g9cQ88bZFBxwd9/FmtA==
"@web3-react/core@^6.1.1":
version "6.1.1"
resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-6.1.1.tgz#06c853890723f600b387b738a4b71ef41d5cccb7"
integrity sha512-HKXOgPNCmFvrVsed+aW/HlVhwzs8t3b+nzg3BoxgJQo/5yLiJXSumHRBdUrPxhBQiHkHRZiVPAvzf/8JMnm74Q==
dependencies:
debug "^2.2.0"
es5-ext "^0.10.50"
nan "^2.14.0"
typedarray-to-buffer "^3.1.5"
yaeti "^0.0.6"
"@ethersproject/keccak256" "^5.0.0-beta.130"
"@web3-react/abstract-connector" "^6.0.7"
"@web3-react/types" "^6.0.7"
tiny-invariant "^1.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":
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"
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"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
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:
version "1.13.1"
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"
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"
resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0"
integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==
dependencies:
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:
version "0.1.1"
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"
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:
version "4.0.3"
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"
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:
version "1.0.0"
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"
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"
resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8"
integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==
@@ -8545,13 +8566,13 @@ ethereum-cryptography@^0.1.3:
secp256k1 "^4.0.1"
setimmediate "^1.0.5"
ethereum-types@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-2.1.6.tgz#57d9d515fad86ab987c0f6962c4203be37da8579"
integrity sha512-xaN5TxLvkdFCGjGfUQ5wV00GHzDHStozP1j+K/YdmUeQXVGiD15cogYPhBVWG3pQJM/aBjtYrpMrjywvKkNC4A==
ethereum-types@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-3.3.3.tgz#b9328185034ee52efa32176eb6fd9f3e741cb231"
integrity sha512-FWW7ajHqgoqVHhPMX4sY2ycARpPFL8p/64rpToo8awNrJY7rBDnSC8esQYlAPeaiawf9fTM/xAgEm9VKY7J5kg==
dependencies:
"@types/node" "*"
bignumber.js "~8.0.2"
"@types/node" "12.12.54"
bignumber.js "~9.0.0"
ethereumjs-abi@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"
integrity sha1-R3hr2qCHyvext15zq8XH1UAVjNA=
eventemitter3@3.1.2, 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==
eventemitter3@4.0.4, eventemitter3@^4.0.0:
eventemitter3@4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
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:
version "3.0.0"
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"
yargs "13.2.4"
ganache-core@^2.6.0:
ganache-core@^2.10.2:
version "2.13.1"
resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.13.1.tgz#bf60399a2dd084e1090db91cbbc7ed3885dc01e4"
integrity sha512-Ewg+kNcDqXtOohe7jCcP+ZUv9EMzOx2MoqOYYP3BCfxrDh3KjBXXaKK+Let7li0TghAs9lxmBgevZku35j5YzA==
@@ -12523,11 +12544,6 @@ lcid@^2.0.0:
dependencies:
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:
version "1.3.0"
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"
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:
version "1.6.1"
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"
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:
version "0.7.5"
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"
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"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -17634,11 +17650,6 @@ scryptsy@^1.2.1:
dependencies:
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:
version "0.2.3"
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"
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:
version "1.0.2"
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"
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:
version "1.3.0"
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-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:
version "1.3.0"
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-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:
version "1.3.0"
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:
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:
version "1.3.0"
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-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:
version "1.3.0"
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"
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:
version "1.3.0"
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-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:
version "1.3.0"
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"
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:
version "1.3.0"
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-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:
version "1.3.0"
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-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:
version "1.3.0"
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-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:
version "1.3.0"
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"
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:
version "1.3.0"
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-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:
version "1.3.0"
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-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:
version "1.3.0"
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-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:
version "1.3.0"
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"
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:
version "1.3.0"
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"
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:
version "1.3.0"
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"
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:
version "1.3.0"
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-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:
version "1.3.0"
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"
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:
version "1.3.0"
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-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:
version "1.3.0"
resolved "https://registry.yarnpkg.com/web3/-/web3-1.3.0.tgz#8fe4cd6e2a21c91904f343ba75717ee4c76bb349"