diff --git a/CONFIGURATION.md b/CONFIGURATION.md index b93db0d1e..58fda6381 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -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` diff --git a/alm/src/components/ManualExecutionButton.tsx b/alm/src/components/ManualExecutionButton.tsx index 0e84e7ad4..e39c61506 100644 --- a/alm/src/components/ManualExecutionButton.tsx +++ b/alm/src/components/ManualExecutionButton.tsx @@ -58,7 +58,7 @@ export const ManualExecutionButton = ({ return } - if (!library || !foreign.bridgeContract) return + if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return const signatures = packSignatures(signatureCollected.map(signatureToVRS)) const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI() diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index f21b7208a..5052cb22a 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -42,7 +42,6 @@ export interface BasicConfirmationParam { export interface ConfirmationParam extends BasicConfirmationParam { txHash: string timestamp: number - signature?: string } export interface ExecutionData { diff --git a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts index adbbe8d65..3d21a46e3 100644 --- a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts +++ b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts @@ -110,8 +110,9 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1) - expect(setSignatureCollected).toBeCalledTimes(1) - expect(setSignatureCollected.mock.calls[0][0]).toEqual([signature, signature]) + 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) @@ -121,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 }, @@ -186,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) @@ -251,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.mock.calls[0][0]).toEqual([signature, signature]) + 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) @@ -265,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 }, @@ -339,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.mock.calls[0][0]).toEqual([signature, signature]) + 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) @@ -353,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 }, @@ -361,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 }, @@ -430,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) @@ -444,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 }, @@ -517,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) @@ -531,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 }, @@ -627,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) @@ -641,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 }, @@ -677,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([signature, signature]) + 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) @@ -694,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(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(setResult.mock.calls[3][0]).toEqual( + 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 }, diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index 04492df7e..c8ab7c67e 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -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,76 +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: boolean | string[] = 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 })) - - notRequiredConfirmations.forEach(validatorData => { - const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) - validatorConfirmations[index] = validatorData - }) - signatureCollectedResult = true + updateConfirmations(notRequiredConfirmations) if (fromHome) { - signatureCollectedResult = await Promise.all( - Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call()) + // 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 !== '') - - 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) { + updateConfirmations(successConfirmationWithTxFound) + + // 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( diff --git a/monitor/.env.example b/monitor/.env.example index 31647b03f..844f360f9 100644 --- a/monitor/.env.example +++ b/monitor/.env.example @@ -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... diff --git a/monitor/checkWorker.js b/monitor/checkWorker.js index a2384025a..bc003c738 100644 --- a/monitor/checkWorker.js +++ b/monitor/checkWorker.js @@ -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) diff --git a/monitor/detectMediators.js b/monitor/detectMediators.js index 8016e12b3..dc8c8465e 100644 --- a/monitor/detectMediators.js +++ b/monitor/detectMediators.js @@ -141,7 +141,8 @@ async function main(mode) { permanentMediators, floatingMediators, remotelyControlledMediators, - unknown + unknown, + lastChecked: Math.floor(Date.now() / 1000) } } module.exports = main diff --git a/monitor/index.js b/monitor/index.js index f62e767b9..3aaaa8200 100644 --- a/monitor/index.js +++ b/monitor/index.js @@ -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`) diff --git a/monitor/validators.js b/monitor/validators.js index ae32bad13..adc6a8fd3 100644 --- a/monitor/validators.js +++ b/monitor/validators.js @@ -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) + }) + ) + + 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 - } - }) + 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) } }