Compare commits
10 Commits
monitor-ou
...
2.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0451d6e373 | ||
|
|
409044b8a5 | ||
|
|
5fc52f42d7 | ||
|
|
8a0d9f38b0 | ||
|
|
1aee0a84ef | ||
|
|
811b1a27f1 | ||
|
|
4d468ae107 | ||
|
|
4497a024b1 | ||
|
|
6ce98ff3dd | ||
|
|
04f66b243c |
22
.github/workflows/main.yml
vendored
22
.github/workflows/main.yml
vendored
@@ -182,6 +182,7 @@ jobs:
|
||||
deployment:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-e2e-images
|
||||
- build-molecule-runner
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -197,11 +198,11 @@ jobs:
|
||||
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
||||
- run: deployment-e2e/molecule.sh ${{ matrix.task }}
|
||||
ultimate:
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags') || contains(github.event.head_commit.message, 'ultimate')
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- initialize
|
||||
- build-e2e-images
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -209,12 +210,16 @@ jobs:
|
||||
include:
|
||||
- task: erc-to-erc
|
||||
ui-e2e-grep: 'ERC TO ERC'
|
||||
ui-config: 'e2e-commons/components-envs/ui-erc20.env'
|
||||
- task: erc-to-native
|
||||
ui-e2e-grep: 'ERC TO NATIVE'
|
||||
ui-config: 'e2e-commons/components-envs/ui-erc20-native.env'
|
||||
- task: native-to-erc
|
||||
ui-e2e-grep: 'NATIVE TO ERC'
|
||||
ui-config: 'e2e-commons/components-envs/ui.env'
|
||||
- task: amb-stake-erc-to-erc
|
||||
ui-e2e-grep: 'AMB-STAKE-ERC-TO-ERC'
|
||||
ui-config: 'e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
@@ -240,7 +245,20 @@ jobs:
|
||||
- name: Deploy contracts
|
||||
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks
|
||||
- name: Pull e2e oracle image
|
||||
run: docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
|
||||
run: |
|
||||
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
|
||||
docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
|
||||
- if: ${{ matrix.ui-e2e-grep }}
|
||||
name: Pull e2e ui image
|
||||
run: |
|
||||
docker-compose -f ./e2e-commons/docker-compose.yml pull ui
|
||||
docker build \
|
||||
--build-arg DOCKER_IMAGE_BASE=${DOCKER_IMAGE_BASE} \
|
||||
--build-arg UI_TAG=${UI_TAG} \
|
||||
--build-arg DOT_ENV_PATH=${{ matrix.ui-config }} \
|
||||
-f ./e2e-commons/Dockerfile.ui \
|
||||
-t ui_ui:latest \
|
||||
.
|
||||
- name: Deploy oracle and ui
|
||||
run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
|
||||
- name: Reset docker socket permissions
|
||||
|
||||
@@ -82,3 +82,5 @@ MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for ot
|
||||
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST | File with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests should be automatically processed by the CollectedSignatures watcher. | string
|
||||
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST | File with a list of addresses, separated by newlines. If set, determines the set of accounts whose requests should be marked as unclaimed. Has a lower priority than the `MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST`. | string
|
||||
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
|
||||
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE | If set, defines the list of home validator addresses for which balance should be checked. | `string`
|
||||
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE | If set, defines the list of foreign validator addresses for which balance should be checked. | `string`
|
||||
|
||||
@@ -44,12 +44,13 @@ export interface ConfirmationsContainerParams {
|
||||
|
||||
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => {
|
||||
const {
|
||||
home: { name: homeName, confirmations },
|
||||
home: { name: homeName },
|
||||
foreign: { name: foreignName }
|
||||
} = useStateProvider()
|
||||
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
|
||||
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
|
||||
const {
|
||||
confirmations,
|
||||
status,
|
||||
executionData,
|
||||
signatureCollected,
|
||||
@@ -115,7 +116,7 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
|
||||
messageData={message.data}
|
||||
executionData={executionData}
|
||||
isHome={!fromHome}
|
||||
requiredSignatures={requiredSignatures}
|
||||
signatureCollected={signatureCollected}
|
||||
setExecutionData={setExecutionData}
|
||||
executionEventsFetched={executionEventsFetched}
|
||||
setPendingExecution={setPendingExecution}
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface ExecutionConfirmationParams {
|
||||
messageData: string
|
||||
executionData: ExecutionData
|
||||
setExecutionData: Function
|
||||
requiredSignatures: number
|
||||
signatureCollected: boolean | string[]
|
||||
isHome: boolean
|
||||
executionEventsFetched: boolean
|
||||
setPendingExecution: Function
|
||||
@@ -28,7 +28,7 @@ export const ExecutionConfirmation = ({
|
||||
messageData,
|
||||
executionData,
|
||||
setExecutionData,
|
||||
requiredSignatures,
|
||||
signatureCollected,
|
||||
isHome,
|
||||
executionEventsFetched,
|
||||
setPendingExecution
|
||||
@@ -105,7 +105,7 @@ export const ExecutionConfirmation = ({
|
||||
<ManualExecutionButton
|
||||
messageData={messageData}
|
||||
setExecutionData={setExecutionData}
|
||||
requiredSignatures={requiredSignatures}
|
||||
signatureCollected={signatureCollected as string[]}
|
||||
setPendingExecution={setPendingExecution}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -18,21 +18,19 @@ const StyledButton = styled.button`
|
||||
interface ManualExecutionButtonParams {
|
||||
messageData: string
|
||||
setExecutionData: Function
|
||||
requiredSignatures: number
|
||||
signatureCollected: string[]
|
||||
setPendingExecution: Function
|
||||
}
|
||||
|
||||
export const ManualExecutionButton = ({
|
||||
messageData,
|
||||
setExecutionData,
|
||||
requiredSignatures,
|
||||
signatureCollected,
|
||||
setPendingExecution
|
||||
}: ManualExecutionButtonParams) => {
|
||||
const { home, foreign, setError } = useStateProvider()
|
||||
const { foreign, setError } = useStateProvider()
|
||||
const { library, activate, account, active } = useWeb3React()
|
||||
const [manualExecution, setManualExecution] = useState(false)
|
||||
const disabled =
|
||||
home.confirmations.filter(({ signature }) => signature && signature.startsWith('0x')).length < requiredSignatures
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@@ -60,12 +58,9 @@ export const ManualExecutionButton = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (!library || !foreign.bridgeContract || !home.confirmations) return
|
||||
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
|
||||
|
||||
const collectedSignatures = home.confirmations
|
||||
.map(confirmation => confirmation.signature!)
|
||||
.filter(signature => signature && signature.startsWith('0x'))
|
||||
const signatures = packSignatures(collectedSignatures.map(signatureToVRS))
|
||||
const signatures = packSignatures(signatureCollected.map(signatureToVRS))
|
||||
const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI()
|
||||
setManualExecution(false)
|
||||
|
||||
@@ -98,7 +93,7 @@ export const ManualExecutionButton = ({
|
||||
foreign.bridgeContract,
|
||||
setError,
|
||||
messageData,
|
||||
home.confirmations,
|
||||
signatureCollected,
|
||||
setExecutionData,
|
||||
setPendingExecution
|
||||
]
|
||||
@@ -106,7 +101,7 @@ export const ManualExecutionButton = ({
|
||||
|
||||
return (
|
||||
<div className="is-center">
|
||||
<StyledButton disabled={disabled} className="button outline" onClick={() => setManualExecution(true)}>
|
||||
<StyledButton className="button outline" onClick={() => setManualExecution(true)}>
|
||||
Execute
|
||||
</StyledButton>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,6 @@ export interface BasicConfirmationParam {
|
||||
export interface ConfirmationParam extends BasicConfirmationParam {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ExecutionData {
|
||||
@@ -63,7 +62,7 @@ export const useMessageConfirmations = ({
|
||||
blockConfirmations
|
||||
}: useMessageConfirmationsParams) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const { confirmations, setConfirmations } = home
|
||||
const [confirmations, setConfirmations] = useState([])
|
||||
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
const [waitingBlocks, setWaitingBlocks] = useState(false)
|
||||
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
|
||||
@@ -94,7 +93,7 @@ export const useMessageConfirmations = ({
|
||||
// Check if the validators are waiting for block confirmations to verify the message
|
||||
useEffect(
|
||||
() => {
|
||||
if (!receipt || !blockConfirmations) return
|
||||
if (!receipt || !blockConfirmations || waitingBlocksResolved) return
|
||||
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
@@ -127,7 +126,16 @@ export const useMessageConfirmations = ({
|
||||
blockProvider.stop()
|
||||
}
|
||||
},
|
||||
[blockConfirmations, foreign.web3, fromHome, validatorList, home.web3, receipt, setConfirmations]
|
||||
[
|
||||
blockConfirmations,
|
||||
foreign.web3,
|
||||
fromHome,
|
||||
validatorList,
|
||||
home.web3,
|
||||
receipt,
|
||||
setConfirmations,
|
||||
waitingBlocksResolved
|
||||
]
|
||||
)
|
||||
|
||||
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
|
||||
@@ -174,6 +182,7 @@ export const useMessageConfirmations = ({
|
||||
useEffect(
|
||||
() => {
|
||||
if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent || !blockConfirmations) return
|
||||
if (waitingBlocksForExecutionResolved) return
|
||||
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
@@ -202,7 +211,7 @@ export const useMessageConfirmations = ({
|
||||
homeBlockNumberProvider.stop()
|
||||
}
|
||||
},
|
||||
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt]
|
||||
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt, waitingBlocksForExecutionResolved]
|
||||
)
|
||||
|
||||
// Checks if validators verified the message
|
||||
@@ -366,6 +375,7 @@ export const useMessageConfirmations = ({
|
||||
)
|
||||
|
||||
return {
|
||||
confirmations,
|
||||
status,
|
||||
signatureCollected,
|
||||
executionData,
|
||||
|
||||
@@ -12,7 +12,6 @@ import Web3 from 'web3'
|
||||
import { useBridgeContracts } from '../hooks/useBridgeContracts'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { foreignSnapshotProvider, homeSnapshotProvider } from '../services/SnapshotProvider'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
export interface BaseNetworkParams {
|
||||
chainId: number
|
||||
@@ -22,13 +21,8 @@ export interface BaseNetworkParams {
|
||||
bridgeContract: Maybe<Contract>
|
||||
}
|
||||
|
||||
export interface HomeNetworkParams extends BaseNetworkParams {
|
||||
confirmations: Array<ConfirmationParam>
|
||||
setConfirmations: Function
|
||||
}
|
||||
|
||||
export interface StateContext {
|
||||
home: HomeNetworkParams
|
||||
home: BaseNetworkParams
|
||||
foreign: BaseNetworkParams
|
||||
loading: boolean
|
||||
error: string
|
||||
@@ -41,9 +35,7 @@ const initialState = {
|
||||
name: '',
|
||||
web3: null,
|
||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
bridgeContract: null,
|
||||
confirmations: [],
|
||||
setConfirmations: () => null
|
||||
bridgeContract: null
|
||||
},
|
||||
foreign: {
|
||||
chainId: 0,
|
||||
@@ -66,7 +58,6 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
homeWeb3: homeNetwork.web3,
|
||||
foreignWeb3: foreignNetwork.web3
|
||||
})
|
||||
const [confirmations, setConfirmations] = useState([])
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const value = {
|
||||
@@ -74,8 +65,6 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
name: HOME_NETWORK_NAME,
|
||||
bridgeContract: homeBridge,
|
||||
confirmations,
|
||||
setConfirmations,
|
||||
...homeNetwork
|
||||
},
|
||||
foreign: {
|
||||
|
||||
@@ -24,7 +24,15 @@ const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
||||
const validatorList = [validator1, validator2, validator3]
|
||||
const bridgeContract = {} as Contract
|
||||
const signature =
|
||||
'0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4'
|
||||
const bridgeContract = {
|
||||
methods: {
|
||||
signature: () => ({
|
||||
call: () => signature
|
||||
})
|
||||
}
|
||||
} as Contract
|
||||
const requiredSignatures = 2
|
||||
const waitingBlocksResolved = true
|
||||
let subscriptions: Array<number> = []
|
||||
@@ -102,8 +110,9 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@@ -113,14 +122,16 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
expect(res1).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
@@ -178,7 +189,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(setResult).toBeCalledTimes(1)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@@ -243,11 +254,12 @@ describe('getConfirmationsForTx', () => {
|
||||
unsubscribe()
|
||||
|
||||
expect(subscriptions.length).toEqual(0)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(setResult).toBeCalledTimes(3)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@@ -257,14 +269,24 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
expect(res1).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
@@ -331,11 +353,12 @@ describe('getConfirmationsForTx', () => {
|
||||
unsubscribe()
|
||||
|
||||
expect(subscriptions.length).toEqual(0)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@@ -345,7 +368,27 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
const res4 = setResult.mock.calls[3][0](res3)
|
||||
expect(res1).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
@@ -353,7 +396,7 @@ describe('getConfirmationsForTx', () => {
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
@@ -422,7 +465,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@@ -436,14 +479,32 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
const res4 = setResult.mock.calls[3][0](res3)
|
||||
expect(res1).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
@@ -509,7 +570,7 @@ describe('getConfirmationsForTx', () => {
|
||||
unsubscribe()
|
||||
|
||||
expect(subscriptions.length).toEqual(0)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(setResult).toBeCalledTimes(3)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@@ -523,14 +584,24 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
expect(res1).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
@@ -619,7 +690,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@@ -633,14 +704,32 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
const res4 = setResult.mock.calls[3][0](res3)
|
||||
expect(res1).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
@@ -669,12 +758,13 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(setResult).toBeCalledTimes(7)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(2)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected).toBeCalledTimes(3)
|
||||
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
|
||||
expect(setSignatureCollected.mock.calls[1][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[2][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(2)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(2)
|
||||
@@ -686,14 +776,24 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
expect(setPendingConfirmations.mock.calls[1][0]).toEqual(false)
|
||||
|
||||
expect(setResult.mock.calls[2][0]()).toEqual(
|
||||
const res5 = setResult.mock.calls[4][0](res4)
|
||||
const res6 = setResult.mock.calls[5][0](res5)
|
||||
const res7 = setResult.mock.calls[6][0](res6)
|
||||
expect(res5).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
|
||||
])
|
||||
)
|
||||
expect(setResult.mock.calls[3][0]).toEqual(
|
||||
expect(res6).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
|
||||
])
|
||||
)
|
||||
expect(res7).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
|
||||
@@ -15,13 +15,19 @@ export const checkWaitingBlocksForExecution = async (
|
||||
const currentBlock = blockProvider.get()
|
||||
|
||||
if (currentBlock && currentBlock >= targetBlock) {
|
||||
setExecutionData({
|
||||
const undefinedExecutionState = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
})
|
||||
}
|
||||
setExecutionData(
|
||||
(data: any) =>
|
||||
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
|
||||
? undefinedExecutionState
|
||||
: data
|
||||
)
|
||||
setWaitingBlocksForExecutionResolved(true)
|
||||
setWaitingBlocksForExecution(false)
|
||||
blockProvider.stop()
|
||||
@@ -31,13 +37,20 @@ export const checkWaitingBlocksForExecution = async (
|
||||
nextInterval = 500
|
||||
} else {
|
||||
setWaitingBlocksForExecution(true)
|
||||
setExecutionData({
|
||||
const waitingExecutionState = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
|
||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
})
|
||||
}
|
||||
setExecutionData(
|
||||
(data: any) =>
|
||||
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
|
||||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
|
||||
? waitingExecutionState
|
||||
: data
|
||||
)
|
||||
}
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
|
||||
@@ -14,7 +14,23 @@ import {
|
||||
getValidatorPendingTransaction,
|
||||
getSuccessExecutionTransaction
|
||||
} from './validatorConfirmationHelpers'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => {
|
||||
const confirmations = [...oldConfirmations]
|
||||
newConfirmations.forEach(validatorData => {
|
||||
const index = confirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
const currentStatus = confirmations[index].status
|
||||
const newStatus = validatorData.status
|
||||
if (
|
||||
(validatorData as ConfirmationParam).txHash ||
|
||||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
|
||||
) {
|
||||
confirmations[index] = validatorData
|
||||
}
|
||||
})
|
||||
return confirmations
|
||||
}
|
||||
|
||||
export const getConfirmationsForTx = async (
|
||||
messageData: string,
|
||||
@@ -38,48 +54,45 @@ export const getConfirmationsForTx = async (
|
||||
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
|
||||
// If all the information was not collected, then it should retry
|
||||
let shouldRetry = false
|
||||
const hashMsg = web3.utils.soliditySha3Raw(messageData)
|
||||
let validatorConfirmations = await Promise.all(
|
||||
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod))
|
||||
)
|
||||
|
||||
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
|
||||
setResult((prevConfirmations: ConfirmationParam[]) => {
|
||||
if (prevConfirmations && prevConfirmations.length) {
|
||||
successConfirmations.forEach(validatorData => {
|
||||
const index = prevConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
return prevConfirmations
|
||||
} else {
|
||||
return validatorConfirmations
|
||||
const updateConfirmations = (confirmations: BasicConfirmationParam[]) => {
|
||||
if (confirmations.length === 0) {
|
||||
return
|
||||
}
|
||||
})
|
||||
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
|
||||
setResult((currentConfirmations: BasicConfirmationParam[]) => {
|
||||
if (currentConfirmations && currentConfirmations.length) {
|
||||
return mergeConfirmations(currentConfirmations, confirmations)
|
||||
}
|
||||
return confirmations
|
||||
})
|
||||
}
|
||||
|
||||
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const hasEnoughSignatures = successConfirmations.length === requiredSignatures
|
||||
|
||||
updateConfirmations(validatorConfirmations)
|
||||
setSignatureCollected(hasEnoughSignatures)
|
||||
|
||||
// If signatures not collected, look for pending transactions
|
||||
let pendingConfirmationsResult = false
|
||||
if (successConfirmations.length !== requiredSignatures) {
|
||||
if (!hasEnoughSignatures) {
|
||||
// Check if confirmation is pending
|
||||
const validatorPendingConfirmationsChecks = await Promise.all(
|
||||
notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions))
|
||||
)
|
||||
|
||||
const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
|
||||
validatorPendingConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
|
||||
if (validatorPendingConfirmations.length > 0) {
|
||||
pendingConfirmationsResult = true
|
||||
}
|
||||
updateConfirmations(validatorPendingConfirmations)
|
||||
setPendingConfirmations(validatorPendingConfirmations.length > 0)
|
||||
} else {
|
||||
setPendingConfirmations(false)
|
||||
}
|
||||
|
||||
const undefinedConfirmations = validatorConfirmations.filter(
|
||||
@@ -87,7 +100,6 @@ export const getConfirmationsForTx = async (
|
||||
)
|
||||
|
||||
// Check if confirmation failed
|
||||
let failedConfirmationsResult = false
|
||||
const validatorFailedConfirmationsChecks = await Promise.all(
|
||||
undefinedConfirmations.map(
|
||||
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions)
|
||||
@@ -96,70 +108,47 @@ export const getConfirmationsForTx = async (
|
||||
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
)
|
||||
validatorFailedConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures
|
||||
if (messageConfirmationsFailed) {
|
||||
failedConfirmationsResult = true
|
||||
}
|
||||
setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures)
|
||||
updateConfirmations(validatorFailedConfirmations)
|
||||
|
||||
const missingConfirmations = validatorConfirmations.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
|
||||
if (successConfirmations.length !== requiredSignatures && missingConfirmations.length > 0) {
|
||||
shouldRetry = true
|
||||
}
|
||||
|
||||
let signatureCollectedResult = false
|
||||
if (successConfirmations.length === requiredSignatures) {
|
||||
if (hasEnoughSignatures) {
|
||||
// If signatures collected, it should set other signatures not found as not required
|
||||
const notRequiredConfirmations = missingConfirmations.map(c => ({
|
||||
validator: c.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
|
||||
}))
|
||||
updateConfirmations(notRequiredConfirmations)
|
||||
|
||||
notRequiredConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
signatureCollectedResult = true
|
||||
if (fromHome) {
|
||||
// fetch collected signatures for possible manual processing
|
||||
setSignatureCollected(
|
||||
await Promise.all(
|
||||
Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// get transactions from success signatures
|
||||
const successConfirmationWithData = await Promise.all(
|
||||
validatorConfirmations
|
||||
.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
.map(
|
||||
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, timestamp, getSuccessTransactions)
|
||||
)
|
||||
successConfirmations.map(
|
||||
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, timestamp, getSuccessTransactions)
|
||||
)
|
||||
)
|
||||
|
||||
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
|
||||
updateConfirmations(successConfirmationWithTxFound)
|
||||
|
||||
const updatedValidatorConfirmations = [...validatorConfirmations]
|
||||
|
||||
if (successConfirmationWithTxFound.length > 0) {
|
||||
successConfirmationWithTxFound.forEach(validatorData => {
|
||||
const index = updatedValidatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
updatedValidatorConfirmations[index] = validatorData
|
||||
})
|
||||
}
|
||||
|
||||
// Set results
|
||||
setResult(updatedValidatorConfirmations)
|
||||
setFailedConfirmations(failedConfirmationsResult)
|
||||
setPendingConfirmations(pendingConfirmationsResult)
|
||||
setSignatureCollected(signatureCollectedResult)
|
||||
|
||||
// Retry if not all transaction were found for validator confirmations
|
||||
if (successConfirmationWithTxFound.length < successConfirmationWithData.length) {
|
||||
shouldRetry = true
|
||||
}
|
||||
|
||||
if (shouldRetry) {
|
||||
// retry if not all signatures are collected and some confirmations are still missing
|
||||
// or some success transactions were not fetched successfully
|
||||
if (
|
||||
(!hasEnoughSignatures && missingConfirmations.length > 0) ||
|
||||
successConfirmationWithTxFound.length < successConfirmationWithData.length
|
||||
) {
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
getConfirmationsForTx(
|
||||
|
||||
@@ -71,24 +71,18 @@ export const getSuccessExecutionTransaction = (
|
||||
|
||||
let txHashTimestamp = 0
|
||||
let txHash = ''
|
||||
let signature = ''
|
||||
const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
|
||||
if (transactions.length > 0) {
|
||||
const tx = transactions[0]
|
||||
txHashTimestamp = parseInt(tx.timeStamp)
|
||||
txHash = tx.hash
|
||||
if (fromHome) {
|
||||
const decoded = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${tx.input.substr(10)}`)
|
||||
signature = decoded[0]
|
||||
}
|
||||
|
||||
// cache the result
|
||||
validatorsCache.setData(validatorCacheKey, {
|
||||
validator,
|
||||
status,
|
||||
txHash,
|
||||
signature,
|
||||
timestamp: txHashTimestamp
|
||||
})
|
||||
}
|
||||
@@ -97,7 +91,6 @@ export const getSuccessExecutionTransaction = (
|
||||
validator,
|
||||
status,
|
||||
txHash,
|
||||
signature,
|
||||
timestamp: txHashTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@ function strip0x(input) {
|
||||
return input.replace(/^0x/, '')
|
||||
}
|
||||
|
||||
function addTxHashToData({ encodedData, transactionHash }) {
|
||||
return encodedData.slice(0, 2) + strip0x(transactionHash) + encodedData.slice(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the datatype byte from the AMB message.
|
||||
* First (the most significant bit) denotes if the message should be forwarded to the manual lane.
|
||||
@@ -33,8 +29,18 @@ function parseAMBMessage(message) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addTxHashToData,
|
||||
parseAMBMessage,
|
||||
strip0x
|
||||
const normalizeAMBMessageEvent = e => {
|
||||
let msgData = e.returnValues.encodedData
|
||||
if (!e.returnValues.messageId) {
|
||||
// append tx hash to an old message, where message id was not used
|
||||
// for old messages, e.messageId is a corresponding transactionHash
|
||||
msgData = e.transactionHash + msgData.slice(2)
|
||||
}
|
||||
return parseAMBMessage(msgData)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
strip0x,
|
||||
parseAMBMessage,
|
||||
normalizeAMBMessageEvent
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { BN } = require('web3-utils')
|
||||
const { expect } = require('chai').use(require('bn-chai')(BN))
|
||||
const { parseAMBMessage, strip0x, addTxHashToData } = require('../message')
|
||||
const { parseAMBMessage, strip0x } = require('../message')
|
||||
|
||||
describe('strip0x', () => {
|
||||
it('should remove 0x from input', () => {
|
||||
@@ -24,28 +24,6 @@ describe('strip0x', () => {
|
||||
expect(result).to.be.equal(input)
|
||||
})
|
||||
})
|
||||
describe('addTxHashToData', () => {
|
||||
it('should add txHash to encoded data at position 2', () => {
|
||||
// Given
|
||||
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
|
||||
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
|
||||
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
|
||||
const msgDataType = '00'
|
||||
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
|
||||
const encodedData = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
|
||||
|
||||
const transactionHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
|
||||
const message = `0x${strip0x(transactionHash)}${strip0x(msgSender)}${strip0x(
|
||||
msgExecutor
|
||||
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
|
||||
|
||||
// When
|
||||
const result = addTxHashToData({ encodedData, transactionHash })
|
||||
|
||||
// Then
|
||||
expect(result).to.be.equal(message)
|
||||
})
|
||||
})
|
||||
describe('parseAMBMessage', () => {
|
||||
it('should parse data type 00', () => {
|
||||
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
|
||||
|
||||
@@ -79,67 +79,6 @@ const homeV1Abi = [
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'foreignDailyLimit',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'foreignMaxPerTx',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getCurrentDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'totalExecutedPerDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -243,67 +182,6 @@ const foreignViAbi = [
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'homeDailyLimit',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'homeMaxPerTx',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getCurrentDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'totalExecutedPerDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-amb-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
ui-amb-stake-erc-to-erc-host:
|
||||
|
||||
@@ -21,8 +21,6 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-amb-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
verifier:
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
---
|
||||
- import_playbook: ../../../deployment/site.yml
|
||||
# The docker-compose files have to be modified, in order to join the docker containers over network with the parity containers
|
||||
- import_playbook: ./oracle-docker-compose.yml
|
||||
- import_playbook: ./ui-docker-compose.yml
|
||||
- import_playbook: ../../../deployment/site.yml
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "/home/poadocker/bridge/oracle/{{ file }}.yml"
|
||||
register: docker_compose_slurp
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Add the external network used to connect to Parity nodes
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
|
||||
|
||||
- name: Add all Oracle containers to the network
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': docker_compose_parsed.services[item].networks | union(['ultimate'])}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Expose Redis port to allow connecting from redis-cli
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {'redis': {'ports': ['6379:6379']}}}, recursive=True) }}"
|
||||
|
||||
- name: Write updated docker file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "/home/poadocker/bridge/oracle/{{ file }}.yml"
|
||||
@@ -1,33 +1,22 @@
|
||||
---
|
||||
- name: Overwrite Oracle the docker-compose
|
||||
- name: Prepare Oracle for ultimate tests
|
||||
hosts: oracle
|
||||
become: true
|
||||
tasks:
|
||||
- name: stop the service
|
||||
shell: service poabridge stop
|
||||
|
||||
- name: ReTag current oracle image
|
||||
shell: docker tag $(docker images --format '{{ '{{' }}.Repository{{ '}}' }}:{{ '{{' }}.Tag{{ '}}' }}' | grep -m 1 tokenbridge-e2e-oracle) oracle:ultimate-testing
|
||||
- name: Connect parity to oracle networks
|
||||
shell: "docker network create {{ item }} && docker network connect {{ item }} parity1 && docker network connect {{ item }} parity2"
|
||||
with_items:
|
||||
- oracle_net_db_bridge_request
|
||||
- oracle_net_db_bridge_collected
|
||||
- oracle_net_db_bridge_affirmation
|
||||
- oracle_net_db_bridge_transfer
|
||||
- oracle_net_db_bridge_senderhome
|
||||
- oracle_net_db_bridge_senderforeign
|
||||
- oracle_net_rabbit_bridge_request
|
||||
- oracle_net_rabbit_bridge_collected
|
||||
- oracle_net_rabbit_bridge_affirmation
|
||||
- oracle_net_rabbit_bridge_transfer
|
||||
- oracle_net_rabbit_bridge_senderhome
|
||||
- oracle_net_rabbit_bridge_senderforeign
|
||||
delegate_to: 127.0.0.1
|
||||
become: false
|
||||
|
||||
- name: Replace oracle image
|
||||
replace:
|
||||
path: "/home/poadocker/bridge/oracle/{{ item }}.yml"
|
||||
regexp: 'poanetwork/tokenbridge-oracle:latest'
|
||||
replace: "oracle:ultimate-testing"
|
||||
with_items:
|
||||
- docker-compose
|
||||
- docker-compose-transfer
|
||||
- docker-compose-erc-native
|
||||
|
||||
- include_tasks: oracle-add-docker-external-network.yml
|
||||
with_items:
|
||||
- docker-compose
|
||||
- docker-compose-transfer
|
||||
- docker-compose-erc-native
|
||||
loop_control:
|
||||
loop_var: file
|
||||
|
||||
- name: start the service
|
||||
shell: service poabridge start
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
- name: Overwrite UI the docker-compose
|
||||
hosts: ui
|
||||
become: true
|
||||
tasks:
|
||||
- name: stop the service
|
||||
shell: service tokenbridge-ui stop
|
||||
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "/home/poadocker/bridge/ui/docker-compose.yml"
|
||||
register: docker_compose_slurp
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Add the external network used to connect to Parity nodes
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
|
||||
|
||||
- name: Add all UI containers to the network
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': ['ultimate']}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Write new docker-compose file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "/home/poadocker/bridge/ui/docker-compose.yml"
|
||||
|
||||
- name: start the service
|
||||
shell: service tokenbridge-ui start
|
||||
@@ -32,8 +32,6 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-erc-to-erc-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
ui-erc-to-erc-host:
|
||||
|
||||
@@ -32,8 +32,6 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-erc-to-native-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
ORACLE_HOME_START_BLOCK: 1
|
||||
|
||||
@@ -32,8 +32,6 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-native-to-erc-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
ui-native-to-erc-host:
|
||||
|
||||
@@ -5,13 +5,13 @@ ORACLE_ALLOW_HTTP_FOR_RPC: yes
|
||||
ORACLE_LOG_LEVEL: debug
|
||||
|
||||
## Home contract
|
||||
COMMON_HOME_RPC_URL: "https://sokol.poa.network"
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol"
|
||||
UI_HOME_WITHOUT_EVENTS: false
|
||||
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
|
||||
|
||||
## Foreign contract
|
||||
COMMON_FOREIGN_RPC_URL: "https://sokol.poa.network"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan"
|
||||
UI_FOREIGN_WITHOUT_EVENTS: false
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
|
||||
@@ -52,3 +52,7 @@ MONITOR_FOREIGN_START_BLOCK: 0
|
||||
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
|
||||
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
|
||||
MONITOR_TX_NUMBER_THRESHOLD: 100
|
||||
|
||||
# disable building and pulling of docker images from the Docker Hub
|
||||
skip_pull: true
|
||||
skip_build: true
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
shell: docker-compose pull
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/monitor"
|
||||
when: skip_pull is undefined
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
shell: docker-compose pull
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/oracle"
|
||||
when: skip_pull is undefined
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
shell: docker-compose build
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/ui"
|
||||
when: skip_build is undefined
|
||||
|
||||
@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
|
||||
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
|
||||
UI_PORT=3000
|
||||
UI_PORT=3003
|
||||
UI_STYLES=stake
|
||||
|
||||
@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
|
||||
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
|
||||
UI_PORT=3000
|
||||
UI_PORT=3002
|
||||
UI_STYLES=core
|
||||
|
||||
@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
|
||||
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
|
||||
UI_PORT=3000
|
||||
UI_PORT=3001
|
||||
UI_STYLES=core
|
||||
|
||||
@@ -4,12 +4,14 @@ networks:
|
||||
external: true
|
||||
services:
|
||||
parity1:
|
||||
container_name: parity1
|
||||
build: ../parity
|
||||
ports:
|
||||
- "8541:8545"
|
||||
networks:
|
||||
- ultimate
|
||||
parity2:
|
||||
container_name: parity2
|
||||
build:
|
||||
context: ../parity
|
||||
dockerfile: Dockerfile-foreign
|
||||
|
||||
@@ -80,9 +80,9 @@ while [ "$1" != "" ]; do
|
||||
docker-compose up -d ui ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20
|
||||
|
||||
docker-compose run -d -p 3000:3000 ui yarn start
|
||||
docker-compose run -d -p 3001:3000 ui-erc20 yarn start
|
||||
docker-compose run -d -p 3002:3000 ui-erc20-native yarn start
|
||||
docker-compose run -d -p 3003:3000 ui-amb-stake-erc20-erc20 yarn start
|
||||
docker-compose run -d -p 3001:3001 ui-erc20 yarn start
|
||||
docker-compose run -d -p 3002:3002 ui-erc20-native yarn start
|
||||
docker-compose run -d -p 3003:3003 ui-amb-stake-erc20-erc20 yarn start
|
||||
fi
|
||||
|
||||
if [ "$1" == "alm" ]; then
|
||||
|
||||
@@ -25,3 +25,6 @@ MONITOR_CACHE_EVENTS=true
|
||||
|
||||
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST=
|
||||
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST=
|
||||
|
||||
# MONITOR_HOME_VALIDATORS_BALANCE_ENABLE=0x... 0x... 0x...
|
||||
# MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE=0x... 0x... 0x...
|
||||
|
||||
@@ -12,10 +12,6 @@ const { web3Home } = require('./utils/web3')
|
||||
|
||||
const { COMMON_HOME_BRIDGE_ADDRESS, MONITOR_BRIDGE_NAME } = process.env
|
||||
|
||||
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
|
||||
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
|
||||
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
|
||||
|
||||
const { HOME_ERC_TO_ERC_ABI } = require('../commons')
|
||||
|
||||
async function checkWorker() {
|
||||
@@ -45,27 +41,6 @@ async function checkWorker() {
|
||||
const vBalances = await validators(bridgeMode)
|
||||
if (!vBalances) throw new Error('vBalances is empty: ' + JSON.stringify(vBalances))
|
||||
|
||||
vBalances.homeOk = true
|
||||
vBalances.foreignOk = true
|
||||
|
||||
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
|
||||
for (const hv in vBalances.home.validators) {
|
||||
if (vBalances.home.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
|
||||
vBalances.homeOk = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
|
||||
for (const hv in vBalances.foreign.validators) {
|
||||
if (vBalances.foreign.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
|
||||
vBalances.foreignOk = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vBalances.ok = vBalances.homeOk && vBalances.foreignOk
|
||||
vBalances.health = true
|
||||
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/validators.json`, vBalances)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
require('dotenv').config()
|
||||
const logger = require('./logger')('checkWorker3')
|
||||
const stuckTransfers = require('./stuckTransfers')
|
||||
const detectMediators = require('./detectMediators')
|
||||
const { writeFile, createDir } = require('./utils/file')
|
||||
const { web3Home } = require('./utils/web3')
|
||||
const { saveCache } = require('./utils/web3Cache')
|
||||
|
||||
const { MONITOR_BRIDGE_NAME, COMMON_HOME_BRIDGE_ADDRESS } = process.env
|
||||
const { getBridgeMode, HOME_NATIVE_TO_ERC_ABI, BRIDGE_MODES } = require('../commons')
|
||||
@@ -20,6 +22,15 @@ async function checkWorker3() {
|
||||
transfers.health = true
|
||||
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/stuckTransfers.json`, transfers)
|
||||
logger.debug('Done')
|
||||
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
|
||||
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
|
||||
logger.debug('calling detectMediators()')
|
||||
const mediators = await detectMediators(bridgeMode)
|
||||
mediators.ok = true
|
||||
mediators.health = true
|
||||
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/mediators.json`, mediators)
|
||||
saveCache()
|
||||
logger.debug('Done')
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('checkWorker3.js', e)
|
||||
|
||||
148
monitor/detectMediators.js
Normal file
148
monitor/detectMediators.js
Normal file
@@ -0,0 +1,148 @@
|
||||
require('dotenv').config()
|
||||
const logger = require('./logger')('stuckTransfers.js')
|
||||
const { isHomeContract, isForeignContract } = require('./utils/web3Cache')
|
||||
const eventsInfo = require('./utils/events')
|
||||
const { getHomeTxSender, getForeignTxSender } = require('./utils/web3Cache')
|
||||
const { addExecutionStatus } = require('./utils/message')
|
||||
const { normalizeAMBMessageEvent } = require('../commons')
|
||||
|
||||
function countInteractions(requests) {
|
||||
const stats = {}
|
||||
requests.forEach(msg => {
|
||||
if (!stats[msg.sender]) {
|
||||
stats[msg.sender] = {}
|
||||
}
|
||||
if (!stats[msg.sender][msg.executor]) {
|
||||
stats[msg.sender][msg.executor] = 0
|
||||
}
|
||||
stats[msg.sender][msg.executor] += 1
|
||||
})
|
||||
return stats
|
||||
}
|
||||
|
||||
const normalize = event => ({
|
||||
...normalizeAMBMessageEvent(event),
|
||||
txHash: event.transactionHash,
|
||||
logIndex: event.transactionLogIndex
|
||||
})
|
||||
|
||||
const flat = arrays => Array.prototype.concat.apply([], arrays)
|
||||
|
||||
function findPermanentMediators(homeToForeignC2C, foreignToHomeC2C) {
|
||||
return flat(
|
||||
Object.entries(homeToForeignC2C).map(([homeMediator, homeStats]) =>
|
||||
Object.entries(foreignToHomeC2C)
|
||||
.map(([foreignMediator, foreignStats]) => ({
|
||||
homeMediator,
|
||||
foreignMediator,
|
||||
homeToForeignRequests: homeStats[foreignMediator],
|
||||
foreignToHomeRequests: foreignStats[homeMediator]
|
||||
}))
|
||||
.filter(stats => stats.homeToForeignRequests && stats.foreignToHomeRequests)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function findFloatingMediators(homeToForeignC2C, foreignToHomeC2C) {
|
||||
return Object.entries(homeToForeignC2C)
|
||||
.map(([homeMediator, homeStats]) => {
|
||||
const noResponses = ([executor]) => !foreignToHomeC2C[executor] || !foreignToHomeC2C[executor][homeMediator]
|
||||
const executorRequestPairs = Object.entries(homeStats).filter(noResponses)
|
||||
return {
|
||||
mediator: homeMediator,
|
||||
executors: executorRequestPairs.map(pair => pair[0]),
|
||||
requests: executorRequestPairs.map(pair => pair[1])
|
||||
}
|
||||
})
|
||||
.filter(stats => stats.executors.length > 0)
|
||||
}
|
||||
|
||||
function findRemotelyControlledMediators(statsU2C) {
|
||||
return Object.entries(statsU2C).map(([user, stats]) => ({
|
||||
user,
|
||||
executors: Object.keys(stats),
|
||||
requests: Object.values(stats)
|
||||
}))
|
||||
}
|
||||
|
||||
function findUnknown(statsA2U) {
|
||||
return Object.entries(statsA2U).map(([sender, stats]) => ({
|
||||
sender,
|
||||
executors: Object.keys(stats),
|
||||
requests: Object.values(stats)
|
||||
}))
|
||||
}
|
||||
|
||||
async function main(mode) {
|
||||
const {
|
||||
homeToForeignRequests,
|
||||
foreignToHomeRequests,
|
||||
homeToForeignConfirmations,
|
||||
foreignToHomeConfirmations
|
||||
} = await eventsInfo(mode)
|
||||
const homeToForeign = homeToForeignRequests
|
||||
.map(normalize)
|
||||
.map(addExecutionStatus(homeToForeignConfirmations))
|
||||
.filter(x => typeof x.status === 'boolean')
|
||||
const foreignToHome = foreignToHomeRequests
|
||||
.map(normalize)
|
||||
.map(addExecutionStatus(foreignToHomeConfirmations))
|
||||
.filter(x => typeof x.status === 'boolean')
|
||||
|
||||
for (const event of homeToForeign) {
|
||||
// AMB contract emits a single UserRequestForSignature event for every home->foreign request.
|
||||
// If index of such event in logs is not equal to 0x0, then some other events occurred before it,
|
||||
// meaning that the sender was a contract.
|
||||
// Alternatively, the sender is a contract, if the message sender is not equal to tx.origin.
|
||||
event.isSenderAContract = event.logIndex !== '0x0' || (await getHomeTxSender(event.txHash)) !== event.sender
|
||||
|
||||
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
|
||||
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
|
||||
event.isExecutorAContract = !event.status || (await isForeignContract(event.executor))
|
||||
}
|
||||
for (const event of foreignToHome) {
|
||||
// AMB contract emits a single UserRequestForAffirmation event for every foreign->home request.
|
||||
// If index of such event in logs is not equal to 0x0, then some other events occurred before it,
|
||||
// meaning that the sender was a contract.
|
||||
// Alternatively, the sender is a contract, if the message sender is not equal to tx.origin.
|
||||
event.isSenderAContract = event.logIndex !== '0x0' || (await getForeignTxSender(event.txHash)) !== event.sender
|
||||
|
||||
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
|
||||
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
|
||||
event.isExecutorAContract = !event.status || (await isHomeContract(event.executor))
|
||||
}
|
||||
const C2C = event => event.isSenderAContract && event.isExecutorAContract
|
||||
const U2C = event => !event.isSenderAContract && event.isExecutorAContract
|
||||
const A2U = event => !event.isExecutorAContract
|
||||
|
||||
const homeToForeignC2C = countInteractions(homeToForeign.filter(C2C))
|
||||
const foreignToHomeC2C = countInteractions(foreignToHome.filter(C2C))
|
||||
const homeToForeignU2C = countInteractions(homeToForeign.filter(U2C))
|
||||
const foreignToHomeU2C = countInteractions(foreignToHome.filter(U2C))
|
||||
const homeToForeignA2U = countInteractions(homeToForeign.filter(A2U))
|
||||
const foreignToHomeA2U = countInteractions(foreignToHome.filter(A2U))
|
||||
|
||||
const permanentMediators = findPermanentMediators(homeToForeignC2C, foreignToHomeC2C)
|
||||
const floatingMediators = {
|
||||
home: findFloatingMediators(homeToForeignC2C, foreignToHomeC2C),
|
||||
foreign: findFloatingMediators(foreignToHomeC2C, homeToForeignC2C)
|
||||
}
|
||||
const remotelyControlledMediators = {
|
||||
home: findRemotelyControlledMediators(homeToForeignU2C),
|
||||
foreign: findRemotelyControlledMediators(foreignToHomeU2C)
|
||||
}
|
||||
const unknown = {
|
||||
home: findUnknown(homeToForeignA2U),
|
||||
foreign: findUnknown(foreignToHomeA2U)
|
||||
}
|
||||
|
||||
logger.debug('Done')
|
||||
return {
|
||||
permanentMediators,
|
||||
floatingMediators,
|
||||
remotelyControlledMediators,
|
||||
unknown,
|
||||
lastChecked: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
}
|
||||
module.exports = main
|
||||
@@ -1,70 +0,0 @@
|
||||
require('dotenv').config()
|
||||
const BN = require('bignumber.js')
|
||||
const { fromWei } = require('web3').utils
|
||||
const logger = require('./logger')('getRequestsOutOfLimits')
|
||||
const { BRIDGE_MODES, getBridgeABIs } = require('../commons')
|
||||
const { web3Home, web3Foreign } = require('./utils/web3')
|
||||
|
||||
const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
|
||||
|
||||
function outOfRemainingQuota(remainingLimit) {
|
||||
let limit = remainingLimit
|
||||
return event => {
|
||||
if (limit.lt(event.value)) {
|
||||
return true
|
||||
}
|
||||
limit = limit.minus(event.value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function checkOutOfLimitsStats(contract, requests) {
|
||||
logger.debug('calling contract.getCurrentDay()')
|
||||
const day = await contract.methods.getCurrentDay().call()
|
||||
logger.debug('calling contract.executionDailyLimit()')
|
||||
const executionDailyLimit = new BN(await contract.methods.executionDailyLimit().call())
|
||||
logger.debug('calling contract.executionMaxPerTx()')
|
||||
const executionMaxPerTx = new BN(await contract.methods.executionMaxPerTx().call())
|
||||
logger.debug('calling contract.totalExecutedPerDay()')
|
||||
const executedPerDay = new BN(await contract.methods.totalExecutedPerDay(day).call())
|
||||
|
||||
const remainingExecutionDailyLimit = executionDailyLimit.minus(executedPerDay)
|
||||
|
||||
// value > executionMaxPerTx
|
||||
const requestsAboveMaxPerTx = requests.filter(event => executionMaxPerTx.lt(event.value))
|
||||
|
||||
// value <= executionMaxPerTx && remainingExecutionDailyLimit < value
|
||||
const requestsAboveDailyLimit = requests
|
||||
.filter(event => executionMaxPerTx.gte(event.value))
|
||||
.filter(outOfRemainingQuota(remainingExecutionDailyLimit))
|
||||
|
||||
return {
|
||||
aboveExecutionMaxPerTx: requestsAboveMaxPerTx.length,
|
||||
aboveExecutionDailyLimit: requestsAboveDailyLimit.length,
|
||||
aboveExecutionDailyLimitAmount: fromWei(BN.sum(0, ...requestsAboveDailyLimit.map(e => e.value)).toFixed()),
|
||||
remainingExecutionDailyLimit: fromWei(remainingExecutionDailyLimit.toFixed())
|
||||
}
|
||||
}
|
||||
|
||||
async function main(bridgeMode, unprocessedRequests) {
|
||||
const { homeRequests, foreignRequests } = unprocessedRequests
|
||||
|
||||
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode)
|
||||
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
|
||||
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
|
||||
|
||||
// replace the required methods by their legacy versions
|
||||
if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1) {
|
||||
homeBridge.methods.executionDailyLimit = homeBridge.methods.foreignDailyLimit
|
||||
homeBridge.methods.executionMaxPerTx = homeBridge.methods.foreignMaxPerTx
|
||||
foreignBridge.methods.executionDailyLimit = foreignBridge.methods.homeDailyLimit
|
||||
foreignBridge.methods.executionMaxPerTx = foreignBridge.methods.homeMaxPerTx
|
||||
}
|
||||
|
||||
return {
|
||||
home: await checkOutOfLimitsStats(homeBridge, foreignRequests),
|
||||
foreign: await checkOutOfLimitsStats(foreignBridge, homeRequests)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = main
|
||||
@@ -8,7 +8,6 @@ const {
|
||||
manuallyProcessedAMBHomeToForeignRequests
|
||||
} = require('./utils/message')
|
||||
const { BRIDGE_MODES } = require('../commons')
|
||||
const getRequestsOutOfLimits = require('./getRequestsOutOfLimits')
|
||||
const { getHomeTxSender } = require('./utils/web3Cache')
|
||||
|
||||
const {
|
||||
@@ -47,11 +46,8 @@ async function main(bridgeMode, eventsInfo) {
|
||||
depositsDiff: homeToForeignRequests.length - homeToForeignConfirmations.length,
|
||||
withdrawalDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length
|
||||
}
|
||||
|
||||
const onlyInHomeDeposits = homeToForeignRequests.filter(eventWithoutReference(homeToForeignConfirmations))
|
||||
const onlyInForeignWithdrawals = foreignToHomeRequests.filter(eventWithoutReference(foreignToHomeConfirmations))
|
||||
|
||||
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST || MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
|
||||
const onlyInHomeDeposits = homeToForeignRequests.filter(eventWithoutReference(homeToForeignConfirmations))
|
||||
if (MONITOR_HOME_TO_FOREIGN_CHECK_SENDER === 'true') {
|
||||
for (let i = 0; i < onlyInHomeDeposits.length; i++) {
|
||||
onlyInHomeDeposits[i].sender = await getHomeTxSender(onlyInHomeDeposits[i].transactionHash)
|
||||
@@ -64,23 +60,15 @@ async function main(bridgeMode, eventsInfo) {
|
||||
stats.unclaimedDiff = unclaimedPool.length
|
||||
stats.unclaimedBalance = Web3Utils.fromWei(BN.sum(0, ...unclaimedPool.map(e => e.value)).toFixed())
|
||||
}
|
||||
|
||||
const limitsStats = await getRequestsOutOfLimits(bridgeMode, {
|
||||
homeRequests: onlyInHomeDeposits,
|
||||
foreignRequests: onlyInForeignWithdrawals
|
||||
})
|
||||
|
||||
return {
|
||||
...stats,
|
||||
home: {
|
||||
deposits: homeToForeignRequests.length,
|
||||
withdrawals: foreignToHomeConfirmations.length,
|
||||
outOfLimits: limitsStats.home
|
||||
withdrawals: foreignToHomeConfirmations.length
|
||||
},
|
||||
foreign: {
|
||||
deposits: homeToForeignConfirmations.length,
|
||||
withdrawals: foreignToHomeRequests.length,
|
||||
outOfLimits: limitsStats.foreign
|
||||
withdrawals: foreignToHomeRequests.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,16 @@ bridgeRouter.get('/alerts', async (req, res, next) => {
|
||||
}
|
||||
})
|
||||
|
||||
bridgeRouter.get('/mediators', async (req, res, next) => {
|
||||
try {
|
||||
const results = await readFile(`./responses/${req.params.bridgeName}/mediators.json`)
|
||||
res.json(results)
|
||||
} catch (e) {
|
||||
// this will eventually be handled by your error handling middleware
|
||||
next(e)
|
||||
}
|
||||
})
|
||||
|
||||
bridgeRouter.get('/stuckTransfers', async (req, res, next) => {
|
||||
try {
|
||||
const results = await readFile(`./responses/${req.params.bridgeName}/stuckTransfers.json`)
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
const { parseAMBMessage } = require('../../commons')
|
||||
const { normalizeAMBMessageEvent } = require('../../commons')
|
||||
const { readAccessListFile } = require('./file')
|
||||
|
||||
const { MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST, MONITOR_HOME_TO_FOREIGN_BLOCK_LIST } = process.env
|
||||
|
||||
const keyAMB = e => [e.messageId, e.sender, e.executor].join(',').toLowerCase()
|
||||
|
||||
const normalizeAMBMessage = e => {
|
||||
let msgData = e.returnValues.encodedData
|
||||
if (!e.returnValues.messageId) {
|
||||
// append tx hash to an old message, where message id was not used
|
||||
// for old messages, e.messageId is a corresponding transactionHash
|
||||
msgData = e.transactionHash + msgData.slice(2)
|
||||
}
|
||||
return parseAMBMessage(msgData)
|
||||
}
|
||||
|
||||
function deliveredMsgNotProcessed(processedList) {
|
||||
const keys = new Set()
|
||||
processedList.forEach(processedMsg => keys.add(keyAMB(processedMsg.returnValues)))
|
||||
return deliveredMsg => !keys.has(keyAMB(normalizeAMBMessage(deliveredMsg)))
|
||||
return deliveredMsg => !keys.has(keyAMB(normalizeAMBMessageEvent(deliveredMsg)))
|
||||
}
|
||||
|
||||
function processedMsgNotDelivered(deliveredList) {
|
||||
const keys = new Set()
|
||||
deliveredList.forEach(deliveredMsg => keys.add(keyAMB(normalizeAMBMessage(deliveredMsg))))
|
||||
deliveredList.forEach(deliveredMsg => keys.add(keyAMB(normalizeAMBMessageEvent(deliveredMsg))))
|
||||
return processedMsg => !keys.has(keyAMB(processedMsg.returnValues))
|
||||
}
|
||||
|
||||
function addExecutionStatus(processedList) {
|
||||
const statuses = {}
|
||||
processedList.forEach(processedMsg => {
|
||||
statuses[keyAMB(processedMsg.returnValues)] = processedMsg.returnValues.status
|
||||
})
|
||||
return deliveredMsg => {
|
||||
deliveredMsg.status = statuses[keyAMB(deliveredMsg)]
|
||||
return deliveredMsg
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the different event objects to facilitate data processing
|
||||
* @param {Object} event
|
||||
@@ -70,23 +71,24 @@ const manuallyProcessedAMBHomeToForeignRequests = () => {
|
||||
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
|
||||
const allowanceList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST)
|
||||
return e => {
|
||||
const { sender, executor, decodedDataType } = normalizeAMBMessage(e)
|
||||
const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
|
||||
return (!allowanceList.includes(sender) && !allowanceList.includes(executor)) || decodedDataType.manualLane
|
||||
}
|
||||
} else if (MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
|
||||
const blockList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_BLOCK_LIST)
|
||||
return e => {
|
||||
const { sender, executor, decodedDataType } = normalizeAMBMessage(e)
|
||||
const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
|
||||
return blockList.includes(sender) || blockList.includes(executor) || decodedDataType.manualLane
|
||||
}
|
||||
} else {
|
||||
return e => normalizeAMBMessage(e).decodedDataType.manualLane
|
||||
return e => normalizeAMBMessageEvent(e).decodedDataType.manualLane
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
deliveredMsgNotProcessed,
|
||||
processedMsgNotDelivered,
|
||||
addExecutionStatus,
|
||||
normalizeEventInformation,
|
||||
eventWithoutReference,
|
||||
unclaimedHomeToForeignRequests,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const logger = require('../logger')('web3Cache')
|
||||
const { readCacheFile, writeCacheFile } = require('./file')
|
||||
const { web3Home } = require('./web3')
|
||||
const { web3Home, web3Foreign } = require('./web3')
|
||||
const { getPastEvents: commonGetPastEvents } = require('../../commons')
|
||||
|
||||
const { MONITOR_BRIDGE_NAME, MONITOR_CACHE_EVENTS } = process.env
|
||||
@@ -9,16 +9,49 @@ let isDirty = false
|
||||
|
||||
const homeTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/txSenders.json`
|
||||
const cachedHomeTxSenders = readCacheFile(homeTxSendersCacheFile) || {}
|
||||
const foreignTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/foreign/txSenders.json`
|
||||
const cachedForeignTxSenders = readCacheFile(foreignTxSendersCacheFile) || {}
|
||||
const homeIsContractCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/isContract.json`
|
||||
const cachedHomeIsContract = readCacheFile(homeIsContractCacheFile) || {}
|
||||
const foreignIsContractCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/foreign/isContract.json`
|
||||
const cachedForeignIsContract = readCacheFile(foreignIsContractCacheFile) || {}
|
||||
|
||||
async function getHomeTxSender(txHash) {
|
||||
if (!cachedHomeTxSenders[txHash]) {
|
||||
logger.debug(`Fetching sender for tx ${txHash}`)
|
||||
logger.debug(`Fetching sender for home tx ${txHash}`)
|
||||
cachedHomeTxSenders[txHash] = (await web3Home.eth.getTransaction(txHash)).from.toLowerCase()
|
||||
isDirty = true
|
||||
}
|
||||
return cachedHomeTxSenders[txHash]
|
||||
}
|
||||
|
||||
async function getForeignTxSender(txHash) {
|
||||
if (!cachedForeignTxSenders[txHash]) {
|
||||
logger.debug(`Fetching sender for foreign tx ${txHash}`)
|
||||
cachedForeignTxSenders[txHash] = (await web3Foreign.eth.getTransaction(txHash)).from.toLowerCase()
|
||||
isDirty = true
|
||||
}
|
||||
return cachedForeignTxSenders[txHash]
|
||||
}
|
||||
|
||||
async function isHomeContract(address) {
|
||||
if (typeof cachedHomeIsContract[address] !== 'boolean') {
|
||||
logger.debug(`Fetching home contract code size for tx ${address}`)
|
||||
cachedHomeIsContract[address] = (await web3Home.eth.getCode(address)).length > 2
|
||||
isDirty = true
|
||||
}
|
||||
return cachedHomeIsContract[address]
|
||||
}
|
||||
|
||||
async function isForeignContract(address) {
|
||||
if (typeof cachedForeignIsContract[address] !== 'boolean') {
|
||||
logger.debug(`Fetching foreign contract code size for tx ${address}`)
|
||||
cachedForeignIsContract[address] = (await web3Foreign.eth.getCode(address)).length > 2
|
||||
isDirty = true
|
||||
}
|
||||
return cachedForeignIsContract[address]
|
||||
}
|
||||
|
||||
async function getPastEvents(contract, options) {
|
||||
if (MONITOR_CACHE_EVENTS !== 'true') {
|
||||
return commonGetPastEvents(contract, options)
|
||||
@@ -123,11 +156,16 @@ function saveCache() {
|
||||
if (isDirty) {
|
||||
logger.debug('Saving cache on disk')
|
||||
writeCacheFile(homeTxSendersCacheFile, cachedHomeTxSenders)
|
||||
writeCacheFile(homeIsContractCacheFile, cachedHomeIsContract)
|
||||
writeCacheFile(foreignIsContractCacheFile, cachedForeignIsContract)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getHomeTxSender,
|
||||
getForeignTxSender,
|
||||
isHomeContract,
|
||||
isForeignContract,
|
||||
getPastEvents,
|
||||
saveCache
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@ const {
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL,
|
||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE,
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK,
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR,
|
||||
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE,
|
||||
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE
|
||||
} = process.env
|
||||
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
|
||||
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
|
||||
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
|
||||
|
||||
const homeGasPriceSupplierOpts = {
|
||||
speedType: COMMON_HOME_GAS_PRICE_SPEED_TYPE,
|
||||
@@ -33,12 +36,6 @@ const foreignGasPriceSupplierOpts = {
|
||||
logger
|
||||
}
|
||||
|
||||
const asyncForEach = async (array, callback) => {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array)
|
||||
}
|
||||
}
|
||||
|
||||
async function main(bridgeMode) {
|
||||
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode)
|
||||
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
|
||||
@@ -109,53 +106,61 @@ async function main(bridgeMode) {
|
||||
}
|
||||
|
||||
let validatorsMatch = true
|
||||
logger.debug('calling asyncForEach foreignValidators foreignVBalances')
|
||||
await asyncForEach(foreignValidators, async v => {
|
||||
const balance = await web3Foreign.eth.getBalance(v)
|
||||
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
|
||||
const leftTx = Web3Utils.toBN(balance)
|
||||
.div(foreignTxCost)
|
||||
.toString(10)
|
||||
foreignVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance),
|
||||
leftTx: Number(leftTx),
|
||||
gasPrice: Number(foreignGasPriceGwei)
|
||||
const foreignValidatorsWithBalanceCheck =
|
||||
typeof MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE === 'string'
|
||||
? MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE.split(' ')
|
||||
: foreignValidators
|
||||
logger.debug('getting foreignValidators balances')
|
||||
await Promise.all(
|
||||
foreignValidators.map(async v => {
|
||||
foreignVBalances[v] = {}
|
||||
if (foreignValidatorsWithBalanceCheck.includes(v)) {
|
||||
const balance = await web3Foreign.eth.getBalance(v)
|
||||
foreignVBalances[v].balance = Web3Utils.fromWei(balance)
|
||||
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
|
||||
foreignVBalances[v].leftTx = Number(
|
||||
Web3Utils.toBN(balance)
|
||||
.div(foreignTxCost)
|
||||
.toString(10)
|
||||
)
|
||||
foreignVBalances[v].gasPrice = parseFloat(foreignGasPriceGwei)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreignVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance)
|
||||
}
|
||||
}
|
||||
|
||||
if (!homeValidators.includes(v)) {
|
||||
validatorsMatch = false
|
||||
foreignVBalances[v].onlyOnForeign = true
|
||||
}
|
||||
})
|
||||
|
||||
logger.debug('calling asyncForEach homeValidators homeVBalances')
|
||||
await asyncForEach(homeValidators, async v => {
|
||||
const balance = await web3Home.eth.getBalance(v)
|
||||
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
|
||||
const leftTx = Web3Utils.toBN(balance)
|
||||
.div(homeTxCost)
|
||||
.toString(10)
|
||||
homeVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance),
|
||||
leftTx: Number(leftTx),
|
||||
gasPrice: Number(homeGasPriceGwei)
|
||||
if (!homeValidators.includes(v)) {
|
||||
validatorsMatch = false
|
||||
foreignVBalances[v].onlyOnForeign = true
|
||||
}
|
||||
} else {
|
||||
homeVBalances[v] = {
|
||||
balance: Web3Utils.fromWei(balance)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (!foreignValidators.includes(v)) {
|
||||
validatorsMatch = false
|
||||
homeVBalances[v].onlyOnHome = true
|
||||
}
|
||||
})
|
||||
const homeValidatorsWithBalanceCheck =
|
||||
typeof MONITOR_HOME_VALIDATORS_BALANCE_ENABLE === 'string'
|
||||
? MONITOR_HOME_VALIDATORS_BALANCE_ENABLE.split(' ')
|
||||
: homeValidators
|
||||
logger.debug('calling homeValidators balances')
|
||||
await Promise.all(
|
||||
homeValidators.map(async v => {
|
||||
homeVBalances[v] = {}
|
||||
if (homeValidatorsWithBalanceCheck.includes(v)) {
|
||||
const balance = await web3Home.eth.getBalance(v)
|
||||
homeVBalances[v].balance = Web3Utils.fromWei(balance)
|
||||
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
|
||||
homeVBalances[v].leftTx = Number(
|
||||
Web3Utils.toBN(balance)
|
||||
.div(homeTxCost)
|
||||
.toString(10)
|
||||
)
|
||||
homeVBalances[v].gasPrice = parseFloat(homeGasPriceGwei)
|
||||
}
|
||||
}
|
||||
|
||||
if (!foreignValidators.includes(v)) {
|
||||
validatorsMatch = false
|
||||
homeVBalances[v].onlyOnHome = true
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
logger.debug('calling homeBridgeValidators.methods.requiredSignatures().call()')
|
||||
const reqSigHome = await homeBridgeValidators.methods.requiredSignatures().call()
|
||||
@@ -164,20 +169,22 @@ async function main(bridgeMode) {
|
||||
logger.debug('Done')
|
||||
return {
|
||||
home: {
|
||||
validators: {
|
||||
...homeVBalances
|
||||
},
|
||||
validators: homeVBalances,
|
||||
requiredSignatures: Number(reqSigHome)
|
||||
},
|
||||
foreign: {
|
||||
validators: {
|
||||
...foreignVBalances
|
||||
},
|
||||
validators: foreignVBalances,
|
||||
requiredSignatures: Number(reqSigForeign)
|
||||
},
|
||||
requiredSignaturesMatch: reqSigHome === reqSigForeign,
|
||||
validatorsMatch,
|
||||
lastChecked: Math.floor(Date.now() / 1000)
|
||||
lastChecked: Math.floor(Date.now() / 1000),
|
||||
homeOk: Object.values(homeVBalances)
|
||||
.filter(vb => typeof vb.leftTx === 'number')
|
||||
.every(vb => vb.leftTx >= MONITOR_TX_NUMBER_THRESHOLD),
|
||||
foreignOk: Object.values(foreignVBalances)
|
||||
.filter(vb => typeof vb.leftTx === 'number')
|
||||
.every(vb => vb.leftTx >= MONITOR_TX_NUMBER_THRESHOLD)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('arbitrary message bridging', () => {
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
await delay(5000)
|
||||
await delay(10000)
|
||||
|
||||
const newSignatures = await homeBridge.getPastEvents('SignedForUserRequest', {
|
||||
fromBlock: 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const baseConfig = require('./base.config')
|
||||
|
||||
const { web3Foreign, web3ForeignRedundant } = require('../src/services/web3')
|
||||
const { web3Foreign, web3ForeignRedundant, web3ForeignFallback } = require('../src/services/web3')
|
||||
|
||||
module.exports = {
|
||||
...baseConfig.bridgeConfig,
|
||||
@@ -9,5 +9,6 @@ module.exports = {
|
||||
id: 'foreign',
|
||||
name: 'sender-foreign',
|
||||
web3: web3Foreign,
|
||||
web3Redundant: web3ForeignRedundant
|
||||
web3Redundant: web3ForeignRedundant,
|
||||
web3Fallback: web3ForeignFallback
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const baseConfig = require('./base.config')
|
||||
|
||||
const { web3Home, web3HomeRedundant } = require('../src/services/web3')
|
||||
const { web3Home, web3HomeRedundant, web3HomeFallback } = require('../src/services/web3')
|
||||
|
||||
module.exports = {
|
||||
...baseConfig.bridgeConfig,
|
||||
@@ -9,5 +9,6 @@ module.exports = {
|
||||
id: 'home',
|
||||
name: 'sender-home',
|
||||
web3: web3Home,
|
||||
web3Redundant: web3HomeRedundant
|
||||
web3Redundant: web3HomeRedundant,
|
||||
web3Fallback: web3HomeFallback
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ const config = require(path.join('../config/', process.argv[2]))
|
||||
|
||||
const web3Instance = config.web3
|
||||
const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : config.web3
|
||||
const { web3Fallback } = config
|
||||
|
||||
const nonceKey = `${config.id}:nonce`
|
||||
let chainId = 0
|
||||
|
||||
@@ -128,7 +130,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
|
||||
|
||||
try {
|
||||
if (isResend) {
|
||||
const tx = await web3Instance.eth.getTransaction(job.txHash)
|
||||
const tx = await web3Fallback.eth.getTransaction(job.txHash)
|
||||
|
||||
if (tx && tx.blockNumber !== null) {
|
||||
logger.debug(`Transaction ${job.txHash} was successfully mined`)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const fetch = require('node-fetch')
|
||||
const promiseRetry = require('promise-retry')
|
||||
const { FALLBACK_RPC_URL_SWITCH_TIMEOUT } = require('../utils/constants')
|
||||
|
||||
// From EIP-1474 and Infura documentation
|
||||
const JSONRPC_ERROR_CODES = [-32603, -32002, -32005]
|
||||
|
||||
const defaultOptions = {
|
||||
name: 'main',
|
||||
requestTimeout: 0,
|
||||
retry: {
|
||||
retries: 0
|
||||
@@ -30,23 +32,74 @@ function HttpListProvider(urls, options = {}) {
|
||||
this.urls = urls
|
||||
this.options = { ...defaultOptions, ...options }
|
||||
this.currentIndex = 0
|
||||
this.lastTimeUsedPrimary = 0
|
||||
this.logger = {
|
||||
debug: () => {},
|
||||
info: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
HttpListProvider.prototype.setLogger = function(logger) {
|
||||
this.logger = logger.child({ module: `HttpListProvider:${this.options.name}` })
|
||||
}
|
||||
|
||||
HttpListProvider.prototype.send = async function send(payload, callback) {
|
||||
// if fallback URL is being used for too long, switch back to the primary URL
|
||||
if (this.currentIndex > 0 && Date.now() - this.lastTimeUsedPrimary > FALLBACK_RPC_URL_SWITCH_TIMEOUT) {
|
||||
this.logger.info(
|
||||
{ oldURL: this.urls[this.currentIndex], newURL: this.urls[0] },
|
||||
'Switching back to the primary JSON-RPC URL'
|
||||
)
|
||||
this.currentIndex = 0
|
||||
}
|
||||
|
||||
// save the currentIndex to avoid race condition
|
||||
const { currentIndex } = this
|
||||
|
||||
try {
|
||||
const [result, index] = await promiseRetry(retry => {
|
||||
return trySend(payload, this.urls, currentIndex, this.options).catch(retry)
|
||||
}, this.options.retry)
|
||||
this.currentIndex = index
|
||||
const [result, index] = await promiseRetry(
|
||||
retry => this.trySend(payload, currentIndex).catch(retry),
|
||||
this.options.retry
|
||||
)
|
||||
|
||||
// if some of URLs failed to respond, current URL index is updated to the first URL that responded
|
||||
if (currentIndex !== index) {
|
||||
this.logger.info(
|
||||
{ index, oldURL: this.urls[currentIndex], newURL: this.urls[index] },
|
||||
'Switching to fallback JSON-RPC URL'
|
||||
)
|
||||
this.currentIndex = index
|
||||
}
|
||||
callback(null, result)
|
||||
} catch (e) {
|
||||
callback(e)
|
||||
}
|
||||
}
|
||||
|
||||
HttpListProvider.prototype.trySend = async function(payload, initialIndex) {
|
||||
const errors = []
|
||||
|
||||
for (let count = 0; count < this.urls.length; count++) {
|
||||
const index = (initialIndex + count) % this.urls.length
|
||||
|
||||
// when request is being sent to the primary URL, the corresponding time marker is updated
|
||||
if (index === 0) {
|
||||
this.lastTimeUsedPrimary = Date.now()
|
||||
}
|
||||
|
||||
const url = this.urls[index]
|
||||
try {
|
||||
const result = await send(url, payload, this.options)
|
||||
return [result, index]
|
||||
} catch (e) {
|
||||
this.logger.debug({ index, url, method: payload.method, error: e.message }, `JSON-RPC has failed to respond`)
|
||||
errors.push(e)
|
||||
}
|
||||
}
|
||||
|
||||
throw new HttpListProviderError('Request failed for all urls', errors)
|
||||
}
|
||||
|
||||
function send(url, payload, options) {
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
@@ -65,31 +118,16 @@ function send(url, payload, options) {
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (response.error && JSONRPC_ERROR_CODES.includes(response.error.code)) {
|
||||
if (
|
||||
response.error &&
|
||||
(JSONRPC_ERROR_CODES.includes(response.error.code) || response.error.message.includes('ancient block'))
|
||||
) {
|
||||
throw new Error(response.error.message)
|
||||
}
|
||||
return response
|
||||
})
|
||||
}
|
||||
|
||||
async function trySend(payload, urls, initialIndex, options) {
|
||||
const errors = []
|
||||
|
||||
let index = initialIndex
|
||||
for (let count = 0; count < urls.length; count++) {
|
||||
const url = urls[index]
|
||||
try {
|
||||
const result = await send(url, payload, options)
|
||||
return [result, index]
|
||||
} catch (e) {
|
||||
errors.push(e)
|
||||
}
|
||||
index = (index + 1) % urls.length
|
||||
}
|
||||
|
||||
throw new HttpListProviderError('Request failed for all urls', errors)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HttpListProvider,
|
||||
HttpListProviderError,
|
||||
|
||||
@@ -15,6 +15,10 @@ function RedundantHttpListProvider(urls, options = {}) {
|
||||
this.options = { ...defaultOptions, ...options }
|
||||
}
|
||||
|
||||
RedundantHttpListProvider.prototype.setLogger = function(logger) {
|
||||
this.logger = logger.child({ module: `RedundantHttpListProvider:${this.options.name}` })
|
||||
}
|
||||
|
||||
RedundantHttpListProvider.prototype.send = async function send(payload, callback) {
|
||||
try {
|
||||
const result = await promiseRetry(retry => {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
const pino = require('pino')
|
||||
const path = require('path')
|
||||
const {
|
||||
web3Home,
|
||||
web3Foreign,
|
||||
web3HomeFallback,
|
||||
web3ForeignFallback,
|
||||
web3HomeRedundant,
|
||||
web3ForeignRedundant
|
||||
} = require('./web3')
|
||||
|
||||
const config = process.env.NODE_ENV !== 'test' ? require(path.join('../../config/', process.argv[2])) : {}
|
||||
|
||||
@@ -15,4 +23,11 @@ const logger = pino({
|
||||
: {}
|
||||
})
|
||||
|
||||
web3Home.currentProvider.setLogger(logger)
|
||||
web3Foreign.currentProvider.setLogger(logger)
|
||||
web3HomeFallback.currentProvider.setLogger(logger)
|
||||
web3ForeignFallback.currentProvider.setLogger(logger)
|
||||
web3HomeRedundant.currentProvider.setLogger(logger)
|
||||
web3ForeignRedundant.currentProvider.setLogger(logger)
|
||||
|
||||
module.exports = logger
|
||||
|
||||
@@ -41,15 +41,37 @@ const web3Home = new Web3(homeProvider)
|
||||
const foreignProvider = new HttpListProvider(foreignUrls, foreignOptions)
|
||||
const web3Foreign = new Web3(foreignProvider)
|
||||
|
||||
const redundantHomeProvider = new RedundantHttpListProvider(homeUrls, homeOptions)
|
||||
const web3HomeRedundant = new Web3(redundantHomeProvider)
|
||||
// secondary fallback providers are intended to be used in places where
|
||||
// it is more likely that RPC calls to the local non-archive nodes can fail
|
||||
// e.g. for checking status of the old transaction via eth_getTransactionByHash
|
||||
let web3HomeFallback = web3Home
|
||||
let web3ForeignFallback = web3Foreign
|
||||
|
||||
const redundantForeignProvider = new RedundantHttpListProvider(foreignUrls, foreignOptions)
|
||||
const web3ForeignRedundant = new Web3(redundantForeignProvider)
|
||||
// secondary redundant providers are intended to be used in places where
|
||||
// the result of a single RPC request can be lost
|
||||
// e.g. for sending transactions eth_sendRawTransaction
|
||||
let web3HomeRedundant = web3Home
|
||||
let web3ForeignRedundant = web3Foreign
|
||||
|
||||
if (homeUrls.length > 1) {
|
||||
const provider = new HttpListProvider(homeUrls, { ...homeOptions, name: 'fallback' })
|
||||
web3HomeFallback = new Web3(provider)
|
||||
const redundantProvider = new RedundantHttpListProvider(homeUrls, { ...homeOptions, name: 'redundant' })
|
||||
web3HomeRedundant = new Web3(redundantProvider)
|
||||
}
|
||||
|
||||
if (foreignUrls.length > 1) {
|
||||
const provider = new HttpListProvider(foreignUrls, { ...foreignOptions, name: 'fallback' })
|
||||
web3ForeignFallback = new Web3(provider)
|
||||
const redundantProvider = new RedundantHttpListProvider(foreignUrls, { ...foreignOptions, name: 'redundant' })
|
||||
web3ForeignRedundant = new Web3(redundantProvider)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
web3Home,
|
||||
web3Foreign,
|
||||
web3HomeRedundant,
|
||||
web3ForeignRedundant
|
||||
web3ForeignRedundant,
|
||||
web3HomeFallback,
|
||||
web3ForeignFallback
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ module.exports = {
|
||||
MAX: 1000
|
||||
},
|
||||
TRANSACTION_RESEND_TIMEOUT: 20 * 60 * 1000,
|
||||
FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000,
|
||||
SENDER_QUEUE_MAX_PRIORITY: 10,
|
||||
SENDER_QUEUE_SEND_PRIORITY: 5,
|
||||
SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1
|
||||
|
||||
@@ -8,7 +8,7 @@ services:
|
||||
ports:
|
||||
- "${UI_PORT}:${UI_PORT}"
|
||||
env_file: ./.env
|
||||
environment:
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
restart: unless-stopped
|
||||
entrypoint: yarn start
|
||||
|
||||
Reference in New Issue
Block a user