From 8c268d6f0697ad35215ae4e32de0a5a5f2e89c2a Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 6 Jul 2020 15:33:23 -0300 Subject: [PATCH 01/12] Add ALM snapshots (#382) --- alm/.gitignore | 2 + alm/package.json | 8 +- alm/scripts/createSnapshots.js | 118 ++++++++++++++++++ alm/src/components/ExecutionConfirmation.tsx | 11 +- .../components/ValidatorsConfirmations.tsx | 11 +- alm/src/components/commons/Table.tsx | 13 ++ alm/src/hooks/useBlockConfirmations.ts | 9 +- alm/src/hooks/useNetwork.ts | 13 +- alm/src/hooks/useValidatorContract.ts | 22 ++-- alm/src/services/SnapshotProvider.ts | 69 ++++++++++ alm/src/snapshots/.gitkeep | 0 alm/src/state/StateProvider.tsx | 5 +- alm/src/utils/contract.ts | 65 +++++++--- alm/src/utils/web3.ts | 9 ++ yarn.lock | 5 + 15 files changed, 309 insertions(+), 51 deletions(-) create mode 100644 alm/scripts/createSnapshots.js create mode 100644 alm/src/components/commons/Table.tsx create mode 100644 alm/src/services/SnapshotProvider.ts create mode 100644 alm/src/snapshots/.gitkeep diff --git a/alm/.gitignore b/alm/.gitignore index 4d29575d..d77603ef 100644 --- a/alm/.gitignore +++ b/alm/.gitignore @@ -1,5 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +src/snapshots/*.json + # dependencies /node_modules /.pnp diff --git a/alm/package.json b/alm/package.json index 16b6d329..dfee20a9 100644 --- a/alm/package.json +++ b/alm/package.json @@ -17,6 +17,7 @@ "@use-it/interval": "^0.1.3", "customize-cra": "^1.0.0", "date-fns": "^2.14.0", + "dotenv": "^8.2.0", "fast-memoize": "^2.5.2", "promise-retry": "^2.0.1", "react": "^16.13.1", @@ -30,11 +31,12 @@ "web3-eth-contract": "1.2.7" }, "scripts": { - "start": "./load-env.sh react-app-rewired start", - "build": "./load-env.sh react-app-rewired build", + "start": "yarn createSnapshots && ./load-env.sh react-app-rewired start", + "build": "yarn createSnapshots && ./load-env.sh react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject", - "lint": "eslint '*/**/*.{js,ts,tsx}' --ignore-path ../.eslintignore" + "lint": "eslint '*/**/*.{js,ts,tsx}' --ignore-path ../.eslintignore", + "createSnapshots": "node scripts/createSnapshots.js" }, "eslintConfig": { "extends": "react-app" diff --git a/alm/scripts/createSnapshots.js b/alm/scripts/createSnapshots.js new file mode 100644 index 00000000..604c72d4 --- /dev/null +++ b/alm/scripts/createSnapshots.js @@ -0,0 +1,118 @@ +const { BRIDGE_VALIDATORS_ABI, HOME_AMB_ABI } = require('commons') + +const path = require('path') +require('dotenv').config() +const Web3 = require('web3') + +const fs = require('fs') + +const { + COMMON_HOME_RPC_URL, + COMMON_HOME_BRIDGE_ADDRESS, + COMMON_FOREIGN_RPC_URL, + COMMON_FOREIGN_BRIDGE_ADDRESS +} = process.env + +const generateSnapshot = async (side, url, bridgeAddress) => { + const snapshotPath = `../src/snapshots/${side}.json` + const snapshotFullPath = path.join(__dirname, snapshotPath) + const snapshot = {} + + const web3 = new Web3(new Web3.providers.HttpProvider(url)) + + const currentBlockNumber = await web3.eth.getBlockNumber() + snapshot.snapshotBlockNumber = currentBlockNumber + + // Save chainId + snapshot.chainId = await web3.eth.getChainId() + + const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress) + + // Save RequiredBlockConfirmationChanged events + let requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('RequiredBlockConfirmationChanged', { + fromBlock: 0, + toBlock: currentBlockNumber + }) + + // In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB + // manually generate an event for this. Example Sokol - Kovan bridge + if (requiredBlockConfirmationChangedEvents.length === 0) { + const deployedAtBlock = await bridgeContract.methods.deployedAtBlock().call() + const blockConfirmations = await bridgeContract.methods.requiredBlockConfirmations().call() + + requiredBlockConfirmationChangedEvents.push({ + blockNumber: parseInt(deployedAtBlock), + returnValues: { + requiredBlockConfirmations: blockConfirmations + } + }) + } + + snapshot.RequiredBlockConfirmationChanged = requiredBlockConfirmationChangedEvents.map(e => ({ + blockNumber: e.blockNumber, + returnValues: { + requiredBlockConfirmations: e.returnValues.requiredBlockConfirmations + } + })) + + const validatorAddress = await bridgeContract.methods.validatorContract().call() + const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress) + + // Save RequiredSignaturesChanged events + const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', { + fromBlock: 0, + toBlock: currentBlockNumber + }) + snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({ + blockNumber: e.blockNumber, + returnValues: { + requiredSignatures: e.returnValues.requiredSignatures + } + })) + + // Save ValidatorAdded events + const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', { + fromBlock: 0, + toBlock: currentBlockNumber + }) + + snapshot.ValidatorAdded = validatorAddedEvents.map(e => ({ + blockNumber: e.blockNumber, + returnValues: { + validator: e.returnValues.validator + }, + event: 'ValidatorAdded' + })) + + // Save ValidatorRemoved events + const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', { + fromBlock: 0, + toBlock: currentBlockNumber + }) + + snapshot.ValidatorRemoved = validatorRemovedEvents.map(e => ({ + blockNumber: e.blockNumber, + returnValues: { + validator: e.returnValues.validator + }, + event: 'ValidatorRemoved' + })) + + // Write snapshot + fs.writeFileSync(snapshotFullPath, JSON.stringify(snapshot, null, 2)) +} + +const main = async () => { + await Promise.all([ + generateSnapshot('home', COMMON_HOME_RPC_URL, COMMON_HOME_BRIDGE_ADDRESS), + generateSnapshot('foreign', COMMON_FOREIGN_RPC_URL, COMMON_FOREIGN_BRIDGE_ADDRESS) + ]) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.log('Error while creating snapshots') + console.error(error) + process.exit(0) + }) diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx index d2f5fee5..17d78148 100644 --- a/alm/src/components/ExecutionConfirmation.tsx +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -7,10 +7,7 @@ import styled from 'styled-components' import { ExecutionData } from '../hooks/useMessageConfirmations' import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels' import { ExplorerTxLink } from './commons/ExplorerTxLink' - -const Thead = styled.thead` - border-bottom: 2px solid #9e9e9e; -` +import { Thead, AgeTd, StatusTd } from './commons/Table' const StyledExecutionConfirmation = styled.div` margin-top: 30px; @@ -55,12 +52,12 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir {formattedValidator ? formattedValidator : } - {getExecutionStatusElement(executionData.status)} - + {getExecutionStatusElement(executionData.status)} + {executionData.timestamp > 0 ? formatTimestamp(executionData.timestamp) : ''} - + diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx index df8d94de..bcd08394 100644 --- a/alm/src/components/ValidatorsConfirmations.tsx +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -7,10 +7,7 @@ import styled from 'styled-components' import { ConfirmationParam } from '../hooks/useMessageConfirmations' import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels' import { ExplorerTxLink } from './commons/ExplorerTxLink' - -const Thead = styled.thead` - border-bottom: 2px solid #9e9e9e; -` +import { Thead, AgeTd, StatusTd } from './commons/Table' const RequiredConfirmations = styled.label` font-size: 14px; @@ -70,14 +67,14 @@ export const ValidatorsConfirmations = ({ return ( {windowWidth < 850 ? formatTxHash(validator) : validator} - {getValidatorStatusElement(displayedStatus)} - + {getValidatorStatusElement(displayedStatus)} + {confirmation && confirmation.timestamp > 0 ? formatTimestamp(confirmation.timestamp) : elementIfNoTimestamp} - + ) })} diff --git a/alm/src/components/commons/Table.tsx b/alm/src/components/commons/Table.tsx new file mode 100644 index 00000000..698b2d54 --- /dev/null +++ b/alm/src/components/commons/Table.tsx @@ -0,0 +1,13 @@ +import styled from 'styled-components' + +export const Thead = styled.thead` + border-bottom: 2px solid #9e9e9e; +` + +export const StatusTd = styled.td` + width: 150px; +` + +export const AgeTd = styled.td` + width: 180px; +` diff --git a/alm/src/hooks/useBlockConfirmations.ts b/alm/src/hooks/useBlockConfirmations.ts index 438e9e5a..470cfc23 100644 --- a/alm/src/hooks/useBlockConfirmations.ts +++ b/alm/src/hooks/useBlockConfirmations.ts @@ -3,6 +3,7 @@ import { TransactionReceipt } from 'web3-eth' import { useStateProvider } from '../state/StateProvider' import { Contract } from 'web3-eth-contract' import { getRequiredBlockConfirmations } from '../utils/contract' +import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider' export interface UseBlockConfirmationsParams { fromHome: boolean @@ -17,17 +18,19 @@ export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmatio const callRequireBlockConfirmations = async ( contract: Contract, receipt: TransactionReceipt, - setResult: Function + setResult: Function, + snapshotProvider: SnapshotProvider ) => { - const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber) + const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider) setResult(result) } useEffect( () => { const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract + const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider if (!bridgeContract || !receipt) return - callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations) + callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations, snapshotProvider) }, [home.bridgeContract, foreign.bridgeContract, receipt, fromHome] ) diff --git a/alm/src/hooks/useNetwork.ts b/alm/src/hooks/useNetwork.ts index 8c017b39..21a48886 100644 --- a/alm/src/hooks/useNetwork.ts +++ b/alm/src/hooks/useNetwork.ts @@ -1,7 +1,8 @@ import { useEffect, useState } from 'react' -import { getWeb3 } from '../utils/web3' +import { getChainId, getWeb3 } from '../utils/web3' +import { SnapshotProvider } from '../services/SnapshotProvider' -export const useNetwork = (url: string) => { +export const useNetwork = (url: string, snapshotProvider: SnapshotProvider) => { const [loading, setLoading] = useState(true) const [chainId, setChainId] = useState(0) const web3 = getWeb3(url) @@ -9,14 +10,14 @@ export const useNetwork = (url: string) => { useEffect( () => { setLoading(true) - const getChainId = async () => { - const id = await web3.eth.getChainId() + const getWeb3ChainId = async () => { + const id = await getChainId(web3, snapshotProvider) setChainId(id) setLoading(false) } - getChainId() + getWeb3ChainId() }, - [web3.eth] + [web3, snapshotProvider] ) return { diff --git a/alm/src/hooks/useValidatorContract.ts b/alm/src/hooks/useValidatorContract.ts index 07ea7cfb..8557883f 100644 --- a/alm/src/hooks/useValidatorContract.ts +++ b/alm/src/hooks/useValidatorContract.ts @@ -5,6 +5,7 @@ import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '.. import { BRIDGE_VALIDATORS_ABI } from '../abis' import { useStateProvider } from '../state/StateProvider' import { TransactionReceipt } from 'web3-eth' +import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider' export interface useValidatorContractParams { fromHome: boolean @@ -28,16 +29,22 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract const callRequiredSignatures = async ( contract: Maybe, receipt: TransactionReceipt, - setResult: Function + setResult: Function, + snapshotProvider: SnapshotProvider ) => { if (!contract) return - const result = await getRequiredSignatures(contract, receipt.blockNumber) + const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider) setResult(result) } - const callValidatorList = async (contract: Maybe, receipt: TransactionReceipt, setResult: Function) => { + const callValidatorList = async ( + contract: Maybe, + receipt: TransactionReceipt, + setResult: Function, + snapshotProvider: SnapshotProvider + ) => { if (!contract) return - const result = await getValidatorList(contract, receipt.blockNumber) + const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider) setResult(result) } @@ -55,10 +62,11 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract useEffect( () => { if (!receipt) return - callRequiredSignatures(validatorContract, receipt, setRequiredSignatures) - callValidatorList(validatorContract, receipt, setValidatorList) + const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider + callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider) + callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider) }, - [validatorContract, receipt] + [validatorContract, receipt, fromHome] ) return { diff --git a/alm/src/services/SnapshotProvider.ts b/alm/src/services/SnapshotProvider.ts new file mode 100644 index 00000000..fec8a1ef --- /dev/null +++ b/alm/src/services/SnapshotProvider.ts @@ -0,0 +1,69 @@ +const initialValue = { + chainId: 0, + RequiredBlockConfirmationChanged: [], + RequiredSignaturesChanged: [], + ValidatorAdded: [], + ValidatorRemoved: [], + snapshotBlockNumber: 0 +} + +export interface SnapshotEvent { + blockNumber: number + returnValues: any +} + +export interface SnapshotValidatorEvent { + blockNumber: number + returnValues: any + event: string +} + +export interface Snapshot { + chainId: number + RequiredBlockConfirmationChanged: SnapshotEvent[] + RequiredSignaturesChanged: SnapshotEvent[] + ValidatorAdded: SnapshotValidatorEvent[] + ValidatorRemoved: SnapshotValidatorEvent[] + snapshotBlockNumber: number +} + +export class SnapshotProvider { + private data: Snapshot + + constructor(side: string) { + let data = initialValue + try { + data = require(`../snapshots/${side}.json`) + } catch (e) { + console.log('Snapshot not found') + } + this.data = data + } + + chainId() { + return this.data.chainId + } + + snapshotBlockNumber() { + return this.data.snapshotBlockNumber + } + + requiredBlockConfirmationEvents(toBlock: number) { + return this.data.RequiredBlockConfirmationChanged.filter(e => e.blockNumber <= toBlock) + } + + requiredSignaturesEvents(toBlock: number) { + return this.data.RequiredSignaturesChanged.filter(e => e.blockNumber <= toBlock) + } + + validatorAddedEvents(fromBlock: number) { + return this.data.ValidatorAdded.filter(e => e.blockNumber >= fromBlock) + } + + validatorRemovedEvents(fromBlock: number) { + return this.data.ValidatorRemoved.filter(e => e.blockNumber >= fromBlock) + } +} + +export const homeSnapshotProvider = new SnapshotProvider('home') +export const foreignSnapshotProvider = new SnapshotProvider('foreign') diff --git a/alm/src/snapshots/.gitkeep b/alm/src/snapshots/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/alm/src/state/StateProvider.tsx b/alm/src/state/StateProvider.tsx index 16762011..c495ef65 100644 --- a/alm/src/state/StateProvider.tsx +++ b/alm/src/state/StateProvider.tsx @@ -11,6 +11,7 @@ import { import Web3 from 'web3' import { useBridgeContracts } from '../hooks/useBridgeContracts' import { Contract } from 'web3-eth-contract' +import { foreignSnapshotProvider, homeSnapshotProvider } from '../services/SnapshotProvider' export interface BaseNetworkParams { chainId: number @@ -47,8 +48,8 @@ const initialState = { const StateContext = createContext(initialState) export const StateProvider = ({ children }: { children: ReactNode }) => { - const homeNetwork = useNetwork(HOME_RPC_URL) - const foreignNetwork = useNetwork(FOREIGN_RPC_URL) + const homeNetwork = useNetwork(HOME_RPC_URL, homeSnapshotProvider) + const foreignNetwork = useNetwork(FOREIGN_RPC_URL, foreignSnapshotProvider) const { homeBridge, foreignBridge } = useBridgeContracts({ homeWeb3: homeNetwork.web3, foreignWeb3: foreignNetwork.web3 diff --git a/alm/src/utils/contract.ts b/alm/src/utils/contract.ts index 2b81c290..f5578c01 100644 --- a/alm/src/utils/contract.ts +++ b/alm/src/utils/contract.ts @@ -1,10 +1,24 @@ import { Contract } from 'web3-eth-contract' +import { EventData } from 'web3-eth-contract' +import { SnapshotProvider } from '../services/SnapshotProvider' -export const getRequiredBlockConfirmations = async (contract: Contract, blockNumber: number) => { - const events = await contract.getPastEvents('RequiredBlockConfirmationChanged', { - fromBlock: 0, - toBlock: blockNumber - }) +export const getRequiredBlockConfirmations = async ( + contract: Contract, + blockNumber: number, + snapshotProvider: SnapshotProvider +) => { + const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber) + const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() + + let contractEvents: EventData[] = [] + if (blockNumber > snapshotBlockNumber) { + contractEvents = await contract.getPastEvents('RequiredBlockConfirmationChanged', { + fromBlock: snapshotBlockNumber + 1, + toBlock: blockNumber + }) + } + + const events = [...eventsFromSnapshot, ...contractEvents] let blockConfirmations if (events.length > 0) { @@ -21,11 +35,23 @@ export const getRequiredBlockConfirmations = async (contract: Contract, blockNum export const getValidatorAddress = (contract: Contract) => contract.methods.validatorContract().call() -export const getRequiredSignatures = async (contract: Contract, blockNumber: number) => { - const events = await contract.getPastEvents('RequiredSignaturesChanged', { - fromBlock: 0, - toBlock: blockNumber - }) +export const getRequiredSignatures = async ( + contract: Contract, + blockNumber: number, + snapshotProvider: SnapshotProvider +) => { + const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber) + const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() + + let contractEvents: EventData[] = [] + if (blockNumber > snapshotBlockNumber) { + contractEvents = await contract.getPastEvents('RequiredSignaturesChanged', { + fromBlock: snapshotBlockNumber + 1, + toBlock: blockNumber + }) + } + + const events = [...eventsFromSnapshot, ...contractEvents] // Use the value form last event before the transaction const event = events[events.length - 1] @@ -33,19 +59,26 @@ export const getRequiredSignatures = async (contract: Contract, blockNumber: num return parseInt(requiredSignatures) } -export const getValidatorList = async (contract: Contract, blockNumber: number) => { - let currentList: string[] = await contract.methods.validatorList().call() - const [added, removed] = await Promise.all([ +export const getValidatorList = async (contract: Contract, blockNumber: number, snapshotProvider: SnapshotProvider) => { + const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber) + const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber) + const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() + + const fromBlock = snapshotBlockNumber > blockNumber ? snapshotBlockNumber + 1 : blockNumber + const [currentList, added, removed] = await Promise.all([ + contract.methods.validatorList().call(), contract.getPastEvents('ValidatorAdded', { - fromBlock: blockNumber + fromBlock }), contract.getPastEvents('ValidatorRemoved', { - fromBlock: blockNumber + fromBlock }) ]) // Ordered desc - const orderedEvents = [...added, ...removed].sort(({ blockNumber: prev }, { blockNumber: next }) => next - prev) + const orderedEvents = [...addedEventsFromSnapshot, ...added, ...removedEventsFromSnapshot, ...removed].sort( + ({ blockNumber: prev }, { blockNumber: next }) => next - prev + ) // Stored as a Set to avoid duplicates const validatorList = new Set(currentList) diff --git a/alm/src/utils/web3.ts b/alm/src/utils/web3.ts index 0ed56a0a..09d129b7 100644 --- a/alm/src/utils/web3.ts +++ b/alm/src/utils/web3.ts @@ -5,6 +5,7 @@ import { AbiItem } from 'web3-utils' import memoize from 'fast-memoize' import promiseRetry from 'promise-retry' import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../abis' +import { SnapshotProvider } from '../services/SnapshotProvider' export interface MessageObject { id: string @@ -61,3 +62,11 @@ export const getBlock = async (web3: Web3, blockNumber: number): Promise { + let id = snapshotProvider.chainId() + if (id === 0) { + id = await web3.eth.getChainId() + } + return id +} diff --git a/yarn.lock b/yarn.lock index 4edd5fa5..e462ecb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7550,6 +7550,11 @@ dotenv@^7.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + dotignore@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/dotignore/-/dotignore-0.1.2.tgz#f942f2200d28c3a76fbdd6f0ee9f3257c8a2e905" From 2edd8f2783214b5b15a3bc50fd4561954b2edf0b Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 7 Jul 2020 21:26:14 +0700 Subject: [PATCH 02/12] Add bw plugin assets (#387) --- burner-wallet-plugin/staging/src/index.tsx | 28 ++++++++-- burner-wallet-plugin/testing/src/index.tsx | 3 +- .../src/bridges/MOONBridge.ts | 12 +++++ .../src/bridges/QDAIBridge.ts | 12 +++++ .../{wetc-bridge => bridges}/WETCBridge.ts | 10 ++-- .../src/bridges/index.ts | 3 ++ .../assets/BridgeableERC20Asset.ts | 53 ++++++++++++------- .../src/burner-wallet/assets/Dai.ts | 11 ++++ .../src/burner-wallet/assets/ERC677Asset.ts | 6 +-- .../src/burner-wallet/assets/Etc.ts | 4 +- .../src/burner-wallet/assets/MOON.ts | 10 ++++ .../assets/NativeMediatorAsset.ts | 6 +-- .../src/burner-wallet/assets/qDai.ts | 9 ++++ .../src/burner-wallet/assets/xMOON.ts | 9 ++++ .../gateways/TokenBridgeGateway.ts | 5 +- .../src/burner-wallet/index.ts | 4 ++ .../tokenbridge-bw-exchange/src/index.ts | 6 ++- .../src/utils/abis/Mediator.ts | 14 +++++ .../src/utils/index.ts | 2 +- .../src/utils/utils.ts | 14 ++++- .../src/wetc-bridge/index.ts | 1 - 21 files changed, 174 insertions(+), 48 deletions(-) create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/MOONBridge.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/QDAIBridge.ts rename burner-wallet-plugin/tokenbridge-bw-exchange/src/{wetc-bridge => bridges}/WETCBridge.ts (90%) create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/index.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Dai.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/MOON.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/qDai.ts create mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/xMOON.ts delete mode 100644 burner-wallet-plugin/tokenbridge-bw-exchange/src/wetc-bridge/index.ts diff --git a/burner-wallet-plugin/staging/src/index.tsx b/burner-wallet-plugin/staging/src/index.tsx index 1571830a..f57c3a7f 100644 --- a/burner-wallet-plugin/staging/src/index.tsx +++ b/burner-wallet-plugin/staging/src/index.tsx @@ -2,19 +2,37 @@ import React from 'react' import ReactDOM from 'react-dom' import BurnerCore from '@burner-wallet/core' import { InjectedSigner, LocalSigner } from '@burner-wallet/core/signers' -import { InfuraGateway, InjectedGateway } from '@burner-wallet/core/gateways' +import { XDaiBridge } from '@burner-wallet/exchange' +import { xdai } from '@burner-wallet/assets' +import { InfuraGateway, InjectedGateway, XDaiGateway } from '@burner-wallet/core/gateways' import Exchange from '@burner-wallet/exchange' import ModernUI from '@burner-wallet/modern-ui' -import { Etc, Wetc, TokenBridgeGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange' +import { + Etc, + Wetc, + Dai, + qDai, + MOON, + xMOON, + TokenBridgeGateway, + WETCBridge, + QDAIBridge, + MOONBridge +} from '@poanet/tokenbridge-bw-exchange' import MetamaskPlugin from '@burner-wallet/metamask-plugin' const core = new BurnerCore({ signers: [new InjectedSigner(), new LocalSigner()], - gateways: [new InjectedGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY), new TokenBridgeGateway()], - assets: [Wetc, Etc] + gateways: [ + new InjectedGateway(), + new XDaiGateway(), + new InfuraGateway(process.env.REACT_APP_INFURA_KEY), + new TokenBridgeGateway() + ], + assets: [xdai, Wetc, Etc, Dai, qDai, MOON, xMOON] }) -const exchange = new Exchange([new WETCBridge()]) +const exchange = new Exchange([new XDaiBridge(), new WETCBridge(), new QDAIBridge(), new MOONBridge()]) const BurnerWallet = () => diff --git a/burner-wallet-plugin/testing/src/index.tsx b/burner-wallet-plugin/testing/src/index.tsx index 11ef314b..55fe875d 100644 --- a/burner-wallet-plugin/testing/src/index.tsx +++ b/burner-wallet-plugin/testing/src/index.tsx @@ -93,8 +93,7 @@ if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') { network: process.env.REACT_APP_FOREIGN_NETWORK, // @ts-ignore address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS, - // @ts-ignore - bridgeAddress: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS + bridgeModes: ['erc-to-native-amb'] }) testBridge = new MediatorErcToNative({ diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/MOONBridge.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/MOONBridge.ts new file mode 100644 index 00000000..055cfbe0 --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/MOONBridge.ts @@ -0,0 +1,12 @@ +import { Mediator } from '../burner-wallet' + +export default class MOONBridge extends Mediator { + constructor() { + super({ + assetA: 'xmoon', + assetABridge: '0x1E0507046130c31DEb20EC2f870ad070Ff266079', + assetB: 'moon', + assetBBridge: '0xFEaB457D95D9990b7eb6c943c839258245541754' + }) + } +} diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/QDAIBridge.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/QDAIBridge.ts new file mode 100644 index 00000000..7ad5c1bc --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/QDAIBridge.ts @@ -0,0 +1,12 @@ +import { MediatorErcToNative } from '../burner-wallet' + +export default class QDAIBridge extends MediatorErcToNative { + constructor() { + super({ + assetA: 'qdai', + assetABridge: '0xFEaB457D95D9990b7eb6c943c839258245541754', + assetB: 'dai', + assetBBridge: '0xf6edFA16926f30b0520099028A145F4E06FD54ed' + }) + } +} diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/wetc-bridge/WETCBridge.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/WETCBridge.ts similarity index 90% rename from burner-wallet-plugin/tokenbridge-bw-exchange/src/wetc-bridge/WETCBridge.ts rename to burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/WETCBridge.ts index 498e17fb..e573bac1 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/wetc-bridge/WETCBridge.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/WETCBridge.ts @@ -1,6 +1,6 @@ import { Mediator } from '../burner-wallet' import { HOME_NATIVE_TO_ERC_ABI, FOREIGN_NATIVE_TO_ERC_ABI } from '../utils' -import { waitForEvent, isBridgeContract, constants } from '../utils' +import { waitForEvent, isVanillaBridgeContract, constants } from '../utils' import { ValueTypes } from '@burner-wallet/exchange' import { toBN, fromWei } from 'web3-utils' @@ -19,7 +19,7 @@ export default class WETCBridge extends Mediator { .getAsset(this.assetA) .getWeb3() const contract = new web3.eth.Contract(HOME_NATIVE_TO_ERC_ABI, this.assetABridge) - const listenToBridgeEvent = await isBridgeContract(contract) + const listenToBridgeEvent = await isVanillaBridgeContract(contract) if (listenToBridgeEvent) { await waitForEvent(web3, contract, 'AffirmationCompleted', this.processBridgeEvents(sendResult.txHash)) } else { @@ -32,7 +32,7 @@ export default class WETCBridge extends Mediator { .getAsset(this.assetB) .getWeb3() const contract = new web3.eth.Contract(FOREIGN_NATIVE_TO_ERC_ABI, this.assetBBridge) - const listenToBridgeEvent = await isBridgeContract(contract) + const listenToBridgeEvent = await isVanillaBridgeContract(contract) if (listenToBridgeEvent) { await waitForEvent(web3, contract, 'RelayedMessage', this.processBridgeEvents(sendResult.txHash)) } else { @@ -53,7 +53,7 @@ export default class WETCBridge extends Mediator { .getWeb3() const contract = new web3.eth.Contract(FOREIGN_NATIVE_TO_ERC_ABI, this.assetBBridge) - const useBridgeContract = await isBridgeContract(contract) + const useBridgeContract = await isVanillaBridgeContract(contract) if (useBridgeContract) { const fee = toBN(await contract.methods.getHomeFee().call()) @@ -79,7 +79,7 @@ export default class WETCBridge extends Mediator { .getWeb3() const contract = new web3.eth.Contract(HOME_NATIVE_TO_ERC_ABI, this.assetABridge) - const useBridgeContract = await isBridgeContract(contract) + const useBridgeContract = await isVanillaBridgeContract(contract) if (useBridgeContract) { const fee = toBN(await contract.methods.getForeignFee().call()) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/index.ts new file mode 100644 index 00000000..ab3128d0 --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/bridges/index.ts @@ -0,0 +1,3 @@ +export { default as WETCBridge } from './WETCBridge' +export { default as QDAIBridge } from './QDAIBridge' +export { default as MOONBridge } from './MOONBridge' diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts index e1175cef..35337657 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/BridgeableERC20Asset.ts @@ -1,42 +1,55 @@ import { ERC20Asset } from '@burner-wallet/assets' -import { MEDIATOR_ABI, constants } from '../../utils' -import { toBN } from 'web3-utils' +import { AssetConstructor } from '@burner-wallet/assets/Asset' +import { MEDIATOR_ABI, constants, isBridgeContract } from '../../utils' +import { toBN, soliditySha3 } from 'web3-utils' -interface BridgeableERC20Constructor { +interface BridgeableERC20Constructor extends AssetConstructor { abi?: object address: string - id: string - name: string - network: string - bridgeAddress: string + bridgeModes: string[] } export default class BridgeableERC20Asset extends ERC20Asset { - protected bridgeAddress: string - private _bridge + private _bridgeModes + private _bridges: { [addr: string]: object | false } - constructor({ bridgeAddress, ...params }: BridgeableERC20Constructor) { - super({ ...params }) - this.bridgeAddress = bridgeAddress.toLowerCase() + public set bridgeModes(bridgeModes: string[]) { + this._bridgeModes = bridgeModes.map(s => soliditySha3(s)!.slice(0, 10)) } - getBridgeContract() { - if (!this._bridge) { + public get bridgeModes() { + return this._bridgeModes + } + + constructor({ + bridgeModes = ['erc-to-native-core', 'erc-to-native-amb', 'erc-to-erc-core', 'erc-to-erc-amb'], + ...params + }: BridgeableERC20Constructor) { + super(params) + this._bridges = {} + this.bridgeModes = bridgeModes + } + + async getBridgeContract(addr: string) { + if (typeof this._bridges[addr] === 'undefined') { const Contract = this.getWeb3().eth.Contract - this._bridge = new Contract(MEDIATOR_ABI, this.bridgeAddress) + const bridge = new Contract(MEDIATOR_ABI, addr) + if (await isBridgeContract(bridge, this.bridgeModes)) { + return (this._bridges[addr] = bridge) + } + return (this._bridges[addr] = false) } - return this._bridge + return this._bridges[addr] } async _send({ from, to, value }) { - if (to.toLowerCase() === this.bridgeAddress) { + const bridge = await this.getBridgeContract(to) + if (bridge) { const allowance = await this.allowance(from, to) if (toBN(allowance).lt(toBN(value))) { await this.approve(from, to, value) } - const receipt = await this.getBridgeContract() - .methods.relayTokens(from, value) - .send({ from }) + const receipt = await bridge.methods.relayTokens(from, value).send({ from }) const transferLog = Object.values(receipt.events as object).find( e => e.raw.topics[0] === constants.TRANSFER_TOPIC ) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Dai.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Dai.ts new file mode 100644 index 00000000..7ad36821 --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Dai.ts @@ -0,0 +1,11 @@ +import BridgeableERC20Asset from './BridgeableERC20Asset' + +export default new BridgeableERC20Asset({ + id: 'dai', + name: 'Dai', + network: '1', + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + usdPrice: 1, + icon: 'https://static.burnerfactory.com/icons/mcd.svg', + bridgeModes: ['erc-to-native-amb'] +}) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts index ecd5364b..8d5125c6 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/ERC677Asset.ts @@ -1,14 +1,12 @@ import { ERC20Asset } from '@burner-wallet/assets' +import { AssetConstructor } from '@burner-wallet/assets/Asset' import { ERC677_ABI, constants } from '../../utils' const BLOCK_LOOKBACK = 250 -interface ERC677Constructor { +interface ERC677Constructor extends AssetConstructor { abi?: object address: string - id: string - name: string - network: string } export default class ERC677Asset extends ERC20Asset { diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Etc.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Etc.ts index 2bbcc56d..7cc00131 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Etc.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/Etc.ts @@ -1,5 +1,5 @@ import NativeMediatorAsset from './NativeMediatorAsset' -import { isBridgeContract, HOME_NATIVE_TO_ERC_ABI } from '../../utils' +import { isVanillaBridgeContract, HOME_NATIVE_TO_ERC_ABI } from '../../utils' class EtcNativeAsset extends NativeMediatorAsset { constructor(props) { @@ -9,7 +9,7 @@ class EtcNativeAsset extends NativeMediatorAsset { async scanMediatorEvents(address, fromBlock, toBlock) { const web3 = this.getWeb3() const contract = new web3.eth.Contract(HOME_NATIVE_TO_ERC_ABI, this.mediatorAddress) - const listenToBridgeEvent = await isBridgeContract(contract) + const listenToBridgeEvent = await isVanillaBridgeContract(contract) if (listenToBridgeEvent && this.mediatorAddress != '') { const events = await contract.getPastEvents('AffirmationCompleted', { fromBlock, diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/MOON.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/MOON.ts new file mode 100644 index 00000000..c3fc08ba --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/MOON.ts @@ -0,0 +1,10 @@ +import BridgeableERC20Asset from './BridgeableERC20Asset' + +export default new BridgeableERC20Asset({ + id: 'moon', + name: 'MOON', + network: '4', + address: '0xDF82c9014F127243CE1305DFE54151647d74B27A', + icon: 'https://blockscout.com/poa/xdai/images/icons/moon.png', + bridgeModes: ['erc-to-erc-amb'] +}) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/NativeMediatorAsset.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/NativeMediatorAsset.ts index 58b5db57..3cee4915 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/NativeMediatorAsset.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/NativeMediatorAsset.ts @@ -1,12 +1,10 @@ import { NativeAsset } from '@burner-wallet/assets' import { Contract, EventData } from 'web3-eth-contract' import { MEDIATOR_ABI } from '../../utils' +import { AssetConstructor } from '@burner-wallet/assets/Asset' -interface NativeMediatorConstructor { +interface NativeMediatorConstructor extends AssetConstructor { mediatorAddress?: string - id: string - name: string - network: string } export default class NativeMediatorAsset extends NativeAsset { diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/qDai.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/qDai.ts new file mode 100644 index 00000000..3b5f6d53 --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/qDai.ts @@ -0,0 +1,9 @@ +import NativeMediatorAsset from './NativeMediatorAsset' + +export default new NativeMediatorAsset({ + id: 'qdai', + name: 'qDai', + network: '181', + usdPrice: 1, + mediatorAddress: '0xFEaB457D95D9990b7eb6c943c839258245541754' +}) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/xMOON.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/xMOON.ts new file mode 100644 index 00000000..81d86061 --- /dev/null +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/assets/xMOON.ts @@ -0,0 +1,9 @@ +import { default as ERC677Asset } from './ERC677Asset' + +export default new ERC677Asset({ + id: 'xmoon', + name: 'xMOON', + network: '100', + address: '0x1e16aa4Df73d29C029d94CeDa3e3114EC191E25A', + icon: 'https://blockscout.com/poa/xdai/images/icons/moon.png' +}) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/gateways/TokenBridgeGateway.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/gateways/TokenBridgeGateway.ts index 302786a4..1b9c2726 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/gateways/TokenBridgeGateway.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/gateways/TokenBridgeGateway.ts @@ -9,7 +9,8 @@ export default class TokenBridgeGateway extends Gateway { this.providerStrings = { '61': `https://www.ethercluster.com/etc`, '77': 'https://sokol.poa.network', - '99': 'https://core.poa.network' + '99': 'https://core.poa.network', + '181': 'https://quorum-rpc.tokenbridge.net' } this.providers = {} } @@ -19,7 +20,7 @@ export default class TokenBridgeGateway extends Gateway { } getNetworks() { - return ['61', '77', '99'] + return ['61', '77', '99', '181'] } _provider(network) { diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts index 90af2375..ccf990e9 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/burner-wallet/index.ts @@ -1,6 +1,10 @@ export { default as sPOA } from './assets/sPOA' export { default as Etc } from './assets/Etc' export { default as Wetc } from './assets/Wetc' +export { default as Dai } from './assets/Dai' +export { default as qDai } from './assets/qDai' +export { default as MOON } from './assets/MOON' +export { default as xMOON } from './assets/xMOON' export { default as ERC677Asset } from './assets/ERC677Asset' export { default as BridgeableERC20Asset } from './assets/BridgeableERC20Asset' export { default as NativeMediatorAsset } from './assets/NativeMediatorAsset' diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts index f43b67b1..933653e6 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/index.ts @@ -5,8 +5,12 @@ export { sPOA, Etc, Wetc, + qDai, + Dai, + MOON, + xMOON, TokenBridgeGateway, Mediator, MediatorErcToNative } from './burner-wallet' -export { WETCBridge } from './wetc-bridge' +export { WETCBridge, QDAIBridge, MOONBridge } from './bridges' diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts index f52a578e..1682f149 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/abis/Mediator.ts @@ -52,5 +52,19 @@ export default [ payable: false, stateMutability: 'nonpayable', type: 'function' + }, + { + constant: true, + inputs: [], + name: 'getBridgeMode', + outputs: [ + { + name: '', + type: 'bytes4' + } + ], + payable: false, + stateMutability: 'pure', + type: 'function' } ] diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts index dad1b86c..4d92f381 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/index.ts @@ -1,4 +1,4 @@ -export { constants, wait, waitForEvent, isBridgeContract } from './utils' +export { constants, wait, waitForEvent, isVanillaBridgeContract, isBridgeContract } from './utils' export { ERC677_ABI, FOREIGN_NATIVE_TO_ERC_ABI, diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts index 766087da..e6b6a45e 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/src/utils/utils.ts @@ -34,7 +34,7 @@ export const waitForEvent = async (web3, contract: Contract, event: string, call } } -export const isBridgeContract = async (contract: Contract): Promise => { +export const isVanillaBridgeContract = async (contract: Contract): Promise => { try { await contract.methods.deployedAtBlock().call() return true @@ -42,3 +42,15 @@ export const isBridgeContract = async (contract: Contract): Promise => return false } } + +export const isBridgeContract = async (contract: Contract, allowedModes?: string[]): Promise => { + try { + const mode = await contract.methods.getBridgeMode().call() + if (typeof allowedModes === 'undefined') { + return true + } + return allowedModes.includes(mode) + } catch (e) { + return false + } +} diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/src/wetc-bridge/index.ts b/burner-wallet-plugin/tokenbridge-bw-exchange/src/wetc-bridge/index.ts deleted file mode 100644 index e9d04fa9..00000000 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/src/wetc-bridge/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as WETCBridge } from './WETCBridge' From 4c44aa5fcd12f5ce4f9384f2a124103cdf4eb3c6 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 9 Jul 2020 07:20:49 -0300 Subject: [PATCH 03/12] Add Alm unit tests (#388) --- alm/src/App.test.tsx | 5 - alm/src/utils/__tests__/contracts.test.ts | 469 ++++++++++++++++++ alm/src/utils/__tests__/explorer.test.ts | 155 ++++++ .../__tests__/getConfirmationsForTx.test.ts | 437 ++++++++++++++++ .../__tests__/getFinalizationEvent.test.ts | 303 +++++++++++ alm/src/utils/explorer.ts | 35 +- alm/src/utils/getConfirmationsForTx.ts | 172 +------ alm/src/utils/validatorConfirmationHelpers.ts | 172 +++++++ 8 files changed, 1558 insertions(+), 190 deletions(-) delete mode 100644 alm/src/App.test.tsx create mode 100644 alm/src/utils/__tests__/contracts.test.ts create mode 100644 alm/src/utils/__tests__/explorer.test.ts create mode 100644 alm/src/utils/__tests__/getConfirmationsForTx.test.ts create mode 100644 alm/src/utils/__tests__/getFinalizationEvent.test.ts create mode 100644 alm/src/utils/validatorConfirmationHelpers.ts diff --git a/alm/src/App.test.tsx b/alm/src/App.test.tsx deleted file mode 100644 index fe49a358..00000000 --- a/alm/src/App.test.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' - -test('renders learn react link', () => { - // Removed basic test from setup. Keeping this so CI passes until we add unit tests. -}) diff --git a/alm/src/utils/__tests__/contracts.test.ts b/alm/src/utils/__tests__/contracts.test.ts new file mode 100644 index 00000000..7296658d --- /dev/null +++ b/alm/src/utils/__tests__/contracts.test.ts @@ -0,0 +1,469 @@ +import 'jest' +import { getRequiredBlockConfirmations, getRequiredSignatures, getValidatorList } from '../contract' +import { Contract } from 'web3-eth-contract' +import { SnapshotProvider } from '../../services/SnapshotProvider' + +describe('getRequiredBlockConfirmations', () => { + const methodsBuilder = (value: string) => ({ + requiredBlockConfirmations: () => { + return { + call: () => { + return value + } + } + } + }) + + test('Should call requiredBlockConfirmations method if no events present', async () => { + const contract = ({ + getPastEvents: () => { + return [] + }, + methods: methodsBuilder('1') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider) + + expect(result).toEqual(1) + }) + test('Should not call to get events if block number was included in the snapshot', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []), + methods: methodsBuilder('3') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [ + { + blockNumber: 8, + returnValues: { + requiredBlockConfirmations: '1' + } + } + ] + }, + snapshotBlockNumber: () => { + return 15 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider) + + expect(result).toEqual(1) + expect(contract.getPastEvents).toBeCalledTimes(0) + }) + test('Should call to get events if block number was not included in the snapshot', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 9, + returnValues: { + requiredBlockConfirmations: '2' + } + } + ]), + methods: methodsBuilder('3') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [ + { + blockNumber: 8, + returnValues: { + requiredBlockConfirmations: '1' + } + } + ] + }, + snapshotBlockNumber: () => { + return 8 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider) + + expect(result).toEqual(2) + expect(contract.getPastEvents).toBeCalledTimes(1) + expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredBlockConfirmationChanged', { + fromBlock: 9, + toBlock: 10 + }) + }) + test('Should use the most updated event', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 9, + returnValues: { + requiredBlockConfirmations: '2' + } + }, + { + blockNumber: 11, + returnValues: { + requiredBlockConfirmations: '3' + } + } + ]), + methods: methodsBuilder('3') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 11 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 15, snapshotProvider) + + expect(result).toEqual(3) + expect(contract.getPastEvents).toBeCalledTimes(1) + expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredBlockConfirmationChanged', { + fromBlock: 12, + toBlock: 15 + }) + }) +}) +describe('getRequiredSignatures', () => { + test('Should not call to get events if block number was included in the snapshot', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []) + } as unknown) as Contract + + const snapshotProvider = ({ + requiredSignaturesEvents: () => { + return [ + { + blockNumber: 7, + returnValues: { + requiredSignatures: '1' + } + }, + { + blockNumber: 8, + returnValues: { + requiredSignatures: '2' + } + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredSignatures(contract, 10, snapshotProvider) + + expect(result).toEqual(2) + expect(contract.getPastEvents).toBeCalledTimes(0) + }) + test('Should call to get events if block number is higher than the snapshot block number', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 15, + returnValues: { + requiredSignatures: '3' + } + } + ]) + } as unknown) as Contract + + const snapshotProvider = ({ + requiredSignaturesEvents: () => { + return [ + { + blockNumber: 7, + returnValues: { + requiredSignatures: '1' + } + }, + { + blockNumber: 8, + returnValues: { + requiredSignatures: '2' + } + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredSignatures(contract, 20, snapshotProvider) + + expect(result).toEqual(3) + expect(contract.getPastEvents).toBeCalledTimes(1) + expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredSignaturesChanged', { + fromBlock: 11, + toBlock: 20 + }) + }) + test('Should use the most updated event before the block number', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 15, + returnValues: { + requiredSignatures: '4' + } + } + ]) + } as unknown) as Contract + + const snapshotProvider = ({ + requiredSignaturesEvents: () => { + return [ + { + blockNumber: 5, + returnValues: { + requiredSignatures: '1' + } + }, + { + blockNumber: 6, + returnValues: { + requiredSignatures: '2' + } + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredSignatures(contract, 7, snapshotProvider) + + expect(result).toEqual(2) + expect(contract.getPastEvents).toBeCalledTimes(0) + }) +}) +describe('getValidatorList', () => { + const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' + const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f' + const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' + const methodsBuilder = (value: string[]) => ({ + validatorList: () => { + return { + call: () => { + return value + } + } + } + }) + test('Should return the current validator list if no events found', async () => { + const currentValidators = [validator1, validator2, validator3] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 20, snapshotProvider) + + expect(list.length).toEqual(3) + expect(list).toEqual(expect.arrayContaining(currentValidators)) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 20 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 20 + }) + }) + 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(() => []), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorAdded' + } + ] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 5, snapshotProvider) + + expect(list.length).toEqual(2) + expect(list).toEqual(expect.arrayContaining([validator1, validator2])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 11 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 11 + }) + }) + 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 => { + if (event === 'ValidatorAdded') { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorAdded' + } + ] + } else { + return [] + } + }), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 15, snapshotProvider) + + expect(list.length).toEqual(2) + expect(list).toEqual(expect.arrayContaining([validator1, validator2])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 15 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 15 + }) + }) + test('If validator was removed later from snapshot it should include it', async () => { + const currentValidators = [validator1, validator2] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorRemoved' + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 5, snapshotProvider) + + expect(list.length).toEqual(3) + expect(list).toEqual(expect.arrayContaining([validator1, validator2, validator3])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 11 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 11 + }) + }) + test('If validator was removed later from chain it should include it', async () => { + const currentValidators = [validator1, validator2] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(event => { + if (event === 'ValidatorRemoved') { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorRemoved' + } + ] + } else { + return [] + } + }), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 15, snapshotProvider) + + expect(list.length).toEqual(3) + expect(list).toEqual(expect.arrayContaining([validator1, validator2, validator3])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 15 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 15 + }) + }) +}) diff --git a/alm/src/utils/__tests__/explorer.test.ts b/alm/src/utils/__tests__/explorer.test.ts new file mode 100644 index 00000000..d4743e6d --- /dev/null +++ b/alm/src/utils/__tests__/explorer.test.ts @@ -0,0 +1,155 @@ +import 'jest' +import { + getFailedTransactions, + getSuccessTransactions, + filterValidatorSignatureTransaction, + getExecutionFailedTransactionForMessage, + APITransaction, + getValidatorPendingTransactionsForMessage, + getExecutionPendingTransactionsForMessage +} from '../explorer' +import { EXECUTE_AFFIRMATION_HASH, EXECUTE_SIGNATURES_HASH, SUBMIT_SIGNATURE_HASH } from '../../config/constants' + +const messageData = '0x123456' +const OTHER_HASH = 'aabbccdd' +const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560' +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 fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + const result = await getFailedTransactions('', '', 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 fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + const result = await getSuccessTransactions('', '', 0, 1, '', fetchAccountTransactions) + expect(result.length).toEqual(2) + }) +}) +describe('filterValidatorSignatureTransaction', () => { + test('should return submit signatures related transaction', () => { + const transactions = [ + { input: `0x${SUBMIT_SIGNATURE_HASH}112233` }, + { input: `0x${SUBMIT_SIGNATURE_HASH}123456` }, + { input: `0x${OTHER_HASH}123456` }, + { input: `0x${OTHER_HASH}112233` } + ] as APITransaction[] + + const result = filterValidatorSignatureTransaction(transactions, messageData) + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${SUBMIT_SIGNATURE_HASH}123456` }) + }) + test('should return execute affirmation related transaction', () => { + const transactions = [ + { input: `0x${EXECUTE_AFFIRMATION_HASH}112233` }, + { input: `0x${EXECUTE_AFFIRMATION_HASH}123456` }, + { input: `0x${OTHER_HASH}123456` }, + { input: `0x${OTHER_HASH}112233` } + ] as APITransaction[] + + const result = filterValidatorSignatureTransaction(transactions, messageData) + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_AFFIRMATION_HASH}123456` }) + }) +}) +describe('getExecutionFailedTransactionForMessage', () => { + test('should return failed transaction related to signatures execution', async () => { + const transactions = [ + { input: `0x${EXECUTE_SIGNATURES_HASH}112233` }, + { input: `0x${EXECUTE_SIGNATURES_HASH}123456` }, + { input: `0x${OTHER_HASH}123456` }, + { input: `0x${OTHER_HASH}112233` } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getExecutionFailedTransactionForMessage( + { + account: '', + to: '', + messageData, + startTimestamp: 0, + endTimestamp: 1 + }, + fetchAccountTransactions + ) + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_SIGNATURES_HASH}123456` }) + }) +}) +describe('getValidatorPendingTransactionsForMessage', () => { + test('should return pending transaction for submit signature transaction', async () => { + const transactions = [ + { input: `0x${SUBMIT_SIGNATURE_HASH}112233`, to: bridgeAddress }, + { input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: bridgeAddress }, + { input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: otherAddress }, + { input: `0x${OTHER_HASH}123456`, to: bridgeAddress }, + { input: `0x${OTHER_HASH}112233`, to: bridgeAddress } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getValidatorPendingTransactionsForMessage( + { + account: '', + to: bridgeAddress, + messageData + }, + fetchAccountTransactions + ) + + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: bridgeAddress }) + }) + test('should return pending transaction for execute affirmation transaction', async () => { + const transactions = [ + { input: `0x${EXECUTE_AFFIRMATION_HASH}112233`, to: bridgeAddress }, + { input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: bridgeAddress }, + { input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: otherAddress }, + { input: `0x${OTHER_HASH}123456`, to: bridgeAddress }, + { input: `0x${OTHER_HASH}112233`, to: bridgeAddress } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getValidatorPendingTransactionsForMessage( + { + account: '', + to: bridgeAddress, + messageData + }, + fetchAccountTransactions + ) + + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: bridgeAddress }) + }) +}) +describe('getExecutionPendingTransactionsForMessage', () => { + test('should return pending transaction for signatures execution transaction', async () => { + const transactions = [ + { input: `0x${EXECUTE_SIGNATURES_HASH}112233`, to: bridgeAddress }, + { input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: bridgeAddress }, + { input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: otherAddress }, + { input: `0x${OTHER_HASH}123456`, to: bridgeAddress }, + { input: `0x${OTHER_HASH}112233`, to: bridgeAddress } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getExecutionPendingTransactionsForMessage( + { + account: '', + to: bridgeAddress, + messageData + }, + fetchAccountTransactions + ) + + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: bridgeAddress }) + }) +}) diff --git a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts new file mode 100644 index 00000000..66080d2c --- /dev/null +++ b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts @@ -0,0 +1,437 @@ +import 'jest' +import { getConfirmationsForTx } from '../getConfirmationsForTx' +import * as helpers from '../validatorConfirmationHelpers' +import Web3 from 'web3' +import { Contract } from 'web3-eth-contract' +import { APIPendingTransaction, APITransaction } from '../explorer' +import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants' +import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations' + +jest.mock('../validatorConfirmationHelpers') + +const getValidatorSuccessTransaction = helpers.getValidatorSuccessTransaction as jest.Mock +const getValidatorConfirmation = helpers.getValidatorConfirmation as jest.Mock +const getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock +const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock + +const messageData = '0x111111111' +const web3 = { + utils: { + soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}` + } +} as Web3 +const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' +const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f' +const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' +const validatorList = [validator1, validator2, validator3] +const bridgeContract = {} as Contract +const confirmationContractMethod = () => {} +const requiredSignatures = 2 +const waitingBlocksResolved = true +let subscriptions: Array = [] +const timestamp = 1594045859 +const getFailedTransactions = (): Promise => Promise.resolve([]) +const getPendingTransactions = (): Promise => Promise.resolve([]) +const getSuccessTransactions = (): Promise => Promise.resolve([]) + +const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) +} + +beforeEach(() => { + // Clear all instances and calls to constructor and all methods: + getValidatorSuccessTransaction.mockClear() + getValidatorConfirmation.mockClear() + getValidatorFailedTransaction.mockClear() + getValidatorPendingTransaction.mockClear() + subscriptions = [] +}) +describe('getConfirmationsForTx', () => { + test('should set validator confirmations status when signatures collected even if validator transactions not found yet and set remaining validator as not required', async () => { + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: '', + timestamp: 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(1) + + expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + }) + test('should set validator confirmations status when signatures not collected even if validator transactions not found yet', async () => { + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: '', + timestamp: 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(0) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(0) + }) + test('should set validator confirmations status, validator transactions and not retry', async () => { + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator !== validator3 ? '0x123' : '', + timestamp: validatorData.validator !== validator3 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(1) + + expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + }) + test('should look for failed and pending transactions for not confirmed validators', async () => { + // Validator1 success + // Validator2 failed + // Validator3 Pending + + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator === validator1 ? '0x123' : '', + timestamp: validatorData.validator === validator1 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator2 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator2 ? '0x123' : '', + timestamp: validatorData.validator === validator2 ? 123 : 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator3 + ? VALIDATOR_CONFIRMATION_STATUS.PENDING + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator3 ? '0x123' : '', + timestamp: validatorData.validator === validator3 ? 123 : 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(0) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(1) + + expect(setResult.mock.calls[0][0]).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.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.PENDING, txHash: '0x123', timestamp: 123 } + ]) + ) + }) + test('should set as failed if enough signatures failed', async () => { + // Validator1 success + // Validator2 failed + // Validator3 failed + + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator === validator1 ? '0x123' : '', + timestamp: validatorData.validator === validator1 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator !== validator1 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator !== validator1 ? '0x123' : '', + timestamp: validatorData.validator !== validator1 ? 123 : 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(0) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(1) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]).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.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.FAILED, txHash: '0x123', timestamp: 123 } + ]) + ) + }) +}) diff --git a/alm/src/utils/__tests__/getFinalizationEvent.test.ts b/alm/src/utils/__tests__/getFinalizationEvent.test.ts new file mode 100644 index 00000000..506533ed --- /dev/null +++ b/alm/src/utils/__tests__/getFinalizationEvent.test.ts @@ -0,0 +1,303 @@ +import 'jest' +import { Contract, EventData } from 'web3-eth-contract' +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' + +const web3 = ({ + eth: { + getTransactionReceipt: () => ({ + from: validator1 + }), + getBlock: () => ({ timestamp }) + }, + utils: { + toChecksumAddress: (a: string) => a + } +} as unknown) as Web3 +const waitingBlocksResolved = true +const message = { + id: '0x123', + data: '0x123456789' +} +const interval = 10000 +let subscriptions: Array = [] + +const event = { + transactionHash: txHash, + blockNumber: 5523145, + returnValues: { + status: true + } +} + +const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560' + +const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) +} + +beforeEach(() => { + subscriptions = [] +}) +describe('getFinalizationEvent', () => { + test('should get finalization event and not try to get failed or pending transactions', async () => { + const contract = ({ + getPastEvents: () => { + return [event] + } + } as unknown) as Contract + + const collectedSignaturesEvent = null + const setResult = jest.fn() + const getFailedExecution = jest.fn() + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn() + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(1) + expect(setResult.mock.calls[0][0]).toEqual({ + validator: validator1, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash, + timestamp, + executionResult: true + }) + + expect(getFailedExecution).toBeCalledTimes(0) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(0) + expect(setPendingExecution).toBeCalledTimes(0) + }) + 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: () => { + return [] + } + } as unknown) as Contract + + const collectedSignaturesEvent = null + const setResult = jest.fn() + const getFailedExecution = jest.fn() + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn() + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(0) + + expect(getFailedExecution).toBeCalledTimes(0) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(0) + expect(setPendingExecution).toBeCalledTimes(0) + }) + test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => { + const contract = ({ + getPastEvents: () => { + return [] + }, + options: { + address: bridgeAddress + } + } as unknown) as Contract + + const collectedSignaturesEvent = ({ + returnValues: { + authorityResponsibleForRelay: validator1 + } + } as unknown) as EventData + const setResult = jest.fn() + const getFailedExecution = jest.fn().mockResolvedValue([]) + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn().mockResolvedValue([]) + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(0) + + expect(getFailedExecution).toBeCalledTimes(1) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(1) + expect(setPendingExecution).toBeCalledTimes(0) + }) + 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: () => { + return [] + }, + options: { + address: bridgeAddress + } + } as unknown) as Contract + + const collectedSignaturesEvent = ({ + returnValues: { + authorityResponsibleForRelay: validator1 + } + } as unknown) as EventData + const setResult = jest.fn() + const getFailedExecution = jest.fn().mockResolvedValue([]) + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn().mockResolvedValue([{ hash: txHash }]) + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(1) + expect(setResult.mock.calls[0][0]).toEqual({ + validator: validator1, + status: VALIDATOR_CONFIRMATION_STATUS.PENDING, + txHash, + timestamp: expect.any(Number), + executionResult: false + }) + + expect(getFailedExecution).toBeCalledTimes(0) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(1) + expect(setPendingExecution).toBeCalledTimes(1) + }) + test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => { + const contract = ({ + getPastEvents: () => { + return [] + }, + options: { + address: bridgeAddress + } + } as unknown) as Contract + + const collectedSignaturesEvent = ({ + returnValues: { + authorityResponsibleForRelay: validator1 + } + } as unknown) as EventData + const setResult = jest.fn() + const getFailedExecution = jest.fn().mockResolvedValue([{ timeStamp: timestamp, hash: txHash }]) + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn().mockResolvedValue([]) + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(1) + expect(setResult.mock.calls[0][0]).toEqual({ + validator: validator1, + status: VALIDATOR_CONFIRMATION_STATUS.FAILED, + txHash, + timestamp: expect.any(Number), + executionResult: false + }) + + expect(getFailedExecution).toBeCalledTimes(1) + expect(setFailedExecution).toBeCalledTimes(1) + + expect(getPendingExecution).toBeCalledTimes(1) + expect(setPendingExecution).toBeCalledTimes(0) + }) +}) diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts index d57bf926..5b354895 100644 --- a/alm/src/utils/explorer.ts +++ b/alm/src/utils/explorer.ts @@ -217,14 +217,11 @@ export const getValidatorSuccessTransactionsForMessage = async ({ return filterValidatorSignatureTransaction(transactions, messageData) } -export const getExecutionFailedTransactionForMessage = async ({ - account, - to, - messageData, - startTimestamp, - endTimestamp -}: GetFailedTransactionParams): Promise => { - const failedTransactions = await getFailedTransactions( +export const getExecutionFailedTransactionForMessage = async ( + { account, to, messageData, startTimestamp, endTimestamp }: GetFailedTransactionParams, + getFailedTransactionsMethod = getFailedTransactions +): Promise => { + const failedTransactions = await getFailedTransactionsMethod( account, to, startTimestamp, @@ -237,12 +234,11 @@ export const getExecutionFailedTransactionForMessage = async ({ return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue)) } -export const getValidatorPendingTransactionsForMessage = async ({ - account, - to, - messageData -}: GetPendingTransactionParams): Promise => { - const pendingTransactions = await fetchPendingTransactions({ +export const getValidatorPendingTransactionsForMessage = async ( + { account, to, messageData }: GetPendingTransactionParams, + fetchPendingTransactionsMethod = fetchPendingTransactions +): Promise => { + const pendingTransactions = await fetchPendingTransactionsMethod({ account, api: HOME_EXPLORER_API }) @@ -258,12 +254,11 @@ export const getValidatorPendingTransactionsForMessage = async ({ ) } -export const getExecutionPendingTransactionsForMessage = async ({ - account, - to, - messageData -}: GetPendingTransactionParams): Promise => { - const pendingTransactions = await fetchPendingTransactions({ +export const getExecutionPendingTransactionsForMessage = async ( + { account, to, messageData }: GetPendingTransactionParams, + fetchPendingTransactionsMethod = fetchPendingTransactions +): Promise => { + const pendingTransactions = await fetchPendingTransactionsMethod({ account, api: FOREIGN_EXPLORER_API }) diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index d9cc3d62..7e83cd1b 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -1,176 +1,18 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' -import validatorsCache from '../services/ValidatorsCache' -import { - CACHE_KEY_FAILED, - CACHE_KEY_SUCCESS, - HOME_RPC_POLLING_INTERVAL, - ONE_DAY_TIMESTAMP, - VALIDATOR_CONFIRMATION_STATUS -} from '../config/constants' +import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { GetFailedTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer' -import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' - -export const getValidatorConfirmation = ( - web3: Web3, - hashMsg: string, - bridgeContract: Contract, - confirmationContractMethod: Function -) => async (validator: string): Promise => { - const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) - - const signatureFromCache = validatorsCache.get(hashSenderMsg) - if (signatureFromCache) { - return { - validator, - status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS - } - } - - const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) - const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - - // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change - if (confirmed) { - validatorsCache.set(hashSenderMsg, confirmed) - } - - return { - validator, - status - } -} - -export const getValidatorSuccessTransaction = ( - bridgeContract: Contract, - messageData: string, - timestamp: number, - getSuccessTransactions: (args: GetFailedTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { - const { validator } = validatorData - const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` - const fromCache = validatorsCache.getData(validatorCacheKey) - - if (fromCache && fromCache.txHash) { - return fromCache - } - - const transactions = await getSuccessTransactions({ - account: validatorData.validator, - to: bridgeContract.options.address, - messageData, - startTimestamp: timestamp, - endTimestamp: timestamp + ONE_DAY_TIMESTAMP - }) - - let txHashTimestamp = 0 - let txHash = '' - const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS - - if (transactions.length > 0) { - const tx = transactions[0] - txHashTimestamp = parseInt(tx.timeStamp) - txHash = tx.hash - - // cache the result - validatorsCache.setData(validatorCacheKey, { - validator, - status, - txHash, - timestamp: txHashTimestamp - }) - } - - return { - validator, - status, - txHash, - timestamp: txHashTimestamp - } -} - -export const getValidatorFailedTransaction = ( - bridgeContract: Contract, - messageData: string, - timestamp: number, - getFailedTransactions: (args: GetFailedTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { - const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` - const failedFromCache = validatorsCache.getData(validatorCacheKey) - - if (failedFromCache && failedFromCache.txHash) { - return failedFromCache - } - - const failedTransactions = await getFailedTransactions({ - account: validatorData.validator, - to: bridgeContract.options.address, - messageData, - startTimestamp: timestamp, - endTimestamp: timestamp + ONE_DAY_TIMESTAMP - }) - const newStatus = - failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - - let txHashTimestamp = 0 - let txHash = '' - // If validator signature failed, we cache the result to avoid doing future requests for a result that won't change - if (failedTransactions.length > 0) { - const failedTx = failedTransactions[0] - txHashTimestamp = parseInt(failedTx.timeStamp) - txHash = failedTx.hash - - validatorsCache.setData(validatorCacheKey, { - validator: validatorData.validator, - status: newStatus, - txHash, - timestamp: txHashTimestamp - }) - } - - return { - validator: validatorData.validator, - status: newStatus, - txHash, - timestamp: txHashTimestamp - } -} - -export const getValidatorPendingTransaction = ( - bridgeContract: Contract, - messageData: string, - getPendingTransactions: (args: GetPendingTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { - const failedTransactions = await getPendingTransactions({ - account: validatorData.validator, - to: bridgeContract.options.address, - messageData - }) - - const newStatus = - failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - - let timestamp = 0 - let txHash = '' - - if (failedTransactions.length > 0) { - const failedTx = failedTransactions[0] - timestamp = Math.floor(new Date().getTime() / 1000.0) - txHash = failedTx.hash - } - - return { - validator: validatorData.validator, - status: newStatus, - txHash, - timestamp - } -} +import { + getValidatorConfirmation, + getValidatorFailedTransaction, + getValidatorPendingTransaction, + getValidatorSuccessTransaction +} from './validatorConfirmationHelpers' export const getConfirmationsForTx = async ( messageData: string, diff --git a/alm/src/utils/validatorConfirmationHelpers.ts b/alm/src/utils/validatorConfirmationHelpers.ts new file mode 100644 index 00000000..0b5c65e6 --- /dev/null +++ b/alm/src/utils/validatorConfirmationHelpers.ts @@ -0,0 +1,172 @@ +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' + +export const getValidatorConfirmation = ( + web3: Web3, + hashMsg: string, + bridgeContract: Contract, + confirmationContractMethod: Function +) => async (validator: string): Promise => { + const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) + + const signatureFromCache = validatorsCache.get(hashSenderMsg) + if (signatureFromCache) { + return { + validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS + } + } + + const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) + const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change + if (confirmed) { + validatorsCache.set(hashSenderMsg, confirmed) + } + + return { + validator, + status + } +} + +export const getValidatorSuccessTransaction = ( + bridgeContract: Contract, + messageData: string, + timestamp: number, + getSuccessTransactions: (args: GetFailedTransactionParams) => Promise +) => async (validatorData: BasicConfirmationParam): Promise => { + const { validator } = validatorData + const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` + const fromCache = validatorsCache.getData(validatorCacheKey) + + if (fromCache && fromCache.txHash) { + return fromCache + } + + const transactions = await getSuccessTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData, + startTimestamp: timestamp, + endTimestamp: timestamp + ONE_DAY_TIMESTAMP + }) + + let txHashTimestamp = 0 + let txHash = '' + const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS + + if (transactions.length > 0) { + const tx = transactions[0] + txHashTimestamp = parseInt(tx.timeStamp) + txHash = tx.hash + + // cache the result + validatorsCache.setData(validatorCacheKey, { + validator, + status, + txHash, + timestamp: txHashTimestamp + }) + } + + return { + validator, + status, + txHash, + timestamp: txHashTimestamp + } +} + +export const getValidatorFailedTransaction = ( + bridgeContract: Contract, + messageData: string, + timestamp: number, + getFailedTransactions: (args: GetFailedTransactionParams) => Promise +) => async (validatorData: BasicConfirmationParam): Promise => { + const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` + const failedFromCache = validatorsCache.getData(validatorCacheKey) + + if (failedFromCache && failedFromCache.txHash) { + return failedFromCache + } + + const failedTransactions = await getFailedTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData, + startTimestamp: timestamp, + endTimestamp: timestamp + ONE_DAY_TIMESTAMP + }) + const newStatus = + failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + let txHashTimestamp = 0 + let txHash = '' + // If validator signature failed, we cache the result to avoid doing future requests for a result that won't change + if (failedTransactions.length > 0) { + const failedTx = failedTransactions[0] + txHashTimestamp = parseInt(failedTx.timeStamp) + txHash = failedTx.hash + + validatorsCache.setData(validatorCacheKey, { + validator: validatorData.validator, + status: newStatus, + txHash, + timestamp: txHashTimestamp + }) + } + + return { + validator: validatorData.validator, + status: newStatus, + txHash, + timestamp: txHashTimestamp + } +} + +export const getValidatorPendingTransaction = ( + bridgeContract: Contract, + messageData: string, + getPendingTransactions: (args: GetPendingTransactionParams) => Promise +) => async (validatorData: BasicConfirmationParam): Promise => { + const failedTransactions = await getPendingTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData + }) + + const newStatus = + failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + let timestamp = 0 + let txHash = '' + + if (failedTransactions.length > 0) { + const failedTx = failedTransactions[0] + timestamp = Math.floor(new Date().getTime() / 1000.0) + txHash = failedTx.hash + } + + return { + validator: validatorData.validator, + status: newStatus, + txHash, + timestamp + } +} From 9e6833eb403e77c52cf4533a13c603275291294e Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Fri, 10 Jul 2020 15:28:03 +0700 Subject: [PATCH 04/12] Update bw plugin README and version (#389) --- .../tokenbridge-bw-exchange/README.md | 52 ++++++++++++++++--- .../tokenbridge-bw-exchange/package.json | 2 +- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/README.md b/burner-wallet-plugin/tokenbridge-bw-exchange/README.md index a5317599..f90ec8f1 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/README.md +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/README.md @@ -4,12 +4,16 @@ This plugin defines a Bridge trading pair to be used in the Exchange Plugin. Bridge trading pairs and assets supported: * ETC - WETC Bridge +* MOON - xMOON Bridge +* DAI - qDAI Bridge (For qDAI Bridge, it's necessary to use a custom DAI token from this repo instead of the DAI asset provided by burner-wallet) It also provides some generic resources that can be used and extended: -* **ERC677Asset** - A representation of an Erc677 token +* **ERC677Asset** - A representation of an Erc677 token. +* **BridgeableERC20Asset** - A representation of Erc20 token with a possibility of bridging it via a call to `relayTokens`. * **NativeMediatorAsset** - Represents a native token that interacts with a Mediator extension. * **Mediator Pair** - Represents an Exchange Pair that interacts with mediators extensions. -* **TokenBridgeGateway** - A gateway to operate with ETC, POA Sokol and POA Core networks. +* **MediatorErcToNative Pair** - Represents a modified Mediator Pair that interacts with a tokenbridge erc-to-native mediators contracts. +* **TokenBridgeGateway** - A gateway to operate with ETC, POA Sokol, POA Core and qDAI networks. ### Install package ``` @@ -18,13 +22,22 @@ yarn add @poanet/tokenbridge-bw-exchange ### Usage +#### WETCBridge example +In this example, we use `TokenBridgeGateway` for connecting to the Ethereum Classic and `InfuraGateway` for connecting to the Ethereum Mainnet. + +`WETCBridge` operates with two assets: `WETC` (Ethereum Mainnet) and `ETC` (Ethereum Classic), they should be added in the assets list. + ```javascript -import { Etc, Wetc, EtcGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange' +import BurnerCore from '@burner-wallet/core' +import Exchange from '@burner-wallet/exchange' +import { LocalSigner } from '@burner-wallet/core/signers' +import { Etc, Wetc, TokenBridgeGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange' +import { InfuraGateway } from '@burner-wallet/core/gateways' const core = new BurnerCore({ - ... - gateways: [new EtcGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)], - assets: [Etc, Wetc] + signers: [new LocalSigner()], + gateways: [new TokenBridgeGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)], + assets: [Wetc, Etc] }) const exchange = new Exchange({ @@ -32,6 +45,33 @@ const exchange = new Exchange({ }) ``` +### Using several exchanges simultaneously +In this example, we use `TokenBridgeGateway` for connecting to the qDAI chain, `XDaiGatewai` for connecting to the xDAI chain and `InfuraGateway` for connecting to the Ethereum Mainnet and Rinkeby Network. + +`QDAIBridge` operates with two assets: `qDAI` (qDAI chain) and `DAI` (Ethereum Mainnet). Note that we use a custom DAI token from the `@poanet/tokenbridge-bw-exchange`, this is necessary for allowing bridge operations on this token. + +`MOONBridge` operates with two assets: `MOON` (Rinkeby network) and `xMOON` (xDAI chain). + +All four assets should be added to the assets list. + +```javascript +import BurnerCore from '@burner-wallet/core' +import Exchange from '@burner-wallet/exchange' +import { LocalSigner } from '@burner-wallet/core/signers' +import { InfuraGateway, XDaiGateway } from '@burner-wallet/core/gateways' +import { Dai, qDai, MOON, xMOON, TokenBridgeGateway, QDAIBridge, MOONBridge } from '@poanet/tokenbridge-bw-exchange' + +const core = new BurnerCore({ + signers: [new LocalSigner()], + gateways: [new TokenBridgeGateway(), new XDaiGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)], + assets: [Dai, qDai, MOON, xMOON] +}) + +const exchange = new Exchange({ + pairs: [new QDAIBridge(), new MOONBridge()] +}) +``` + This is how the exchange plugin will look like: ![exchange-wetc](https://user-images.githubusercontent.com/4614574/80991095-e40d0900-8e0d-11ea-9915-1b4e4a052694.png) diff --git a/burner-wallet-plugin/tokenbridge-bw-exchange/package.json b/burner-wallet-plugin/tokenbridge-bw-exchange/package.json index 0f0bbc48..63dfa012 100644 --- a/burner-wallet-plugin/tokenbridge-bw-exchange/package.json +++ b/burner-wallet-plugin/tokenbridge-bw-exchange/package.json @@ -1,6 +1,6 @@ { "name": "@poanet/tokenbridge-bw-exchange", - "version": "1.0.0", + "version": "1.1.0", "license": "GPL-3.0", "main": "dist/index.js", "types": "dist/index.d.ts", From 4f6d53964f5f45493d3ea316b177b4235c44388a Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 13 Jul 2020 19:09:07 +0700 Subject: [PATCH 05/12] Possibility to send a tx through all provided RPC endpoints (#394) --- CONFIGURATION.md | 1 + deployment/roles/oracle/templates/.env.j2 | 3 ++ oracle/package.json | 1 - oracle/src/services/RpcUrlsManager.js | 21 +++++++----- oracle/src/tx/sendTx.js | 42 +++++++++++++---------- oracle/src/utils/utils.js | 8 ++++- oracle/test/utils.test.js | 33 +++++++++++++++++- 7 files changed, 79 insertions(+), 30 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index aec83c87..20251b9b 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -36,6 +36,7 @@ ORACLE_LOG_LEVEL | Set the level of details in the logs. | `trace` / `debug` / ` ORACLE_MAX_PROCESSING_TIME | The workers processes will be killed if this amount of time (in milliseconds) is elapsed before they finish processing. It is recommended to set this value to 4 times the value of the longest polling time (set with the `HOME_POLLING_INTERVAL` and `FOREIGN_POLLING_INTERVAL` variables). To disable this, set the time to 0. | integer ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY | The private key of the bridge validator used to sign confirmations before sending transactions to the bridge contracts. The validator account is calculated automatically from the private key. Every bridge instance (set of watchers and senders) must have its own unique private key. The specified private key is used to sign transactions on both sides of the bridge. | hexidecimal without "0x" ORACLE_VALIDATOR_ADDRESS | The public address of the bridge validator | hexidecimal with "0x" +ORACLE_TX_REDUNDANCY | If set to `true`, instructs oracle to send `eth_sendRawTransaction` requests through all available RPC urls defined in `COMMON_HOME_RPC_URL` and `COMMON_FOREIGN_RPC_URL` variables instead of using first available one ## UI configuration diff --git a/deployment/roles/oracle/templates/.env.j2 b/deployment/roles/oracle/templates/.env.j2 index f5c403ca..680ad692 100644 --- a/deployment/roles/oracle/templates/.env.j2 +++ b/deployment/roles/oracle/templates/.env.j2 @@ -47,6 +47,9 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }} ORACLE_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }} ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }} ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }} +{% if ORACLE_TX_REDUNDANCY | default('') != '' %} +ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }} +{% endif %} {% if ORACLE_HOME_START_BLOCK | default('') != '' %} ORACLE_HOME_START_BLOCK={{ ORACLE_HOME_START_BLOCK }} diff --git a/oracle/package.json b/oracle/package.json index 6a9d802f..2872aa6c 100644 --- a/oracle/package.json +++ b/oracle/package.json @@ -30,7 +30,6 @@ "dotenv": "^5.0.1", "http-list-provider": "0.0.5", "ioredis": "^3.2.2", - "lodash": "^4.17.10", "node-fetch": "^2.1.2", "pino": "^4.17.3", "pino-pretty": "^2.0.1", diff --git a/oracle/src/services/RpcUrlsManager.js b/oracle/src/services/RpcUrlsManager.js index 225ed303..7ee9e652 100644 --- a/oracle/src/services/RpcUrlsManager.js +++ b/oracle/src/services/RpcUrlsManager.js @@ -1,7 +1,7 @@ -const _ = require('lodash') const promiseRetry = require('promise-retry') const tryEach = require('../utils/tryEach') const { RETRY_CONFIG } = require('../utils/constants') +const { promiseAny } = require('../utils/utils') function RpcUrlsManager(homeUrls, foreignUrls) { if (!homeUrls) { @@ -15,19 +15,22 @@ function RpcUrlsManager(homeUrls, foreignUrls) { this.foreignUrls = foreignUrls.split(',') } -RpcUrlsManager.prototype.tryEach = async function(chain, f) { +RpcUrlsManager.prototype.tryEach = async function(chain, f, redundant = false) { if (chain !== 'home' && chain !== 'foreign') { throw new Error(`Invalid argument chain: '${chain}'`) } - // save homeUrls to avoid race condition - const urls = chain === 'home' ? _.cloneDeep(this.homeUrls) : _.cloneDeep(this.foreignUrls) + // save urls to avoid race condition + const urls = chain === 'home' ? [...this.homeUrls] : [...this.foreignUrls] - const [result, index] = await promiseRetry(retry => - tryEach(urls, f).catch(() => { - retry() - }, RETRY_CONFIG) - ) + if (redundant) { + // result from first responded node will be returned immediately + // remaining nodes will continue to retry queries in separate promises + // promiseAny will throw only if all urls reached max retry number + return promiseAny(urls.map(url => promiseRetry(retry => f(url).catch(retry), RETRY_CONFIG))) + } + + const [result, index] = await promiseRetry(retry => tryEach(urls, f).catch(retry), RETRY_CONFIG) if (index > 0) { // rotate urls diff --git a/oracle/src/tx/sendTx.js b/oracle/src/tx/sendTx.js index 6bdfdf45..55da4a4e 100644 --- a/oracle/src/tx/sendTx.js +++ b/oracle/src/tx/sendTx.js @@ -2,6 +2,8 @@ const Web3Utils = require('web3-utils') const fetch = require('node-fetch') const rpcUrlsManager = require('../services/getRpcUrlsManager') +const { ORACLE_TX_REDUNDANCY } = process.env + // eslint-disable-next-line consistent-return async function sendTx({ chain, privateKey, data, nonce, gasPrice, amount, gasLimit, to, chainId, web3 }) { const serializedTx = await web3.eth.accounts.signTransaction( @@ -26,27 +28,31 @@ async function sendTx({ chain, privateKey, data, nonce, gasPrice, amount, gasLim // eslint-disable-next-line consistent-return async function sendRawTx({ chain, params, method }) { - const result = await rpcUrlsManager.tryEach(chain, async url => { - // curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}' - const response = await fetch(url, { - headers: { - 'Content-type': 'application/json' - }, - method: 'POST', - body: JSON.stringify({ - jsonrpc: '2.0', - method, - params, - id: Math.floor(Math.random() * 100) + 1 + const result = await rpcUrlsManager.tryEach( + chain, + async url => { + // curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}' + const response = await fetch(url, { + headers: { + 'Content-type': 'application/json' + }, + method: 'POST', + body: JSON.stringify({ + jsonrpc: '2.0', + method, + params, + id: Math.floor(Math.random() * 100) + 1 + }) }) - }) - if (!response.ok) { - throw new Error(response.statusText) - } + if (!response.ok) { + throw new Error(response.statusText) + } - return response - }) + return response + }, + ORACLE_TX_REDUNDANCY === 'true' && method === 'eth_sendRawTransaction' + ) const json = await result.json() if (json.error) { diff --git a/oracle/src/utils/utils.js b/oracle/src/utils/utils.js index 56a655e6..eb3a197e 100644 --- a/oracle/src/utils/utils.js +++ b/oracle/src/utils/utils.js @@ -100,6 +100,11 @@ function nonceError(e) { ) } +// Promise.all rejects on the first rejected Promise or fulfills with the list of results +// inverted Promise.all fulfills with the first obtained result or rejects with the list of errors +const invert = p => new Promise((res, rej) => p.then(rej, res)) +const promiseAny = ps => invert(Promise.all(ps.map(invert))) + module.exports = { syncForEach, checkHTTPS, @@ -109,5 +114,6 @@ module.exports = { watchdog, privateKeyToAddress, nonceError, - getRetrySequence + getRetrySequence, + promiseAny } diff --git a/oracle/test/utils.test.js b/oracle/test/utils.test.js index 2742690a..494e6dca 100644 --- a/oracle/test/utils.test.js +++ b/oracle/test/utils.test.js @@ -3,9 +3,10 @@ const chai = require('chai') const chaiAsPromised = require('chai-as-promised') const BigNumber = require('bignumber.js') const proxyquire = require('proxyquire') -const { addExtraGas, syncForEach } = require('../src/utils/utils') +const { addExtraGas, syncForEach, promiseAny } = require('../src/utils/utils') chai.use(chaiAsPromised) +chai.should() const { expect } = chai describe('utils', () => { @@ -134,4 +135,34 @@ describe('utils', () => { }) }) }) + + describe('promiseAny', () => { + const f = x => new Promise((res, rej) => setTimeout(() => (x > 0 ? res : rej)(x), 10 * x)) + + it('should return first obtained result', async () => { + const array = [2, 1, 3] + const result = await promiseAny(array.map(f)) + + expect(result).to.equal(1) + }) + + it('should return first obtained result with one reject', async () => { + const array = [2, -1, 3] + const result = await promiseAny(array.map(f)) + + expect(result).to.equal(2) + }) + + it('should return first obtained result with several rejects', async () => { + const array = [2, -1, -3] + const result = await promiseAny(array.map(f)) + + expect(result).to.equal(2) + }) + + it('should reject if all functions failed', async () => { + const array = [-2, -1, -3] + await promiseAny(array.map(f)).should.be.rejected + }) + }) }) From 42953ffe3078e66da3648cc28f1610e86cd006b3 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 13 Jul 2020 15:11:43 -0300 Subject: [PATCH 06/12] Properly handle not found transaction and disable auto refresh (#397) --- alm/src/components/Form.tsx | 7 ++++- alm/src/components/MainPage.tsx | 9 +++++- alm/src/components/StatusContainer.tsx | 30 ++----------------- alm/src/components/TransactionSelector.tsx | 22 +++++++++++++- alm/src/components/commons/BackButton.tsx | 35 ++++++++++++++++++++++ alm/src/components/commons/MultiLine.tsx | 5 ++++ alm/src/config/descriptions.ts | 3 +- 7 files changed, 80 insertions(+), 31 deletions(-) create mode 100644 alm/src/components/commons/BackButton.tsx create mode 100644 alm/src/components/commons/MultiLine.tsx diff --git a/alm/src/components/Form.tsx b/alm/src/components/Form.tsx index c438915b..06e23fb6 100644 --- a/alm/src/components/Form.tsx +++ b/alm/src/components/Form.tsx @@ -35,8 +35,13 @@ export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash, receipt }: Fo onSubmit({ chainId, txHash, receipt }) } + const onBack = () => { + setTxHash('') + setSearchTx(false) + } + if (searchTx) { - return + return } return ( diff --git a/alm/src/components/MainPage.tsx b/alm/src/components/MainPage.tsx index d84a85dc..f8eaf813 100644 --- a/alm/src/components/MainPage.tsx +++ b/alm/src/components/MainPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import styled from 'styled-components' import { Route, useHistory } from 'react-router-dom' import { Form } from './Form' @@ -64,6 +64,13 @@ export const MainPage = () => { setNetworkData(chainId) } + useEffect(() => { + const w = window as any + if (w.ethereum) { + w.ethereum.autoRefreshOnNetworkChange = false + } + }, []) + return (
diff --git a/alm/src/components/StatusContainer.tsx b/alm/src/components/StatusContainer.tsx index c2a4b484..661fa4d1 100644 --- a/alm/src/components/StatusContainer.tsx +++ b/alm/src/components/StatusContainer.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react' -import { Link, useHistory, useParams } from 'react-router-dom' +import { useHistory, useParams } from 'react-router-dom' import { useTransactionStatus } from '../hooks/useTransactionStatus' import { formatTxHash, getExplorerTxUrl, getTransactionStatusDescription, validTxHash } from '../utils/networks' import { TRANSACTION_STATUS } from '../config/constants' @@ -8,23 +8,8 @@ import { Loading } from './commons/Loading' import { useStateProvider } from '../state/StateProvider' import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ConfirmationsContainer } from './ConfirmationsContainer' -import { LeftArrow } from './commons/LeftArrow' -import styled from 'styled-components' import { TransactionReceipt } from 'web3-eth' - -const BackButton = styled.button` - color: var(--button-color); - border-color: var(--font-color); - margin-top: 10px; - &:focus { - outline: var(--button-color); - } -` - -const BackLabel = styled.label` - margin-left: 5px; - cursor: pointer; -` +import { BackButton } from './commons/BackButton' export interface StatusContainerParam { onBackToMain: () => void @@ -107,16 +92,7 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar {displayConfirmations && ( )} -
-
- - - - Search another transaction - - -
-
+ ) } diff --git a/alm/src/components/TransactionSelector.tsx b/alm/src/components/TransactionSelector.tsx index 846ff3c4..b7aba39b 100644 --- a/alm/src/components/TransactionSelector.tsx +++ b/alm/src/components/TransactionSelector.tsx @@ -5,13 +5,23 @@ import { TRANSACTION_STATUS } from '../config/constants' import { TransactionReceipt } from 'web3-eth' import { Loading } from './commons/Loading' import { NetworkTransactionSelector } from './NetworkTransactionSelector' +import { BackButton } from './commons/BackButton' +import { TRANSACTION_STATUS_DESCRIPTION } from '../config/descriptions' +import { MultiLine } from './commons/MultiLine' +import styled from 'styled-components' + +const StyledMultiLine = styled(MultiLine)` + margin-bottom: 40px; +` export const TransactionSelector = ({ txHash, - onSelected + onSelected, + onBack }: { txHash: string onSelected: (chainId: number, receipt: TransactionReceipt) => void + onBack: () => void }) => { const { home, foreign } = useStateProvider() const { receipt: homeReceipt, status: homeStatus } = useTransactionFinder({ txHash, web3: home.web3 }) @@ -43,5 +53,15 @@ export const TransactionSelector = ({ return } + if (foreignStatus === TRANSACTION_STATUS.NOT_FOUND && homeStatus === TRANSACTION_STATUS.NOT_FOUND) { + const message = TRANSACTION_STATUS_DESCRIPTION[TRANSACTION_STATUS.NOT_FOUND] + return ( +
+ {message} + +
+ ) + } + return } diff --git a/alm/src/components/commons/BackButton.tsx b/alm/src/components/commons/BackButton.tsx new file mode 100644 index 00000000..eead9516 --- /dev/null +++ b/alm/src/components/commons/BackButton.tsx @@ -0,0 +1,35 @@ +import { Link } from 'react-router-dom' +import { LeftArrow } from './LeftArrow' +import React from 'react' +import styled from 'styled-components' + +const StyledButton = styled.button` + color: var(--button-color); + border-color: var(--font-color); + margin-top: 10px; + &:focus { + outline: var(--button-color); + } +` + +const BackLabel = styled.label` + margin-left: 5px; + cursor: pointer; +` + +export interface BackButtonParam { + onBackToMain: () => void +} + +export const BackButton = ({ onBackToMain }: BackButtonParam) => ( +
+
+ + + + Search another transaction + + +
+
+) diff --git a/alm/src/components/commons/MultiLine.tsx b/alm/src/components/commons/MultiLine.tsx new file mode 100644 index 00000000..84d78765 --- /dev/null +++ b/alm/src/components/commons/MultiLine.tsx @@ -0,0 +1,5 @@ +import styled from 'styled-components' + +export const MultiLine = styled.div` + white-space: pre-wrap; +` diff --git a/alm/src/config/descriptions.ts b/alm/src/config/descriptions.ts index fb395555..a220e2f3 100644 --- a/alm/src/config/descriptions.ts +++ b/alm/src/config/descriptions.ts @@ -4,7 +4,8 @@ export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = { SUCCESS_ONE_MESSAGE: 'was initiated %t', SUCCESS_NO_MESSAGES: 'execution succeeded %t but it does not contain any bridge messages', FAILED: 'failed %t', - NOT_FOUND: 'was not found' + NOT_FOUND: + 'Transaction not found. \n1. Check that the transaction hash is correct. \n2. Wait several blocks for the transaction to be\nmined, gas price affects mining speed.' } export const CONFIRMATIONS_STATUS_LABEL: { [key: string]: string } = { From ebd97dce5c1bb80b8f811e9188ff86436a86ea49 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 14 Jul 2020 21:53:05 +0700 Subject: [PATCH 07/12] Speed up e2e building process (#395) --- .circleci/config.yml | 171 +++++++++++++++++--------- Dockerfile.e2e | 30 +++-- alm/Dockerfile | 23 ++-- deployment-e2e/docker-compose.yml | 12 -- deployment-e2e/molecule.sh | 6 +- e2e-commons/Dockerfile.ui | 7 ++ e2e-commons/README.md | 7 ++ e2e-commons/ULTIMATE.md | 4 +- e2e-commons/docker-compose.yml | 33 ++++- e2e-commons/pull.sh | 15 +++ e2e-commons/scripts/blocks.js | 4 +- e2e-commons/up.sh | 11 +- monitor-e2e/package.json | 9 +- monitor-e2e/periodically-check-all.sh | 8 +- monitor/Dockerfile | 22 ++-- oracle-e2e/package.json | 1 + oracle/Dockerfile | 22 ++-- package.json | 3 +- ui/Dockerfile | 23 ++-- 19 files changed, 280 insertions(+), 131 deletions(-) delete mode 100644 deployment-e2e/docker-compose.yml create mode 100644 e2e-commons/Dockerfile.ui create mode 100755 e2e-commons/pull.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index e6c1e8b9..95b1997e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,20 +36,6 @@ orbs: curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update && sudo apt-get -y install yarn - yarn-install-cached-on-machine: - steps: - - restore_cache: - name: Restore Machine Yarn Package Cache - keys: - - yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} - - run: - name: Install npm dependencies using Yarn - command: nvm use default; yarn install --frozen-lockfile - - save_cache: - name: Save Machine Yarn Package Cache - key: yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} - paths: - - ~/.cache/yarn wait-for-oracle: parameters: redis-key: @@ -73,21 +59,30 @@ orbs: echo "Sleeping..." sleep 3 done + init-repo: + steps: + - checkout + - run: git submodule update --init executors: docker-node: docker: - image: circleci/node:10.15 - machine-with-docker-caching: + machine-with-dlc: + machine: + image: ubuntu-1604:202007-01 + docker_layer_caching: true + classic-machine-without-dlc: machine: image: circleci/classic:latest - docker_layer_caching: true + machine-without-dlc: + machine: + image: ubuntu-1604:202007-01 jobs: initialize: executor: tokenbridge-orb/docker-node steps: - - checkout - - run: git submodule update --init + - tokenbridge-orb/init-repo - restore_cache: name: Restore Yarn Package Cache keys: @@ -140,29 +135,34 @@ jobs: - restore_cache: key: initialize-{{ .Environment.CIRCLE_SHA1 }} - run: yarn run test + build-e2e-images: + executor: tokenbridge-orb/machine-without-dlc + steps: + - tokenbridge-orb/init-repo + - run: + command: | + docker login -u ${DOCKER_LOGIN} -p ${DOCKER_PASSWORD} + cd e2e-commons + docker-compose build oracle monitor ui e2e molecule_runner + docker-compose push oracle monitor ui e2e molecule_runner oracle-e2e: - executor: tokenbridge-orb/docker-node + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - - run: git submodule update --init - - setup_remote_docker: - docker_layer_caching: true - - run: yarn run oracle-e2e + - tokenbridge-orb/init-repo + - run: ./oracle-e2e/run-tests.sh ui-e2e: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - tokenbridge-orb/install-node - tokenbridge-orb/install-yarn - tokenbridge-orb/install-chrome - - run: git submodule update --init - - tokenbridge-orb/yarn-install-cached-on-machine + - restore_cache: + key: initialize-{{ .Environment.CIRCLE_SHA1 }} - run: yarn run ui-e2e monitor-e2e: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - - run: git submodule update --init + - tokenbridge-orb/init-repo - run: ./monitor-e2e/run-tests.sh cover: executor: tokenbridge-orb/docker-node @@ -172,55 +172,47 @@ jobs: - run: yarn workspace ui run coverage - run: yarn workspace ui run coveralls deployment-oracle: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - - run: git submodule update --init + - tokenbridge-orb/init-repo - run: name: Run the scenario command: deployment-e2e/molecule.sh oracle no_output_timeout: 40m deployment-ui: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - - run: git submodule update --init + - tokenbridge-orb/init-repo - run: name: Run the scenario command: deployment-e2e/molecule.sh ui no_output_timeout: 40m deployment-monitor: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - - run: git submodule update --init + - tokenbridge-orb/init-repo - run: name: Run the scenario command: deployment-e2e/molecule.sh monitor no_output_timeout: 40m deployment-repo: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - - run: git submodule update --init - - tokenbridge-orb/install-node - - tokenbridge-orb/install-yarn - - tokenbridge-orb/yarn-install-cached-on-machine + - tokenbridge-orb/init-repo - run: name: Run the scenario command: deployment-e2e/molecule.sh repo no_output_timeout: 40m deployment-multiple: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/machine-without-dlc steps: - - checkout - - run: git submodule update --init + - tokenbridge-orb/init-repo - run: name: Run the scenario command: deployment-e2e/molecule.sh multiple no_output_timeout: 40m ultimate: - executor: tokenbridge-orb/machine-with-docker-caching + executor: tokenbridge-orb/classic-machine-without-dlc parameters: scenario-name: description: "Molecule scenario name used to create the infrastructure" @@ -237,12 +229,11 @@ jobs: default: '' type: string steps: - - checkout - - run: git submodule update --init - tokenbridge-orb/install-node - tokenbridge-orb/install-chrome - tokenbridge-orb/install-yarn - - tokenbridge-orb/yarn-install-cached-on-machine + - restore_cache: + key: initialize-{{ .Environment.CIRCLE_SHA1 }} - run: name: Prepare the infrastructure command: e2e-commons/up.sh deploy << parameters.scenario-name >> blocks @@ -286,35 +277,93 @@ workflows: filters: branches: only: master - - oracle-e2e - - ui-e2e - - monitor-e2e - - deployment-oracle - - deployment-ui - - deployment-monitor - - deployment-repo - - deployment-multiple + - build-e2e-images + - oracle-e2e: + requires: + - build-e2e-images + - ui-e2e: + requires: + - build-e2e-images + - initialize + - monitor-e2e: + requires: + - build-e2e-images + - deployment-oracle: + requires: + - build-e2e-images + - deployment-ui: + requires: + - build-e2e-images + - deployment-monitor: + requires: + - build-e2e-images + - deployment-repo: + requires: + - build-e2e-images + - deployment-multiple: + requires: + - build-e2e-images - ultimate: + requires: + - build-e2e-images + - initialize + filters: + branches: + only: + - master + - develop name: "ultimate: native to erc" scenario-name: native-to-erc redis-key: native-erc-collected-signatures:lastProcessedBlock ui-e2e-grep: "NATIVE TO ERC" - ultimate: + requires: + - build-e2e-images + - initialize + filters: + branches: + only: + - master + - develop name: "ultimate: erc to native" scenario-name: erc-to-native redis-key: erc-native-collected-signatures:lastProcessedBlock ui-e2e-grep: "ERC TO NATIVE" - ultimate: + requires: + - build-e2e-images + - initialize + filters: + branches: + only: + - master + - develop name: "ultimate: erc to erc" scenario-name: erc-to-erc redis-key: erc-erc-collected-signatures:lastProcessedBlock ui-e2e-grep: "ERC TO ERC" - ultimate: + requires: + - build-e2e-images + - initialize + filters: + branches: + only: + - master + - develop name: "ultimate: amb" scenario-name: amb redis-key: amb-collected-signatures:lastProcessedBlock oracle-e2e-script: "amb" - ultimate: + requires: + - build-e2e-images + - initialize + filters: + branches: + only: + - master + - develop name: "ultimate: amb stake erc to erc" scenario-name: ultimate-amb-stake-erc-to-erc redis-key: amb-collected-signatures:lastProcessedBlock diff --git a/Dockerfile.e2e b/Dockerfile.e2e index bc9ae1a3..4f0e1db3 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -1,15 +1,31 @@ -FROM node:10 +FROM node:8 as contracts + +WORKDIR /mono + +COPY contracts/package.json contracts/package-lock.json ./contracts/ + +WORKDIR /mono/contracts +RUN npm install --only=prod + +COPY ./contracts/truffle-config.js ./ +COPY ./contracts/contracts ./contracts +RUN npm run compile + +FROM node:8 WORKDIR /mono COPY package.json . +COPY --from=contracts /mono/contracts/build ./contracts/build COPY oracle-e2e/package.json ./oracle-e2e/ COPY monitor-e2e/package.json ./monitor-e2e/ -COPY contracts/package.json ./contracts/ COPY yarn.lock . -RUN yarn install --frozen-lockfile -COPY ./contracts ./contracts -RUN yarn install:deploy -RUN yarn compile:contracts +RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production -COPY . . +COPY ./contracts/deploy ./contracts/deploy +RUN yarn install:deploy + +COPY commons/ ./commons/ +COPY oracle-e2e/ ./oracle-e2e/ +COPY monitor-e2e/ ./monitor-e2e/ +COPY e2e-commons/ ./e2e-commons/ diff --git a/alm/Dockerfile b/alm/Dockerfile index bcd293f0..7fdc124b 100644 --- a/alm/Dockerfile +++ b/alm/Dockerfile @@ -1,20 +1,29 @@ +FROM node:8 as contracts + +WORKDIR /mono + +COPY contracts/package.json contracts/package-lock.json ./contracts/ + +WORKDIR /mono/contracts +RUN npm install --only=prod + +COPY ./contracts/truffle-config.js ./ +COPY ./contracts/contracts ./contracts +RUN npm run compile + FROM node:12 as alm-builder WORKDIR /mono COPY package.json . -COPY contracts/package.json ./contracts/ +COPY --from=contracts /mono/contracts/build ./contracts/build COPY commons/package.json ./commons/ COPY alm/package.json ./alm/ COPY yarn.lock . -RUN yarn install --production --frozen-lockfile - -COPY ./contracts ./contracts -RUN yarn run compile:contracts -RUN mv ./contracts/build ./ && rm -rf ./contracts/* ./contracts/.[!.]* && mv ./build ./contracts/ +RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production COPY ./commons ./commons - COPY ./alm ./alm + ARG DOT_ENV_PATH=./alm/.env COPY ${DOT_ENV_PATH} ./alm/.env diff --git a/deployment-e2e/docker-compose.yml b/deployment-e2e/docker-compose.yml deleted file mode 100644 index b2dc6e8a..00000000 --- a/deployment-e2e/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -version: '3.0' -services: - molecule_runner: - build: - context: .. - dockerfile: deployment-e2e/Dockerfile - restart: 'no' - privileged: true - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ..:/mono diff --git a/deployment-e2e/molecule.sh b/deployment-e2e/molecule.sh index fe255522..18389c18 100755 --- a/deployment-e2e/molecule.sh +++ b/deployment-e2e/molecule.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash -cd $(dirname $0) +cd ./e2e-commons set -e # exit when any command fails +docker-compose pull molecule_runner +docker network create --driver bridge ultimate || true while [ "$1" != "" ]; do - docker-compose build && docker-compose run molecule_runner /bin/bash -c "molecule test --scenario-name $1" + docker-compose run molecule_runner /bin/bash -c "molecule test --scenario-name $1" shift # Shift all the parameters down by one done diff --git a/e2e-commons/Dockerfile.ui b/e2e-commons/Dockerfile.ui new file mode 100644 index 00000000..cbd415a5 --- /dev/null +++ b/e2e-commons/Dockerfile.ui @@ -0,0 +1,7 @@ +ARG DOCKER_LOGIN +ARG CIRCLE_BRANCH +FROM ${DOCKER_LOGIN}/tokenbridge-e2e-ui:${CIRCLE_BRANCH} + +ARG DOT_ENV_PATH + +COPY ${DOT_ENV_PATH} ./.env diff --git a/e2e-commons/README.md b/e2e-commons/README.md index 8ce8e7bd..838e1353 100644 --- a/e2e-commons/README.md +++ b/e2e-commons/README.md @@ -22,9 +22,16 @@ Shut down and cleans up containers, networks, services, running scripts: | --- | --- | | deploy | Deploys the Smart Contracts | | oracle | Launches Oracle containers | +| oracle-validator-2 | Launches Oracle containers for second validator | +| oracle-validator-3 | Launches Oracle containers for third validator | | ui | Launches UI containers | | blocks | Auto mines blocks | +| monitor | Launches Monitor containers | | native-to-erc | Creates infrastructure for ultimate e2e testing, for native-to-erc type of bridge | +| erc-to-native | Creates infrastructure for ultimate e2e testing, for erc-to-native type of bridge | +| erc-to-erc | Creates infrastructure for ultimate e2e testing, for erc-to-erc type of bridge | +| amb | Creates infrastructure for ultimate e2e testing, for arbitrary message type of bridge | +| ultimate-amb-stake-erc-to-erc | Creates infrastructure for ultimate e2e testing, for stake token bridge | #### Ultimate e2e testing diff --git a/e2e-commons/ULTIMATE.md b/e2e-commons/ULTIMATE.md index 807ef3af..69da0654 100644 --- a/e2e-commons/ULTIMATE.md +++ b/e2e-commons/ULTIMATE.md @@ -15,13 +15,13 @@ It runs the e2e tests on components deployed using the deployment playbooks. Run the Parity nodes, deploy the bridge contracts, deploy Oracle using the deployment playbook. ```bash -./up.sh deploy native-to-erc +./up.sh deploy native-to-erc blocks ``` ### 2. Run the E2E tests ``` -docker-compose run e2e yarn workspace oracle-e2e run native-to-erc +cd ui-e2e; yarn mocha -g "NATIVE_TO_ERC" -b ./test.js ``` ## Diagram diff --git a/e2e-commons/docker-compose.yml b/e2e-commons/docker-compose.yml index f1568355..89eb0fb3 100644 --- a/e2e-commons/docker-compose.yml +++ b/e2e-commons/docker-compose.yml @@ -27,6 +27,7 @@ services: networks: - ultimate oracle: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-oracle:${CIRCLE_BRANCH} build: context: .. dockerfile: oracle/Dockerfile @@ -37,6 +38,7 @@ services: networks: - ultimate oracle-erc20: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-oracle:${CIRCLE_BRANCH} build: context: .. dockerfile: oracle/Dockerfile @@ -47,6 +49,7 @@ services: networks: - ultimate oracle-erc20-native: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-oracle:${CIRCLE_BRANCH} build: context: .. dockerfile: oracle/Dockerfile @@ -57,6 +60,7 @@ services: networks: - ultimate oracle-amb: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-oracle:${CIRCLE_BRANCH} build: context: .. dockerfile: oracle/Dockerfile @@ -67,6 +71,7 @@ services: networks: - ultimate ui: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-ui:${CIRCLE_BRANCH} build: context: .. dockerfile: ui/Dockerfile @@ -78,8 +83,10 @@ services: ui-erc20: build: context: .. - dockerfile: ui/Dockerfile + dockerfile: e2e-commons/Dockerfile.ui args: + DOCKER_LOGIN: ${DOCKER_LOGIN} + CIRCLE_BRANCH: ${CIRCLE_BRANCH} DOT_ENV_PATH: e2e-commons/components-envs/ui-erc20.env command: "true" networks: @@ -87,8 +94,10 @@ services: ui-erc20-native: build: context: .. - dockerfile: ui/Dockerfile + dockerfile: e2e-commons/Dockerfile.ui args: + DOCKER_LOGIN: ${DOCKER_LOGIN} + CIRCLE_BRANCH: ${CIRCLE_BRANCH} DOT_ENV_PATH: e2e-commons/components-envs/ui-erc20-native.env command: "true" networks: @@ -96,13 +105,16 @@ services: ui-amb-stake-erc20-erc20: build: context: .. - dockerfile: ui/Dockerfile + dockerfile: e2e-commons/Dockerfile.ui args: + DOCKER_LOGIN: ${DOCKER_LOGIN} + CIRCLE_BRANCH: ${CIRCLE_BRANCH} DOT_ENV_PATH: e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env command: "true" networks: - ultimate monitor: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-monitor:${CIRCLE_BRANCH} build: context: .. dockerfile: monitor/Dockerfile @@ -113,6 +125,7 @@ services: networks: - ultimate monitor-erc20: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-monitor:${CIRCLE_BRANCH} build: context: .. dockerfile: monitor/Dockerfile @@ -123,6 +136,7 @@ services: networks: - ultimate monitor-erc20-native: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-monitor:${CIRCLE_BRANCH} build: context: .. dockerfile: monitor/Dockerfile @@ -133,6 +147,7 @@ services: networks: - ultimate monitor-amb: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-monitor:${CIRCLE_BRANCH} build: context: .. dockerfile: monitor/Dockerfile @@ -143,6 +158,7 @@ services: networks: - ultimate e2e: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-e2e:${CIRCLE_BRANCH} build: context: .. dockerfile: Dockerfile.e2e @@ -150,9 +166,20 @@ services: networks: - ultimate blocks: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-e2e:${CIRCLE_BRANCH} build: context: .. dockerfile: Dockerfile.e2e entrypoint: node e2e-commons/scripts/blocks.js networks: - ultimate + molecule_runner: + image: ${DOCKER_LOGIN}/tokenbridge-e2e-molecule_runner:${CIRCLE_BRANCH} + build: + context: .. + dockerfile: deployment-e2e/Dockerfile + restart: 'no' + privileged: true + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ..:/mono diff --git a/e2e-commons/pull.sh b/e2e-commons/pull.sh new file mode 100755 index 00000000..b5a702d7 --- /dev/null +++ b/e2e-commons/pull.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +cd $(dirname $0) +set -e # exit when any command fails + +docker-compose pull e2e +while [ "$1" != "" ]; do + if [ "$1" == "oracle" ]; then + docker-compose pull oracle + elif [ "$1" == "monitor" ]; then + docker-compose pull monitor + elif [ "$1" == "ui" ]; then + docker-compose pull ui + fi + shift +done diff --git a/e2e-commons/scripts/blocks.js b/e2e-commons/scripts/blocks.js index c53e56d0..4bc2eeb9 100644 --- a/e2e-commons/scripts/blocks.js +++ b/e2e-commons/scripts/blocks.js @@ -23,10 +23,10 @@ function main() { setTimeout(async () => { try { generateNewBlock(homeWeb3, blockGenerator.address) - } catch {} // in case of Transaction with the same hash was already imported. + } catch (_) {} // in case of Transaction with the same hash was already imported. try { generateNewBlock(foreignWeb3, blockGenerator.address) - } catch {} // in case of Transaction with the same hash was already imported. + } catch (_) {} // in case of Transaction with the same hash was already imported. main() }, 1000) } diff --git a/e2e-commons/up.sh b/e2e-commons/up.sh index f8f0fe32..30f2ea22 100755 --- a/e2e-commons/up.sh +++ b/e2e-commons/up.sh @@ -3,14 +3,12 @@ cd $(dirname $0) set -e # exit when any command fails ./down.sh -docker-compose build +docker-compose build parity1 parity2 +test -n "$NODOCKERPULL" || ./pull.sh $@ docker network create --driver bridge ultimate || true docker-compose up -d parity1 parity2 e2e startValidator () { - # make sure that old image tags are not cached - docker-compose $1 build - docker-compose $1 run -d --name $4 redis docker-compose $1 run -d --name $5 rabbit docker-compose $1 run $2 $3 -d oracle yarn watcher:signature-request @@ -36,7 +34,7 @@ startValidator () { while [ "$1" != "" ]; do if [ "$1" == "oracle" ]; then - docker-compose up -d redis rabbit oracle oracle-erc20 oracle-erc20-native oracle-amb + docker-compose up -d redis rabbit docker-compose run -d oracle yarn watcher:signature-request docker-compose run -d oracle yarn watcher:collected-signatures @@ -74,6 +72,9 @@ while [ "$1" != "" ]; do fi if [ "$1" == "ui" ]; then + # this should only rebuild last 3 steps from ui/Dockerfile + docker-compose build ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20 + docker-compose up -d ui ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20 docker-compose run -d -p 3000:3000 ui yarn start diff --git a/monitor-e2e/package.json b/monitor-e2e/package.json index 41b20be5..66d8b436 100644 --- a/monitor-e2e/package.json +++ b/monitor-e2e/package.json @@ -9,11 +9,12 @@ }, "author": "", "license": "ISC", - "dependencies": {}, + "dependencies": { + "mocha": "^5.2.0", + "axios": "0.19.0" + }, "engines": { "node": ">= 8.9" }, - "devDependencies": { - "axios": "0.19.0" - } + "devDependencies": {} } diff --git a/monitor-e2e/periodically-check-all.sh b/monitor-e2e/periodically-check-all.sh index fb494cff..251bad9a 100755 --- a/monitor-e2e/periodically-check-all.sh +++ b/monitor-e2e/periodically-check-all.sh @@ -1,7 +1,7 @@ while true; do sleep 3 - docker-compose -f ../e2e-commons/docker-compose.yml exec monitor yarn check-all - docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 yarn check-all - docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native yarn check-all - docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-amb yarn check-all + COMPOSE_INTERACTIVE_NO_CLI=1 nohup docker-compose -f ../e2e-commons/docker-compose.yml exec monitor yarn check-all + COMPOSE_INTERACTIVE_NO_CLI=1 nohup docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 yarn check-all + COMPOSE_INTERACTIVE_NO_CLI=1 nohup docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native yarn check-all + COMPOSE_INTERACTIVE_NO_CLI=1 nohup docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-amb yarn check-all done diff --git a/monitor/Dockerfile b/monitor/Dockerfile index 6d4f1a75..3d48c839 100644 --- a/monitor/Dockerfile +++ b/monitor/Dockerfile @@ -1,19 +1,27 @@ +FROM node:8 as contracts + +WORKDIR /mono + +COPY contracts/package.json contracts/package-lock.json ./contracts/ + +WORKDIR /mono/contracts +RUN npm install --only=prod + +COPY ./contracts/truffle-config.js ./ +COPY ./contracts/contracts ./contracts +RUN npm run compile + FROM node:8 WORKDIR /mono COPY package.json . -COPY contracts/package.json ./contracts/ +COPY --from=contracts /mono/contracts/build ./contracts/build COPY commons/package.json ./commons/ COPY monitor/package.json ./monitor/ COPY yarn.lock . -RUN yarn install --frozen-lockfile --production - -COPY ./contracts ./contracts -RUN yarn run compile:contracts -RUN mv ./contracts/build ./ && rm -rf ./contracts/* ./contracts/.[!.]* && mv ./build ./contracts/ +RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production COPY ./commons ./commons - COPY ./monitor ./monitor WORKDIR /mono/monitor diff --git a/oracle-e2e/package.json b/oracle-e2e/package.json index 53bd2ed0..506ae7c0 100644 --- a/oracle-e2e/package.json +++ b/oracle-e2e/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "mocha": "^5.2.0", "chalk": "^2.4.1", "dotenv": "^6.0.0", "promise-retry": "^1.1.1", diff --git a/oracle/Dockerfile b/oracle/Dockerfile index 6edc546b..86a8866b 100644 --- a/oracle/Dockerfile +++ b/oracle/Dockerfile @@ -1,3 +1,16 @@ +FROM node:8 as contracts + +WORKDIR /mono + +COPY contracts/package.json contracts/package-lock.json ./contracts/ + +WORKDIR /mono/contracts +RUN npm install --only=prod + +COPY ./contracts/truffle-config.js ./ +COPY ./contracts/contracts ./contracts +RUN npm run compile + FROM node:8 RUN apt-get update @@ -9,18 +22,13 @@ RUN apt-get clean WORKDIR /mono COPY package.json . -COPY contracts/package.json ./contracts/ +COPY --from=contracts /mono/contracts/build ./contracts/build COPY commons/package.json ./commons/ COPY oracle/package.json ./oracle/ COPY yarn.lock . -RUN yarn install --production --frozen-lockfile - -COPY ./contracts ./contracts -RUN yarn run compile:contracts -RUN mv ./contracts/build ./ && rm -rf ./contracts/* ./contracts/.[!.]* && mv ./build ./contracts/ +RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production COPY ./commons ./commons - COPY ./oracle ./oracle WORKDIR /mono/oracle diff --git a/package.json b/package.json index bf92b2ff..6e9683a5 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,10 @@ "test": "yarn wsrun --exclude oracle-e2e --exclude ui-e2e --exclude monitor-e2e test", "oracle-e2e": "./oracle-e2e/run-tests.sh", "ui-e2e": "./ui-e2e/run-tests.sh", + "monitor-e2e": "./monitor-e2e/run-tests.sh", "clean": "rm -rf ./node_modules ./**/node_modules ./**/**/node_modules ./**/build ./**/**/dist", "compile:contracts": "yarn workspace token-bridge-contracts run compile", "install:deploy": "cd contracts/deploy && npm install --unsafe-perm --silent", - "postinstall": "ln -sf $(pwd)/node_modules/openzeppelin-solidity/ contracts/node_modules/openzeppelin-solidity" + "postinstall": "test -n \"$NOYARNPOSTINSTALL\" || ln -sf $(pwd)/node_modules/openzeppelin-solidity/ contracts/node_modules/openzeppelin-solidity" } } diff --git a/ui/Dockerfile b/ui/Dockerfile index 57751fb0..90f6aab0 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -1,21 +1,30 @@ +FROM node:8 as contracts + +WORKDIR /mono + +COPY contracts/package.json contracts/package-lock.json ./contracts/ + +WORKDIR /mono/contracts +RUN npm install --only=prod + +COPY ./contracts/truffle-config.js ./ +COPY ./contracts/contracts ./contracts +RUN npm run compile + FROM node:8 WORKDIR /mono COPY package.json . -COPY contracts/package.json ./contracts/ +COPY --from=contracts /mono/contracts/build ./contracts/build COPY commons/package.json ./commons/ COPY ui/package.json ./ui/ COPY ui/lib/web3-eth/index.js ./ui/lib/web3-eth/index.js COPY yarn.lock . -RUN yarn install --production --frozen-lockfile - -COPY ./contracts ./contracts -RUN yarn run compile:contracts -RUN mv ./contracts/build ./ && rm -rf ./contracts/* ./contracts/.[!.]* && mv ./build ./contracts/ +RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production COPY ./commons ./commons - COPY ./ui ./ui + ARG DOT_ENV_PATH=./ui/.env COPY ${DOT_ENV_PATH} ./ui/.env From efc433e9e02ba008886cfc270ab2a990b8f7cb0b Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 14 Jul 2020 16:55:22 -0300 Subject: [PATCH 08/12] Set descriptions for every state in ALM (#398) --- alm/src/components/ConfirmationsContainer.tsx | 37 +++++++++--- alm/src/components/StatusContainer.tsx | 2 +- alm/src/config/constants.ts | 3 +- alm/src/config/descriptions.ts | 57 ++++++++++++++----- alm/src/hooks/useMessageConfirmations.ts | 18 +++++- alm/src/utils/executionWaitingForBlocks.ts | 2 +- alm/src/utils/networks.ts | 15 ++--- alm/src/utils/signatureWaitingForBlocks.ts | 2 +- 8 files changed, 102 insertions(+), 34 deletions(-) diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx index f4995c44..437a69d5 100644 --- a/alm/src/components/ConfirmationsContainer.tsx +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -4,7 +4,7 @@ import { useMessageConfirmations } from '../hooks/useMessageConfirmations' import { MessageObject } from '../utils/web3' import styled from 'styled-components' import { CONFIRMATIONS_STATUS } from '../config/constants' -import { CONFIRMATIONS_STATUS_LABEL } from '../config/descriptions' +import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions' import { SimpleLoading } from './commons/Loading' import { ValidatorsConfirmations } from './ValidatorsConfirmations' import { getConfirmationsStatusDescription } from '../utils/networks' @@ -12,6 +12,8 @@ import { useStateProvider } from '../state/StateProvider' import { ExecutionConfirmation } from './ExecutionConfirmation' import { useValidatorContract } from '../hooks/useValidatorContract' import { useBlockConfirmations } from '../hooks/useBlockConfirmations' +import { MultiLine } from './commons/MultiLine' +import { ExplorerTxLink } from './commons/ExplorerTxLink' const StatusLabel = styled.label` font-weight: bold; @@ -57,21 +59,42 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp } blockConfirmations }) + const statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL + + const parseDescription = () => { + let description = getConfirmationsStatusDescription(status, homeName, foreignName, fromHome) + let link + const descArray = description.split('%link') + if (descArray.length > 1) { + description = descArray[0] + link = ( + + {descArray[1]} + + ) + } + + return ( +
+ {description} + {link} +
+ ) + } + return (
Status: - {status !== CONFIRMATIONS_STATUS.UNDEFINED ? CONFIRMATIONS_STATUS_LABEL[status] : } + {status !== CONFIRMATIONS_STATUS.UNDEFINED ? statusLabel[status] : }
-

- {status !== CONFIRMATIONS_STATUS.UNDEFINED - ? getConfirmationsStatusDescription(status, homeName, foreignName) - : ''} -

+ + {status !== CONFIRMATIONS_STATUS.UNDEFINED ? parseDescription() : ''} +
The request{' '} {displayExplorerLink && ( - + {formattedMessageId} )} diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 5543c537..5a745af7 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -45,7 +45,8 @@ export const CONFIRMATIONS_STATUS = { EXECUTION_WAITING: 'EXECUTION_WAITING', FAILED: 'FAILED', PENDING: 'PENDING', - WAITING: 'WAITING', + WAITING_VALIDATORS: 'WAITING_VALIDATORS', + WAITING_CHAIN: 'WAITING_CHAIN', UNDEFINED: 'UNDEFINED' } diff --git a/alm/src/config/descriptions.ts b/alm/src/config/descriptions.ts index a220e2f3..edc38270 100644 --- a/alm/src/config/descriptions.ts +++ b/alm/src/config/descriptions.ts @@ -9,29 +9,58 @@ export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = { } export const CONFIRMATIONS_STATUS_LABEL: { [key: string]: string } = { + SUCCESS: 'Success', + SUCCESS_MESSAGE_FAILED: 'Success', + FAILED: 'Failed', + PENDING: 'Pending', + WAITING_VALIDATORS: 'Waiting', + WAITING_CHAIN: 'Waiting' +} + +export const CONFIRMATIONS_STATUS_LABEL_HOME: { [key: string]: string } = { SUCCESS: 'Success', SUCCESS_MESSAGE_FAILED: 'Success', EXECUTION_FAILED: 'Execution failed', EXECUTION_PENDING: 'Execution pending', EXECUTION_WAITING: 'Execution waiting', - FAILED: 'Failed', - PENDING: 'Pending', - WAITING: 'Waiting' + FAILED: 'Confirmation Failed', + PENDING: 'Confirmation Pending', + WAITING_VALIDATORS: 'Confirmation Waiting', + WAITING_CHAIN: 'Confirmation Waiting' } -// %homeChain will be replaced by the home network name -// %foreignChain will be replaced by the foreign network name +// use %link to identify a link export const CONFIRMATIONS_STATUS_DESCRIPTION: { [key: string]: string } = { SUCCESS: '', SUCCESS_MESSAGE_FAILED: - 'Signatures have been collected in the %homeChain and they were successfully sent to the %foreignChain but the contained message execution failed.', - EXECUTION_FAILED: - 'Signatures have been collected in the %homeChain and they were sent to the %foreignChain but the transaction with signatures failed', - EXECUTION_PENDING: - 'Signatures have been collected in the %homeChain and they were sent to the %foreignChain but the transaction is in the pending state (transactions congestion or low gas price)', - EXECUTION_WAITING: 'Execution waiting', + '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.', FAILED: - 'Some validators sent improper transactions as so they were failed, collected confirmations are not enough to execute the relay request', - PENDING: 'Some confirmations are in pending state', - WAITING: 'Validators are waiting for the chain finalization' + 'The specified transaction was included in a block,\nbut confirmations sent by a majority of validators\nfailed. The cross-chain relay request will not be\nprocessed. Contact to the validators by\nmessaging on %linkhttps://forum.poa.network/c/support', + PENDING: + 'The specified transaction was included in a block. A\nmajority of validators sent confirmations which have\nnot yet been added to a block.', + WAITING_VALIDATORS: + 'The specified transaction was included in a block.\nSome validators have sent confirmations, others are\nwaiting for chain finalization.', + WAITING_CHAIN: + 'The specified transaction was included in a block.\nValidators are waiting for chain finalization before\nsending their confirmations.' +} + +// use %link to identify a link +export const CONFIRMATIONS_STATUS_DESCRIPTION_HOME: { [key: string]: string } = { + SUCCESS: '', + 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\nvalidator’s 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\nvalidator’s 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.\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', + 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: + 'The specified transaction was included in a block.\nA majority of validators sent signatures which have not\nyet been added to a block.', + WAITING_VALIDATORS: + 'The specified transaction was included in a block.\nSome validators have sent signatures, others are\nwaiting for chain finalization.', + WAITING_CHAIN: + 'The specified transaction was included in a block.\nValidators are waiting for chain finalization\nbefore sending their signatures.' } diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index 9b27f010..7b2713f4 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -83,6 +83,13 @@ export const useMessageConfirmations = ({ const [pendingConfirmations, setPendingConfirmations] = useState(false) const [pendingExecution, setPendingExecution] = useState(false) + const existsConfirmation = (confirmationArray: ConfirmationParam[]) => { + const filteredList = confirmationArray.filter( + c => c.status !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED && c.status !== VALIDATOR_CONFIRMATION_STATUS.WAITING + ) + return filteredList.length > 0 + } + // Check if the validators are waiting for block confirmations to verify the message useEffect( () => { @@ -319,6 +326,8 @@ export const useMessageConfirmations = ({ setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED) } else if (pendingExecution) { setStatus(CONFIRMATIONS_STATUS.EXECUTION_PENDING) + } else if (waitingBlocksForExecutionResolved) { + setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING) } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } @@ -326,11 +335,13 @@ export const useMessageConfirmations = ({ setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } } else if (waitingBlocks) { - setStatus(CONFIRMATIONS_STATUS.WAITING) + setStatus(CONFIRMATIONS_STATUS.WAITING_CHAIN) } else if (failedConfirmations) { setStatus(CONFIRMATIONS_STATUS.FAILED) } else if (pendingConfirmations) { setStatus(CONFIRMATIONS_STATUS.PENDING) + } else if (waitingBlocksResolved && existsConfirmation(confirmations)) { + setStatus(CONFIRMATIONS_STATUS.WAITING_VALIDATORS) } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } @@ -344,7 +355,10 @@ export const useMessageConfirmations = ({ failedConfirmations, failedExecution, pendingConfirmations, - pendingExecution + pendingExecution, + waitingBlocksResolved, + confirmations, + waitingBlocksForExecutionResolved ] ) diff --git a/alm/src/utils/executionWaitingForBlocks.ts b/alm/src/utils/executionWaitingForBlocks.ts index 95834826..0ba40e4e 100644 --- a/alm/src/utils/executionWaitingForBlocks.ts +++ b/alm/src/utils/executionWaitingForBlocks.ts @@ -22,8 +22,8 @@ export const checkWaitingBlocksForExecution = async ( timestamp: 0, executionResult: false }) - setWaitingBlocksForExecution(false) setWaitingBlocksForExecutionResolved(true) + setWaitingBlocksForExecution(false) blockProvider.stop() } else { let nextInterval = interval diff --git a/alm/src/utils/networks.ts b/alm/src/utils/networks.ts index 2b58df76..2b995c5b 100644 --- a/alm/src/utils/networks.ts +++ b/alm/src/utils/networks.ts @@ -1,5 +1,9 @@ import { formatDistance } from 'date-fns' -import { CONFIRMATIONS_STATUS_DESCRIPTION, TRANSACTION_STATUS_DESCRIPTION } from '../config/descriptions' +import { + CONFIRMATIONS_STATUS_DESCRIPTION, + CONFIRMATIONS_STATUS_DESCRIPTION_HOME, + TRANSACTION_STATUS_DESCRIPTION +} from '../config/descriptions' import { FOREIGN_EXPLORER_TX_TEMPLATE, HOME_EXPLORER_TX_TEMPLATE } from '../config/constants' export const validTxHash = (txHash: string) => /^0x[a-fA-F0-9]{64}$/.test(txHash) @@ -31,11 +35,8 @@ export const getTransactionStatusDescription = (status: string, timestamp: Maybe return description } -export const getConfirmationsStatusDescription = (status: string, home: string, foreign: string) => { - let description = CONFIRMATIONS_STATUS_DESCRIPTION[status] +export const getConfirmationsStatusDescription = (status: string, home: string, foreign: string, fromHome: boolean) => { + const statusDescription = fromHome ? CONFIRMATIONS_STATUS_DESCRIPTION_HOME : CONFIRMATIONS_STATUS_DESCRIPTION - description = description.replace('%homeChain', home) - description = description.replace('%foreignChain', foreign) - - return description + return statusDescription[status] } diff --git a/alm/src/utils/signatureWaitingForBlocks.ts b/alm/src/utils/signatureWaitingForBlocks.ts index 74f768ff..9133d364 100644 --- a/alm/src/utils/signatureWaitingForBlocks.ts +++ b/alm/src/utils/signatureWaitingForBlocks.ts @@ -14,8 +14,8 @@ export const checkSignaturesWaitingForBLocks = async ( const currentBlock = blockProvider.get() if (currentBlock && currentBlock >= targetBlock) { - setWaitingStatus(false) setWaitingBlocksResolved(true) + setWaitingStatus(false) blockProvider.stop() } else { let nextInterval = interval From dc060387bce2aa74c28a623189bb07700ec0ced8 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Fri, 17 Jul 2020 17:59:25 -0300 Subject: [PATCH 09/12] Update ALM logic to continue displaying failed signatures from validators (#399) --- alm/src/hooks/useMessageConfirmations.ts | 2 +- alm/src/services/BlockNumberProvider.ts | 2 +- .../__tests__/getConfirmationsForTx.test.ts | 99 +++++++++++++++++-- alm/src/utils/getConfirmationsForTx.ts | 79 ++++++++------- 4 files changed, 141 insertions(+), 41 deletions(-) diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index 7b2713f4..e79c8389 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -313,7 +313,7 @@ export const useMessageConfirmations = ({ // Sets the message status based in the collected information useEffect( () => { - if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) { + if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) { const newStatus = executionData.executionResult ? CONFIRMATIONS_STATUS.SUCCESS : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED diff --git a/alm/src/services/BlockNumberProvider.ts b/alm/src/services/BlockNumberProvider.ts index a6606c87..686cf003 100644 --- a/alm/src/services/BlockNumberProvider.ts +++ b/alm/src/services/BlockNumberProvider.ts @@ -33,7 +33,7 @@ export class BlockNumberProvider { } stop() { - this.running = this.running - 1 + this.running = this.running > 0 ? this.running - 1 : 0 if (!this.running) { clearTimeout(this.ref) diff --git a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts index 66080d2c..4a91266b 100644 --- a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts +++ b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts @@ -105,13 +105,13 @@ describe('getConfirmationsForTx', () => { expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1) - expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(0) expect(getValidatorPendingTransaction).toBeCalledTimes(0) expect(setPendingConfirmations).toBeCalledTimes(0) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, @@ -243,13 +243,13 @@ describe('getConfirmationsForTx', () => { expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1) - expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(0) expect(getValidatorPendingTransaction).toBeCalledTimes(0) expect(setPendingConfirmations).toBeCalledTimes(0) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, @@ -264,6 +264,93 @@ describe('getConfirmationsForTx', () => { ]) ) }) + test('should set validator confirmations status, validator transactions, keep failed found transaction and not retry', async () => { + const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3' + const validatorList = [validator1, validator2, validator3, validator4] + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: + validator !== validator3 && validator !== validator4 + ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '', + timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator3 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator3 ? '0x123' : '', + timestamp: validatorData.validator === validator3 ? 123 : 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(1) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]()).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + }) test('should look for failed and pending transactions for not confirmed validators', async () => { // Validator1 success // Validator2 failed @@ -335,7 +422,7 @@ describe('getConfirmationsForTx', () => { expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, @@ -419,7 +506,7 @@ describe('getConfirmationsForTx', () => { expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(0) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index 7e83cd1b..ab1f423e 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -13,6 +13,7 @@ import { getValidatorPendingTransaction, getValidatorSuccessTransaction } from './validatorConfirmationHelpers' +import { ConfirmationParam } from '../hooks/useMessageConfirmations' export const getConfirmationsForTx = async ( messageData: string, @@ -43,9 +44,21 @@ export const getConfirmationsForTx = async ( 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 notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS) - // If signatures not collected, it needs to retry in the next blocks + // If signatures not collected, look for pending transactions if (successConfirmations.length !== requiredSignatures) { // Check if confirmation is pending const validatorPendingConfirmationsChecks = await Promise.all( @@ -63,49 +76,49 @@ export const getConfirmationsForTx = async ( if (validatorPendingConfirmations.length > 0) { setPendingConfirmations(true) } + } - const undefinedConfirmations = validatorConfirmations.filter( - c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - ) - // Check if confirmation failed - const validatorFailedConfirmationsChecks = await Promise.all( - undefinedConfirmations.map( - getValidatorFailedTransaction(bridgeContract, messageData, timestamp, 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) { - setFailedConfirmations(true) - } + const undefinedConfirmations = validatorConfirmations.filter( + c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + ) - const missingConfirmations = validatorConfirmations.filter( - c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING + // Check if confirmation failed + const validatorFailedConfirmationsChecks = await Promise.all( + undefinedConfirmations.map( + getValidatorFailedTransaction(bridgeContract, messageData, timestamp, 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) { + setFailedConfirmations(true) + } - if (missingConfirmations.length > 0) { - shouldRetry = true - } - } else { - // If signatures collected, it should set other signatures as not required - const notRequiredConfirmations = notSuccessConfirmations.map(c => ({ + 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 + } + + if (successConfirmations.length === requiredSignatures) { + // If signatures collected, it should set other signatures not found as not required + const notRequiredConfirmations = missingConfirmations.map(c => ({ validator: c.validator, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED })) - validatorConfirmations = [...successConfirmations, ...notRequiredConfirmations] + validatorConfirmations = [...validatorConfirmations, ...notRequiredConfirmations] setSignatureCollected(true) } - // Set confirmations to update UI and continue requesting the transactions for the signatures - setResult(validatorConfirmations) - // get transactions from success signatures const successConfirmationWithData = await Promise.all( validatorConfirmations From 77bc6c662a10dfe4f048c4a57229f45a1084769c Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 20 Jul 2020 12:29:16 -0300 Subject: [PATCH 10/12] Add info alert in ALM (#402) --- alm/src/components/MainPage.tsx | 53 +++++++++++++++++++++++- alm/src/components/commons/CloseIcon.tsx | 21 ++++++++++ alm/src/components/commons/InfoAlert.tsx | 31 ++++++++++++++ alm/src/components/commons/InfoIcon.tsx | 16 +++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 alm/src/components/commons/CloseIcon.tsx create mode 100644 alm/src/components/commons/InfoAlert.tsx create mode 100644 alm/src/components/commons/InfoIcon.tsx diff --git a/alm/src/components/MainPage.tsx b/alm/src/components/MainPage.tsx index f8eaf813..88ced815 100644 --- a/alm/src/components/MainPage.tsx +++ b/alm/src/components/MainPage.tsx @@ -1,10 +1,13 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useCallback } from 'react' import styled from 'styled-components' import { Route, useHistory } from 'react-router-dom' import { Form } from './Form' import { StatusContainer } from './StatusContainer' import { useStateProvider } from '../state/StateProvider' 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' const StyledMainPage = styled.div` text-align: center; @@ -32,6 +35,14 @@ const HeaderContainer = styled.header` } ` +const AlertP = styled.p` + align-items: start; + margin-bottom: 0; + @media (max-width: 600px) { + flex-direction: column; + } +` + export interface FormSubmitParams { chainId: number txHash: string @@ -43,6 +54,27 @@ export const MainPage = () => { const { home, foreign } = useStateProvider() const [networkName, setNetworkName] = useState('') const [receipt, setReceipt] = useState>(null) + const [showInfoAlert, setShowInfoAlert] = useState(false) + + const loadFromStorage = useCallback(() => { + const hideAlert = window.localStorage.getItem('hideInfoAlert') + setShowInfoAlert(!hideAlert) + }, []) + + useEffect( + () => { + loadFromStorage() + }, + [loadFromStorage] + ) + + const onAlertClose = useCallback( + () => { + window.localStorage.setItem('hideInfoAlert', 'true') + loadFromStorage() + }, + [loadFromStorage] + ) const setNetworkData = (chainId: number) => { const network = chainId === home.chainId ? home.name : foreign.name @@ -80,6 +112,25 @@ export const MainPage = () => {
+ {showInfoAlert && ( + +

+ The Arbitrary Message Bridge Live Monitoring application provides real-time status updates for messages + bridged between {HOME_NETWORK_NAME} and {FOREIGN_NETWORK_NAME}. You can check current tx status, view + validator info, and troubleshoot potential issues with bridge transfers. +

+ + For more information refer to  + + the ALM documentation + + +
+ )} } /> ( + +) diff --git a/alm/src/components/commons/InfoAlert.tsx b/alm/src/components/commons/InfoAlert.tsx new file mode 100644 index 00000000..f5dbbfb7 --- /dev/null +++ b/alm/src/components/commons/InfoAlert.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import styled from 'styled-components' +import { InfoIcon } from './InfoIcon' +import { CloseIcon } from './CloseIcon' + +const StyledInfoAlert = styled.div` + border: 1px solid var(--button-color); + border-radius: 4px; + margin-bottom: 20px; + padding-top: 10px; +` + +const CloseIconContainer = styled.div` + cursor: pointer; +` + +const TextContainer = styled.div` + flex-direction: column; +` + +export const InfoAlert = ({ onClick, children }: { onClick: () => void; children: React.ReactChild[] }) => ( +
+ + + {children} + + + + +
+) diff --git a/alm/src/components/commons/InfoIcon.tsx b/alm/src/components/commons/InfoIcon.tsx new file mode 100644 index 00000000..f8d84f92 --- /dev/null +++ b/alm/src/components/commons/InfoIcon.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +export const InfoIcon = () => ( + +) From fdfa5cd7af51868e80108d37dbe6326077eaf78f Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 20 Jul 2020 18:44:29 -0300 Subject: [PATCH 11/12] Add new status for when there is no registered activity by validators (#401) --- alm/src/components/ConfirmationsContainer.tsx | 3 +- alm/src/components/ExecutionConfirmation.tsx | 20 +- .../components/ValidatorsConfirmations.tsx | 31 ++- alm/src/config/constants.ts | 3 + alm/src/config/descriptions.ts | 10 +- alm/src/hooks/useMessageConfirmations.ts | 7 +- .../__tests__/getConfirmationsForTx.test.ts | 207 ++++++++++++++++-- alm/src/utils/getConfirmationsForTx.ts | 13 +- 8 files changed, 259 insertions(+), 35 deletions(-) diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx index 437a69d5..4f0bffae 100644 --- a/alm/src/components/ConfirmationsContainer.tsx +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -49,7 +49,7 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp } } = useStateProvider() const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt }) const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt }) - const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({ + const { confirmations, status, executionData, signatureCollected, waitingBlocksResolved } = useMessageConfirmations({ message, receipt, fromHome, @@ -100,6 +100,7 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp } confirmations={confirmations} requiredSignatures={requiredSignatures} validatorList={validatorList} + waitingBlocksResolved={waitingBlocksResolved} /> {signatureCollected && } diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx index 17d78148..d1af0a90 100644 --- a/alm/src/components/ExecutionConfirmation.tsx +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -1,7 +1,7 @@ import React from 'react' import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { useWindowWidth } from '@react-hook/window-size' -import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { SimpleLoading } from './commons/Loading' import styled from 'styled-components' import { ExecutionData } from '../hooks/useMessageConfirmations' @@ -35,7 +35,11 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir case VALIDATOR_CONFIRMATION_STATUS.WAITING: return {validatorStatus} default: - return + return executionData.validator ? ( + {VALIDATOR_CONFIRMATION_STATUS.WAITING} + ) : ( + + ) } } @@ -54,9 +58,15 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir {formattedValidator ? formattedValidator : } {getExecutionStatusElement(executionData.status)} - - {executionData.timestamp > 0 ? formatTimestamp(executionData.timestamp) : ''} - + {executionData.timestamp > 0 ? ( + + {formatTimestamp(executionData.timestamp)} + + ) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? ( + '' + ) : ( + SEARCHING_TX + )} diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx index bcd08394..127effcf 100644 --- a/alm/src/components/ValidatorsConfirmations.tsx +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -1,7 +1,7 @@ import React from 'react' import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { useWindowWidth } from '@react-hook/window-size' -import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { SimpleLoading } from './commons/Loading' import styled from 'styled-components' import { ConfirmationParam } from '../hooks/useMessageConfirmations' @@ -17,12 +17,14 @@ export interface ValidatorsConfirmationsParams { confirmations: Array requiredSignatures: number validatorList: string[] + waitingBlocksResolved: boolean } export const ValidatorsConfirmations = ({ confirmations, requiredSignatures, - validatorList + validatorList, + waitingBlocksResolved }: ValidatorsConfirmationsParams) => { const windowWidth = useWindowWidth() @@ -37,7 +39,11 @@ export const ValidatorsConfirmations = ({ case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED: return {validatorStatus} default: - return + return waitingBlocksResolved ? ( + {VALIDATOR_CONFIRMATION_STATUS.WAITING} + ) : ( + + ) } } @@ -60,7 +66,12 @@ export const ValidatorsConfirmations = ({ const elementIfNoTimestamp = displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING && displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? ( - + (displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') && + waitingBlocksResolved ? ( + SEARCHING_TX + ) : ( + + ) ) : ( '' ) @@ -69,11 +80,13 @@ export const ValidatorsConfirmations = ({ {windowWidth < 850 ? formatTxHash(validator) : validator} {getValidatorStatusElement(displayedStatus)} - - {confirmation && confirmation.timestamp > 0 - ? formatTimestamp(confirmation.timestamp) - : elementIfNoTimestamp} - + {confirmation && confirmation.timestamp > 0 ? ( + + {formatTimestamp(confirmation.timestamp)} + + ) : ( + elementIfNoTimestamp + )} ) diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 5a745af7..6c2e2fba 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -45,6 +45,7 @@ export const CONFIRMATIONS_STATUS = { EXECUTION_WAITING: 'EXECUTION_WAITING', FAILED: 'FAILED', PENDING: 'PENDING', + SEARCHING: 'SEARCHING', WAITING_VALIDATORS: 'WAITING_VALIDATORS', WAITING_CHAIN: 'WAITING_CHAIN', UNDEFINED: 'UNDEFINED' @@ -58,3 +59,5 @@ export const VALIDATOR_CONFIRMATION_STATUS = { NOT_REQUIRED: 'Not required', UNDEFINED: 'UNDEFINED' } + +export const SEARCHING_TX = 'Searching Transaction...' diff --git a/alm/src/config/descriptions.ts b/alm/src/config/descriptions.ts index edc38270..d030db50 100644 --- a/alm/src/config/descriptions.ts +++ b/alm/src/config/descriptions.ts @@ -14,6 +14,7 @@ export const CONFIRMATIONS_STATUS_LABEL: { [key: string]: string } = { FAILED: 'Failed', PENDING: 'Pending', WAITING_VALIDATORS: 'Waiting', + SEARCHING: 'Waiting', WAITING_CHAIN: 'Waiting' } @@ -26,6 +27,7 @@ export const CONFIRMATIONS_STATUS_LABEL_HOME: { [key: string]: string } = { FAILED: 'Confirmation Failed', PENDING: 'Confirmation Pending', WAITING_VALIDATORS: 'Confirmation Waiting', + SEARCHING: 'Confirmation Waiting', WAITING_CHAIN: 'Confirmation Waiting' } @@ -39,7 +41,9 @@ export const CONFIRMATIONS_STATUS_DESCRIPTION: { [key: string]: string } = { PENDING: 'The specified transaction was included in a block. A\nmajority of validators sent confirmations which have\nnot yet been added to a block.', WAITING_VALIDATORS: - 'The specified transaction was included in a block.\nSome validators have sent confirmations, others are\nwaiting for chain finalization.', + 'The specified transaction was included in a block.\nSome validators have sent confirmations, others are\nwaiting for chain finalization.\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', + SEARCHING: + 'The specified transaction was included in a block. The app is looking for confirmations. Either\n1. Validators are waiting for chain finalization before sending their signatures.\n2. Validators are not active.\n3. The bridge was stopped.\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', WAITING_CHAIN: 'The specified transaction was included in a block.\nValidators are waiting for chain finalization before\nsending their confirmations.' } @@ -60,7 +64,9 @@ export const CONFIRMATIONS_STATUS_DESCRIPTION_HOME: { [key: string]: string } = PENDING: 'The specified transaction was included in a block.\nA majority of validators sent signatures which have not\nyet been added to a block.', WAITING_VALIDATORS: - 'The specified transaction was included in a block.\nSome validators have sent signatures, others are\nwaiting for chain finalization.', + 'The specified transaction was included in a block.\nSome validators have sent signatures, others are\nwaiting for chain finalization.\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', + SEARCHING: + 'The specified transaction was included in a block. The app is looking for confirmations. Either\n1. Validators are waiting for chain finalization before sending their signatures.\n2. Validators are not active.\n3. The bridge was stopped.\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', WAITING_CHAIN: 'The specified transaction was included in a block.\nValidators are waiting for chain finalization\nbefore sending their signatures.' } diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index e79c8389..c470185c 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -329,7 +329,7 @@ export const useMessageConfirmations = ({ } else if (waitingBlocksForExecutionResolved) { setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING) } else { - setStatus(CONFIRMATIONS_STATUS.UNDEFINED) + setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING) } } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) @@ -342,6 +342,8 @@ export const useMessageConfirmations = ({ setStatus(CONFIRMATIONS_STATUS.PENDING) } else if (waitingBlocksResolved && existsConfirmation(confirmations)) { setStatus(CONFIRMATIONS_STATUS.WAITING_VALIDATORS) + } else if (waitingBlocksResolved) { + setStatus(CONFIRMATIONS_STATUS.SEARCHING) } else { setStatus(CONFIRMATIONS_STATUS.UNDEFINED) } @@ -366,6 +368,7 @@ export const useMessageConfirmations = ({ confirmations, status, signatureCollected, - executionData + executionData, + waitingBlocksResolved } } diff --git a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts index 4a91266b..67aa5fa5 100644 --- a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts +++ b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts @@ -104,12 +104,15 @@ describe('getConfirmationsForTx', () => { expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) expect(getValidatorFailedTransaction).toBeCalledTimes(1) - expect(setFailedConfirmations).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(1) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) expect(getValidatorPendingTransaction).toBeCalledTimes(0) - expect(setPendingConfirmations).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(1) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ @@ -179,13 +182,16 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorSuccessTransaction).toBeCalledTimes(1) - expect(setSignatureCollected).toBeCalledTimes(0) + expect(setSignatureCollected).toBeCalledTimes(1) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(getValidatorFailedTransaction).toBeCalledTimes(1) - expect(setFailedConfirmations).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(1) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) expect(getValidatorPendingTransaction).toBeCalledTimes(1) - expect(setPendingConfirmations).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(1) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) }) test('should set validator confirmations status, validator transactions and not retry', async () => { getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ @@ -242,12 +248,15 @@ describe('getConfirmationsForTx', () => { expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) expect(getValidatorFailedTransaction).toBeCalledTimes(1) - expect(setFailedConfirmations).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(1) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) expect(getValidatorPendingTransaction).toBeCalledTimes(0) - expect(setPendingConfirmations).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(1) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ @@ -327,12 +336,15 @@ describe('getConfirmationsForTx', () => { expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) expect(getValidatorFailedTransaction).toBeCalledTimes(1) - expect(setFailedConfirmations).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(1) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) expect(getValidatorPendingTransaction).toBeCalledTimes(0) - expect(setPendingConfirmations).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(1) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ @@ -414,13 +426,16 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorSuccessTransaction).toBeCalledTimes(1) - expect(setSignatureCollected).toBeCalledTimes(0) + expect(setSignatureCollected).toBeCalledTimes(1) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(getValidatorFailedTransaction).toBeCalledTimes(1) - expect(setFailedConfirmations).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(1) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true) expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ @@ -498,13 +513,16 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorSuccessTransaction).toBeCalledTimes(1) - expect(setSignatureCollected).toBeCalledTimes(0) + expect(setSignatureCollected).toBeCalledTimes(1) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true) expect(getValidatorPendingTransaction).toBeCalledTimes(1) - expect(setPendingConfirmations).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(1) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false) expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ @@ -521,4 +539,167 @@ describe('getConfirmationsForTx', () => { ]) ) }) + test('should remove pending state after transaction mined', async () => { + // Validator1 success + // Validator2 failed + // Validator3 Pending + + getValidatorConfirmation + .mockImplementationOnce(() => async (validator: string) => ({ + validator, + status: + validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + .mockImplementation(() => async (validator: string) => ({ + validator, + status: + validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction + .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator === validator1 ? '0x123' : '', + timestamp: validatorData.validator === validator1 ? 123 : 0 + })) + .mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator !== validator2 ? '0x123' : '', + timestamp: validatorData.validator !== validator2 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator2 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator2 ? '0x123' : '', + timestamp: validatorData.validator === validator2 ? 123 : 0 + })) + getValidatorPendingTransaction + .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator3 + ? VALIDATOR_CONFIRMATION_STATUS.PENDING + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator3 ? '0x123' : '', + timestamp: validatorData.validator === validator3 ? 123 : 0 + })) + .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(1) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(1) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(1) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true) + + expect(setResult.mock.calls[0][0]()).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.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.PENDING, txHash: '0x123', timestamp: 123 } + ]) + ) + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(setResult).toBeCalledTimes(4) + expect(getValidatorConfirmation).toBeCalledTimes(2) + expect(getValidatorSuccessTransaction).toBeCalledTimes(2) + expect(setSignatureCollected).toBeCalledTimes(2) + expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) + expect(setSignatureCollected.mock.calls[1][0]).toEqual(true) + + expect(getValidatorFailedTransaction).toBeCalledTimes(2) + expect(setFailedConfirmations).toBeCalledTimes(2) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) + expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(2) + expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true) + expect(setPendingConfirmations.mock.calls[1][0]).toEqual(false) + + expect(setResult.mock.calls[2][0]()).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.SUCCESS } + ]) + ) + expect(setResult.mock.calls[3][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 } + ]) + ) + }) }) diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index ab1f423e..6140868d 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -59,6 +59,7 @@ export const getConfirmationsForTx = async ( const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS) // If signatures not collected, look for pending transactions + let pendingConfirmationsResult = false if (successConfirmations.length !== requiredSignatures) { // Check if confirmation is pending const validatorPendingConfirmationsChecks = await Promise.all( @@ -74,7 +75,7 @@ export const getConfirmationsForTx = async ( }) if (validatorPendingConfirmations.length > 0) { - setPendingConfirmations(true) + pendingConfirmationsResult = true } } @@ -83,6 +84,7 @@ export const getConfirmationsForTx = async ( ) // Check if confirmation failed + let failedConfirmationsResult = false const validatorFailedConfirmationsChecks = await Promise.all( undefinedConfirmations.map( getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) @@ -97,7 +99,7 @@ export const getConfirmationsForTx = async ( }) const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures if (messageConfirmationsFailed) { - setFailedConfirmations(true) + failedConfirmationsResult = true } const missingConfirmations = validatorConfirmations.filter( @@ -108,6 +110,7 @@ export const getConfirmationsForTx = async ( shouldRetry = true } + let signatureCollectedResult = false if (successConfirmations.length === requiredSignatures) { // If signatures collected, it should set other signatures not found as not required const notRequiredConfirmations = missingConfirmations.map(c => ({ @@ -116,7 +119,7 @@ export const getConfirmationsForTx = async ( })) validatorConfirmations = [...validatorConfirmations, ...notRequiredConfirmations] - setSignatureCollected(true) + signatureCollectedResult = true } // get transactions from success signatures @@ -137,7 +140,11 @@ export const getConfirmationsForTx = async ( }) } + // 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) { From 7a4849511884fc31478ef4e953cee15702c40515 Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Wed, 22 Jul 2020 23:24:47 +0300 Subject: [PATCH 12/12] Update the contract's submodule to the release 5.2.0 (#406) --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index d67761d9..f405ba9e 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit d67761d93855a2693b719dd619a400b9302b8952 +Subproject commit f405ba9e56ca447b84ee5d53e93ac919b8f8565f