From 4c44aa5fcd12f5ce4f9384f2a124103cdf4eb3c6 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 9 Jul 2020 07:20:49 -0300 Subject: [PATCH] Add Alm unit tests (#388) --- alm/src/App.test.tsx | 5 - alm/src/utils/__tests__/contracts.test.ts | 469 ++++++++++++++++++ alm/src/utils/__tests__/explorer.test.ts | 155 ++++++ .../__tests__/getConfirmationsForTx.test.ts | 437 ++++++++++++++++ .../__tests__/getFinalizationEvent.test.ts | 303 +++++++++++ alm/src/utils/explorer.ts | 35 +- alm/src/utils/getConfirmationsForTx.ts | 172 +------ alm/src/utils/validatorConfirmationHelpers.ts | 172 +++++++ 8 files changed, 1558 insertions(+), 190 deletions(-) delete mode 100644 alm/src/App.test.tsx create mode 100644 alm/src/utils/__tests__/contracts.test.ts create mode 100644 alm/src/utils/__tests__/explorer.test.ts create mode 100644 alm/src/utils/__tests__/getConfirmationsForTx.test.ts create mode 100644 alm/src/utils/__tests__/getFinalizationEvent.test.ts create mode 100644 alm/src/utils/validatorConfirmationHelpers.ts diff --git a/alm/src/App.test.tsx b/alm/src/App.test.tsx deleted file mode 100644 index fe49a358..00000000 --- a/alm/src/App.test.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' - -test('renders learn react link', () => { - // Removed basic test from setup. Keeping this so CI passes until we add unit tests. -}) diff --git a/alm/src/utils/__tests__/contracts.test.ts b/alm/src/utils/__tests__/contracts.test.ts new file mode 100644 index 00000000..7296658d --- /dev/null +++ b/alm/src/utils/__tests__/contracts.test.ts @@ -0,0 +1,469 @@ +import 'jest' +import { getRequiredBlockConfirmations, getRequiredSignatures, getValidatorList } from '../contract' +import { Contract } from 'web3-eth-contract' +import { SnapshotProvider } from '../../services/SnapshotProvider' + +describe('getRequiredBlockConfirmations', () => { + const methodsBuilder = (value: string) => ({ + requiredBlockConfirmations: () => { + return { + call: () => { + return value + } + } + } + }) + + test('Should call requiredBlockConfirmations method if no events present', async () => { + const contract = ({ + getPastEvents: () => { + return [] + }, + methods: methodsBuilder('1') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider) + + expect(result).toEqual(1) + }) + test('Should not call to get events if block number was included in the snapshot', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []), + methods: methodsBuilder('3') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [ + { + blockNumber: 8, + returnValues: { + requiredBlockConfirmations: '1' + } + } + ] + }, + snapshotBlockNumber: () => { + return 15 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider) + + expect(result).toEqual(1) + expect(contract.getPastEvents).toBeCalledTimes(0) + }) + test('Should call to get events if block number was not included in the snapshot', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 9, + returnValues: { + requiredBlockConfirmations: '2' + } + } + ]), + methods: methodsBuilder('3') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [ + { + blockNumber: 8, + returnValues: { + requiredBlockConfirmations: '1' + } + } + ] + }, + snapshotBlockNumber: () => { + return 8 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 10, snapshotProvider) + + expect(result).toEqual(2) + expect(contract.getPastEvents).toBeCalledTimes(1) + expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredBlockConfirmationChanged', { + fromBlock: 9, + toBlock: 10 + }) + }) + test('Should use the most updated event', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 9, + returnValues: { + requiredBlockConfirmations: '2' + } + }, + { + blockNumber: 11, + returnValues: { + requiredBlockConfirmations: '3' + } + } + ]), + methods: methodsBuilder('3') + } as unknown) as Contract + + const snapshotProvider = ({ + requiredBlockConfirmationEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 11 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredBlockConfirmations(contract, 15, snapshotProvider) + + expect(result).toEqual(3) + expect(contract.getPastEvents).toBeCalledTimes(1) + expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredBlockConfirmationChanged', { + fromBlock: 12, + toBlock: 15 + }) + }) +}) +describe('getRequiredSignatures', () => { + test('Should not call to get events if block number was included in the snapshot', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []) + } as unknown) as Contract + + const snapshotProvider = ({ + requiredSignaturesEvents: () => { + return [ + { + blockNumber: 7, + returnValues: { + requiredSignatures: '1' + } + }, + { + blockNumber: 8, + returnValues: { + requiredSignatures: '2' + } + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredSignatures(contract, 10, snapshotProvider) + + expect(result).toEqual(2) + expect(contract.getPastEvents).toBeCalledTimes(0) + }) + test('Should call to get events if block number is higher than the snapshot block number', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 15, + returnValues: { + requiredSignatures: '3' + } + } + ]) + } as unknown) as Contract + + const snapshotProvider = ({ + requiredSignaturesEvents: () => { + return [ + { + blockNumber: 7, + returnValues: { + requiredSignatures: '1' + } + }, + { + blockNumber: 8, + returnValues: { + requiredSignatures: '2' + } + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredSignatures(contract, 20, snapshotProvider) + + expect(result).toEqual(3) + expect(contract.getPastEvents).toBeCalledTimes(1) + expect(contract.getPastEvents).toHaveBeenCalledWith('RequiredSignaturesChanged', { + fromBlock: 11, + toBlock: 20 + }) + }) + test('Should use the most updated event before the block number', async () => { + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => [ + { + blockNumber: 15, + returnValues: { + requiredSignatures: '4' + } + } + ]) + } as unknown) as Contract + + const snapshotProvider = ({ + requiredSignaturesEvents: () => { + return [ + { + blockNumber: 5, + returnValues: { + requiredSignatures: '1' + } + }, + { + blockNumber: 6, + returnValues: { + requiredSignatures: '2' + } + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const result = await getRequiredSignatures(contract, 7, snapshotProvider) + + expect(result).toEqual(2) + expect(contract.getPastEvents).toBeCalledTimes(0) + }) +}) +describe('getValidatorList', () => { + const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' + const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f' + const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' + const methodsBuilder = (value: string[]) => ({ + validatorList: () => { + return { + call: () => { + return value + } + } + } + }) + test('Should return the current validator list if no events found', async () => { + const currentValidators = [validator1, validator2, validator3] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 20, snapshotProvider) + + expect(list.length).toEqual(3) + expect(list).toEqual(expect.arrayContaining(currentValidators)) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 20 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 20 + }) + }) + test('If validator was added later from snapshot it should not include it', async () => { + const currentValidators = [validator1, validator2, validator3] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorAdded' + } + ] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 5, snapshotProvider) + + expect(list.length).toEqual(2) + expect(list).toEqual(expect.arrayContaining([validator1, validator2])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 11 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 11 + }) + }) + test('If validator was added later from chain it should not include it', async () => { + const currentValidators = [validator1, validator2, validator3] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(event => { + if (event === 'ValidatorAdded') { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorAdded' + } + ] + } else { + return [] + } + }), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 15, snapshotProvider) + + expect(list.length).toEqual(2) + expect(list).toEqual(expect.arrayContaining([validator1, validator2])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 15 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 15 + }) + }) + test('If validator was removed later from snapshot it should include it', async () => { + const currentValidators = [validator1, validator2] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(() => []), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorRemoved' + } + ] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 5, snapshotProvider) + + expect(list.length).toEqual(3) + expect(list).toEqual(expect.arrayContaining([validator1, validator2, validator3])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 11 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 11 + }) + }) + test('If validator was removed later from chain it should include it', async () => { + const currentValidators = [validator1, validator2] + const contract = ({ + getPastEvents: jest.fn().mockImplementation(event => { + if (event === 'ValidatorRemoved') { + return [ + { + blockNumber: 9, + returnValues: { + validator: validator3 + }, + event: 'ValidatorRemoved' + } + ] + } else { + return [] + } + }), + methods: methodsBuilder(currentValidators) + } as unknown) as Contract + + const snapshotProvider = ({ + validatorAddedEvents: () => { + return [] + }, + validatorRemovedEvents: () => { + return [] + }, + snapshotBlockNumber: () => { + return 10 + } + } as unknown) as SnapshotProvider + + const list = await getValidatorList(contract, 15, snapshotProvider) + + expect(list.length).toEqual(3) + expect(list).toEqual(expect.arrayContaining([validator1, validator2, validator3])) + expect(contract.getPastEvents).toBeCalledTimes(2) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorAdded', { + fromBlock: 15 + }) + expect(contract.getPastEvents).toHaveBeenCalledWith('ValidatorRemoved', { + fromBlock: 15 + }) + }) +}) diff --git a/alm/src/utils/__tests__/explorer.test.ts b/alm/src/utils/__tests__/explorer.test.ts new file mode 100644 index 00000000..d4743e6d --- /dev/null +++ b/alm/src/utils/__tests__/explorer.test.ts @@ -0,0 +1,155 @@ +import 'jest' +import { + getFailedTransactions, + getSuccessTransactions, + filterValidatorSignatureTransaction, + getExecutionFailedTransactionForMessage, + APITransaction, + getValidatorPendingTransactionsForMessage, + getExecutionPendingTransactionsForMessage +} from '../explorer' +import { EXECUTE_AFFIRMATION_HASH, EXECUTE_SIGNATURES_HASH, SUBMIT_SIGNATURE_HASH } from '../../config/constants' + +const messageData = '0x123456' +const OTHER_HASH = 'aabbccdd' +const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560' +const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1' + +describe('getFailedTransactions', () => { + test('should only return failed transactions', async () => { + const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }] + + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + const result = await getFailedTransactions('', '', 0, 1, '', fetchAccountTransactions) + expect(result.length).toEqual(3) + }) +}) +describe('getSuccessTransactions', () => { + test('should only return success transactions', async () => { + const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }] + + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + const result = await getSuccessTransactions('', '', 0, 1, '', fetchAccountTransactions) + expect(result.length).toEqual(2) + }) +}) +describe('filterValidatorSignatureTransaction', () => { + test('should return submit signatures related transaction', () => { + const transactions = [ + { input: `0x${SUBMIT_SIGNATURE_HASH}112233` }, + { input: `0x${SUBMIT_SIGNATURE_HASH}123456` }, + { input: `0x${OTHER_HASH}123456` }, + { input: `0x${OTHER_HASH}112233` } + ] as APITransaction[] + + const result = filterValidatorSignatureTransaction(transactions, messageData) + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${SUBMIT_SIGNATURE_HASH}123456` }) + }) + test('should return execute affirmation related transaction', () => { + const transactions = [ + { input: `0x${EXECUTE_AFFIRMATION_HASH}112233` }, + { input: `0x${EXECUTE_AFFIRMATION_HASH}123456` }, + { input: `0x${OTHER_HASH}123456` }, + { input: `0x${OTHER_HASH}112233` } + ] as APITransaction[] + + const result = filterValidatorSignatureTransaction(transactions, messageData) + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_AFFIRMATION_HASH}123456` }) + }) +}) +describe('getExecutionFailedTransactionForMessage', () => { + test('should return failed transaction related to signatures execution', async () => { + const transactions = [ + { input: `0x${EXECUTE_SIGNATURES_HASH}112233` }, + { input: `0x${EXECUTE_SIGNATURES_HASH}123456` }, + { input: `0x${OTHER_HASH}123456` }, + { input: `0x${OTHER_HASH}112233` } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getExecutionFailedTransactionForMessage( + { + account: '', + to: '', + messageData, + startTimestamp: 0, + endTimestamp: 1 + }, + fetchAccountTransactions + ) + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_SIGNATURES_HASH}123456` }) + }) +}) +describe('getValidatorPendingTransactionsForMessage', () => { + test('should return pending transaction for submit signature transaction', async () => { + const transactions = [ + { input: `0x${SUBMIT_SIGNATURE_HASH}112233`, to: bridgeAddress }, + { input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: bridgeAddress }, + { input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: otherAddress }, + { input: `0x${OTHER_HASH}123456`, to: bridgeAddress }, + { input: `0x${OTHER_HASH}112233`, to: bridgeAddress } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getValidatorPendingTransactionsForMessage( + { + account: '', + to: bridgeAddress, + messageData + }, + fetchAccountTransactions + ) + + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${SUBMIT_SIGNATURE_HASH}123456`, to: bridgeAddress }) + }) + test('should return pending transaction for execute affirmation transaction', async () => { + const transactions = [ + { input: `0x${EXECUTE_AFFIRMATION_HASH}112233`, to: bridgeAddress }, + { input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: bridgeAddress }, + { input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: otherAddress }, + { input: `0x${OTHER_HASH}123456`, to: bridgeAddress }, + { input: `0x${OTHER_HASH}112233`, to: bridgeAddress } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getValidatorPendingTransactionsForMessage( + { + account: '', + to: bridgeAddress, + messageData + }, + fetchAccountTransactions + ) + + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_AFFIRMATION_HASH}123456`, to: bridgeAddress }) + }) +}) +describe('getExecutionPendingTransactionsForMessage', () => { + test('should return pending transaction for signatures execution transaction', async () => { + const transactions = [ + { input: `0x${EXECUTE_SIGNATURES_HASH}112233`, to: bridgeAddress }, + { input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: bridgeAddress }, + { input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: otherAddress }, + { input: `0x${OTHER_HASH}123456`, to: bridgeAddress }, + { input: `0x${OTHER_HASH}112233`, to: bridgeAddress } + ] as APITransaction[] + const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) + + const result = await getExecutionPendingTransactionsForMessage( + { + account: '', + to: bridgeAddress, + messageData + }, + fetchAccountTransactions + ) + + expect(result.length).toEqual(1) + expect(result[0]).toEqual({ input: `0x${EXECUTE_SIGNATURES_HASH}123456`, to: bridgeAddress }) + }) +}) diff --git a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts new file mode 100644 index 00000000..66080d2c --- /dev/null +++ b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts @@ -0,0 +1,437 @@ +import 'jest' +import { getConfirmationsForTx } from '../getConfirmationsForTx' +import * as helpers from '../validatorConfirmationHelpers' +import Web3 from 'web3' +import { Contract } from 'web3-eth-contract' +import { APIPendingTransaction, APITransaction } from '../explorer' +import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants' +import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations' + +jest.mock('../validatorConfirmationHelpers') + +const getValidatorSuccessTransaction = helpers.getValidatorSuccessTransaction as jest.Mock +const getValidatorConfirmation = helpers.getValidatorConfirmation as jest.Mock +const getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock +const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock + +const messageData = '0x111111111' +const web3 = { + utils: { + soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}` + } +} as Web3 +const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' +const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f' +const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' +const validatorList = [validator1, validator2, validator3] +const bridgeContract = {} as Contract +const confirmationContractMethod = () => {} +const requiredSignatures = 2 +const waitingBlocksResolved = true +let subscriptions: Array = [] +const timestamp = 1594045859 +const getFailedTransactions = (): Promise => Promise.resolve([]) +const getPendingTransactions = (): Promise => Promise.resolve([]) +const getSuccessTransactions = (): Promise => Promise.resolve([]) + +const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) +} + +beforeEach(() => { + // Clear all instances and calls to constructor and all methods: + getValidatorSuccessTransaction.mockClear() + getValidatorConfirmation.mockClear() + getValidatorFailedTransaction.mockClear() + getValidatorPendingTransaction.mockClear() + subscriptions = [] +}) +describe('getConfirmationsForTx', () => { + test('should set validator confirmations status when signatures collected even if validator transactions not found yet and set remaining validator as not required', async () => { + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: '', + timestamp: 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(1) + + expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + }) + test('should set validator confirmations status when signatures not collected even if validator transactions not found yet', async () => { + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: '', + timestamp: 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(0) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(0) + }) + test('should set validator confirmations status, validator transactions and not retry', async () => { + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator !== validator3 ? '0x123' : '', + timestamp: validatorData.validator !== validator3 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(1) + + expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + }) + test('should look for failed and pending transactions for not confirmed validators', async () => { + // Validator1 success + // Validator2 failed + // Validator3 Pending + + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator === validator1 ? '0x123' : '', + timestamp: validatorData.validator === validator1 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator2 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator2 ? '0x123' : '', + timestamp: validatorData.validator === validator2 ? 123 : 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator3 + ? VALIDATOR_CONFIRMATION_STATUS.PENDING + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator3 ? '0x123' : '', + timestamp: validatorData.validator === validator3 ? 123 : 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(0) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(1) + + expect(setResult.mock.calls[0][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } + ]) + ) + }) + test('should set as failed if enough signatures failed', async () => { + // Validator1 success + // Validator2 failed + // Validator3 failed + + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator === validator1 ? '0x123' : '', + timestamp: validatorData.validator === validator1 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator !== validator1 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator !== validator1 ? '0x123' : '', + timestamp: validatorData.validator !== validator1 ? 123 : 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(0) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(1) + + expect(getValidatorPendingTransaction).toBeCalledTimes(1) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 } + ]) + ) + }) +}) diff --git a/alm/src/utils/__tests__/getFinalizationEvent.test.ts b/alm/src/utils/__tests__/getFinalizationEvent.test.ts new file mode 100644 index 00000000..506533ed --- /dev/null +++ b/alm/src/utils/__tests__/getFinalizationEvent.test.ts @@ -0,0 +1,303 @@ +import 'jest' +import { Contract, EventData } from 'web3-eth-contract' +import Web3 from 'web3' +import { getFinalizationEvent } from '../getFinalizationEvent' +import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants' + +const eventName = 'RelayedMessage' +const timestamp = 1594045859 +const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' +const txHash = '0xdab36c9210e7e45fb82af10ffe4960461e41661dce0c9cd36b2843adaa1df156' + +const web3 = ({ + eth: { + getTransactionReceipt: () => ({ + from: validator1 + }), + getBlock: () => ({ timestamp }) + }, + utils: { + toChecksumAddress: (a: string) => a + } +} as unknown) as Web3 +const waitingBlocksResolved = true +const message = { + id: '0x123', + data: '0x123456789' +} +const interval = 10000 +let subscriptions: Array = [] + +const event = { + transactionHash: txHash, + blockNumber: 5523145, + returnValues: { + status: true + } +} + +const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560' + +const unsubscribe = () => { + subscriptions.forEach(s => { + clearTimeout(s) + }) +} + +beforeEach(() => { + subscriptions = [] +}) +describe('getFinalizationEvent', () => { + test('should get finalization event and not try to get failed or pending transactions', async () => { + const contract = ({ + getPastEvents: () => { + return [event] + } + } as unknown) as Contract + + const collectedSignaturesEvent = null + const setResult = jest.fn() + const getFailedExecution = jest.fn() + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn() + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(1) + expect(setResult.mock.calls[0][0]).toEqual({ + validator: validator1, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash, + timestamp, + executionResult: true + }) + + expect(getFailedExecution).toBeCalledTimes(0) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(0) + expect(setPendingExecution).toBeCalledTimes(0) + }) + test('should retry to get finalization event and not try to get failed or pending transactions if foreign to home transaction', async () => { + const contract = ({ + getPastEvents: () => { + return [] + } + } as unknown) as Contract + + const collectedSignaturesEvent = null + const setResult = jest.fn() + const getFailedExecution = jest.fn() + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn() + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(0) + + expect(getFailedExecution).toBeCalledTimes(0) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(0) + expect(setPendingExecution).toBeCalledTimes(0) + }) + test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => { + const contract = ({ + getPastEvents: () => { + return [] + }, + options: { + address: bridgeAddress + } + } as unknown) as Contract + + const collectedSignaturesEvent = ({ + returnValues: { + authorityResponsibleForRelay: validator1 + } + } as unknown) as EventData + const setResult = jest.fn() + const getFailedExecution = jest.fn().mockResolvedValue([]) + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn().mockResolvedValue([]) + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(0) + + expect(getFailedExecution).toBeCalledTimes(1) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(1) + expect(setPendingExecution).toBeCalledTimes(0) + }) + test('should retry to get finalization event and not to try to get failed transaction if pending transactions found if home to foreign transaction', async () => { + const contract = ({ + getPastEvents: () => { + return [] + }, + options: { + address: bridgeAddress + } + } as unknown) as Contract + + const collectedSignaturesEvent = ({ + returnValues: { + authorityResponsibleForRelay: validator1 + } + } as unknown) as EventData + const setResult = jest.fn() + const getFailedExecution = jest.fn().mockResolvedValue([]) + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn().mockResolvedValue([{ hash: txHash }]) + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(1) + expect(setResult.mock.calls[0][0]).toEqual({ + validator: validator1, + status: VALIDATOR_CONFIRMATION_STATUS.PENDING, + txHash, + timestamp: expect.any(Number), + executionResult: false + }) + + expect(getFailedExecution).toBeCalledTimes(0) + expect(setFailedExecution).toBeCalledTimes(0) + + expect(getPendingExecution).toBeCalledTimes(1) + expect(setPendingExecution).toBeCalledTimes(1) + }) + test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => { + const contract = ({ + getPastEvents: () => { + return [] + }, + options: { + address: bridgeAddress + } + } as unknown) as Contract + + const collectedSignaturesEvent = ({ + returnValues: { + authorityResponsibleForRelay: validator1 + } + } as unknown) as EventData + const setResult = jest.fn() + const getFailedExecution = jest.fn().mockResolvedValue([{ timeStamp: timestamp, hash: txHash }]) + const setFailedExecution = jest.fn() + const getPendingExecution = jest.fn().mockResolvedValue([]) + const setPendingExecution = jest.fn() + + await getFinalizationEvent( + contract, + eventName, + web3, + setResult, + waitingBlocksResolved, + message, + interval, + subscriptions, + timestamp, + collectedSignaturesEvent, + getFailedExecution, + setFailedExecution, + getPendingExecution, + setPendingExecution + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(1) + expect(setResult).toBeCalledTimes(1) + expect(setResult.mock.calls[0][0]).toEqual({ + validator: validator1, + status: VALIDATOR_CONFIRMATION_STATUS.FAILED, + txHash, + timestamp: expect.any(Number), + executionResult: false + }) + + expect(getFailedExecution).toBeCalledTimes(1) + expect(setFailedExecution).toBeCalledTimes(1) + + expect(getPendingExecution).toBeCalledTimes(1) + expect(setPendingExecution).toBeCalledTimes(0) + }) +}) diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts index d57bf926..5b354895 100644 --- a/alm/src/utils/explorer.ts +++ b/alm/src/utils/explorer.ts @@ -217,14 +217,11 @@ export const getValidatorSuccessTransactionsForMessage = async ({ return filterValidatorSignatureTransaction(transactions, messageData) } -export const getExecutionFailedTransactionForMessage = async ({ - account, - to, - messageData, - startTimestamp, - endTimestamp -}: GetFailedTransactionParams): Promise => { - const failedTransactions = await getFailedTransactions( +export const getExecutionFailedTransactionForMessage = async ( + { account, to, messageData, startTimestamp, endTimestamp }: GetFailedTransactionParams, + getFailedTransactionsMethod = getFailedTransactions +): Promise => { + const failedTransactions = await getFailedTransactionsMethod( account, to, startTimestamp, @@ -237,12 +234,11 @@ export const getExecutionFailedTransactionForMessage = async ({ return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue)) } -export const getValidatorPendingTransactionsForMessage = async ({ - account, - to, - messageData -}: GetPendingTransactionParams): Promise => { - const pendingTransactions = await fetchPendingTransactions({ +export const getValidatorPendingTransactionsForMessage = async ( + { account, to, messageData }: GetPendingTransactionParams, + fetchPendingTransactionsMethod = fetchPendingTransactions +): Promise => { + const pendingTransactions = await fetchPendingTransactionsMethod({ account, api: HOME_EXPLORER_API }) @@ -258,12 +254,11 @@ export const getValidatorPendingTransactionsForMessage = async ({ ) } -export const getExecutionPendingTransactionsForMessage = async ({ - account, - to, - messageData -}: GetPendingTransactionParams): Promise => { - const pendingTransactions = await fetchPendingTransactions({ +export const getExecutionPendingTransactionsForMessage = async ( + { account, to, messageData }: GetPendingTransactionParams, + fetchPendingTransactionsMethod = fetchPendingTransactions +): Promise => { + const pendingTransactions = await fetchPendingTransactionsMethod({ account, api: FOREIGN_EXPLORER_API }) diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index d9cc3d62..7e83cd1b 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -1,176 +1,18 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' -import validatorsCache from '../services/ValidatorsCache' -import { - CACHE_KEY_FAILED, - CACHE_KEY_SUCCESS, - HOME_RPC_POLLING_INTERVAL, - ONE_DAY_TIMESTAMP, - VALIDATOR_CONFIRMATION_STATUS -} from '../config/constants' +import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { GetFailedTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer' -import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' - -export const getValidatorConfirmation = ( - web3: Web3, - hashMsg: string, - bridgeContract: Contract, - confirmationContractMethod: Function -) => async (validator: string): Promise => { - const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) - - const signatureFromCache = validatorsCache.get(hashSenderMsg) - if (signatureFromCache) { - return { - validator, - status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS - } - } - - const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) - const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - - // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change - if (confirmed) { - validatorsCache.set(hashSenderMsg, confirmed) - } - - return { - validator, - status - } -} - -export const getValidatorSuccessTransaction = ( - bridgeContract: Contract, - messageData: string, - timestamp: number, - getSuccessTransactions: (args: GetFailedTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { - const { validator } = validatorData - const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` - const fromCache = validatorsCache.getData(validatorCacheKey) - - if (fromCache && fromCache.txHash) { - return fromCache - } - - const transactions = await getSuccessTransactions({ - account: validatorData.validator, - to: bridgeContract.options.address, - messageData, - startTimestamp: timestamp, - endTimestamp: timestamp + ONE_DAY_TIMESTAMP - }) - - let txHashTimestamp = 0 - let txHash = '' - const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS - - if (transactions.length > 0) { - const tx = transactions[0] - txHashTimestamp = parseInt(tx.timeStamp) - txHash = tx.hash - - // cache the result - validatorsCache.setData(validatorCacheKey, { - validator, - status, - txHash, - timestamp: txHashTimestamp - }) - } - - return { - validator, - status, - txHash, - timestamp: txHashTimestamp - } -} - -export const getValidatorFailedTransaction = ( - bridgeContract: Contract, - messageData: string, - timestamp: number, - getFailedTransactions: (args: GetFailedTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { - const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` - const failedFromCache = validatorsCache.getData(validatorCacheKey) - - if (failedFromCache && failedFromCache.txHash) { - return failedFromCache - } - - const failedTransactions = await getFailedTransactions({ - account: validatorData.validator, - to: bridgeContract.options.address, - messageData, - startTimestamp: timestamp, - endTimestamp: timestamp + ONE_DAY_TIMESTAMP - }) - const newStatus = - failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - - let txHashTimestamp = 0 - let txHash = '' - // If validator signature failed, we cache the result to avoid doing future requests for a result that won't change - if (failedTransactions.length > 0) { - const failedTx = failedTransactions[0] - txHashTimestamp = parseInt(failedTx.timeStamp) - txHash = failedTx.hash - - validatorsCache.setData(validatorCacheKey, { - validator: validatorData.validator, - status: newStatus, - txHash, - timestamp: txHashTimestamp - }) - } - - return { - validator: validatorData.validator, - status: newStatus, - txHash, - timestamp: txHashTimestamp - } -} - -export const getValidatorPendingTransaction = ( - bridgeContract: Contract, - messageData: string, - getPendingTransactions: (args: GetPendingTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { - const failedTransactions = await getPendingTransactions({ - account: validatorData.validator, - to: bridgeContract.options.address, - messageData - }) - - const newStatus = - failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - - let timestamp = 0 - let txHash = '' - - if (failedTransactions.length > 0) { - const failedTx = failedTransactions[0] - timestamp = Math.floor(new Date().getTime() / 1000.0) - txHash = failedTx.hash - } - - return { - validator: validatorData.validator, - status: newStatus, - txHash, - timestamp - } -} +import { + getValidatorConfirmation, + getValidatorFailedTransaction, + getValidatorPendingTransaction, + getValidatorSuccessTransaction +} from './validatorConfirmationHelpers' export const getConfirmationsForTx = async ( messageData: string, diff --git a/alm/src/utils/validatorConfirmationHelpers.ts b/alm/src/utils/validatorConfirmationHelpers.ts new file mode 100644 index 00000000..0b5c65e6 --- /dev/null +++ b/alm/src/utils/validatorConfirmationHelpers.ts @@ -0,0 +1,172 @@ +import Web3 from 'web3' +import { Contract } from 'web3-eth-contract' +import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' +import validatorsCache from '../services/ValidatorsCache' +import { + CACHE_KEY_FAILED, + CACHE_KEY_SUCCESS, + ONE_DAY_TIMESTAMP, + VALIDATOR_CONFIRMATION_STATUS +} from '../config/constants' +import { + APIPendingTransaction, + APITransaction, + GetFailedTransactionParams, + GetPendingTransactionParams +} from './explorer' + +export const getValidatorConfirmation = ( + web3: Web3, + hashMsg: string, + bridgeContract: Contract, + confirmationContractMethod: Function +) => async (validator: string): Promise => { + const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) + + const signatureFromCache = validatorsCache.get(hashSenderMsg) + if (signatureFromCache) { + return { + validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS + } + } + + const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) + const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change + if (confirmed) { + validatorsCache.set(hashSenderMsg, confirmed) + } + + return { + validator, + status + } +} + +export const getValidatorSuccessTransaction = ( + bridgeContract: Contract, + messageData: string, + timestamp: number, + getSuccessTransactions: (args: GetFailedTransactionParams) => Promise +) => async (validatorData: BasicConfirmationParam): Promise => { + const { validator } = validatorData + const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` + const fromCache = validatorsCache.getData(validatorCacheKey) + + if (fromCache && fromCache.txHash) { + return fromCache + } + + const transactions = await getSuccessTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData, + startTimestamp: timestamp, + endTimestamp: timestamp + ONE_DAY_TIMESTAMP + }) + + let txHashTimestamp = 0 + let txHash = '' + const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS + + if (transactions.length > 0) { + const tx = transactions[0] + txHashTimestamp = parseInt(tx.timeStamp) + txHash = tx.hash + + // cache the result + validatorsCache.setData(validatorCacheKey, { + validator, + status, + txHash, + timestamp: txHashTimestamp + }) + } + + return { + validator, + status, + txHash, + timestamp: txHashTimestamp + } +} + +export const getValidatorFailedTransaction = ( + bridgeContract: Contract, + messageData: string, + timestamp: number, + getFailedTransactions: (args: GetFailedTransactionParams) => Promise +) => async (validatorData: BasicConfirmationParam): Promise => { + const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` + const failedFromCache = validatorsCache.getData(validatorCacheKey) + + if (failedFromCache && failedFromCache.txHash) { + return failedFromCache + } + + const failedTransactions = await getFailedTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData, + startTimestamp: timestamp, + endTimestamp: timestamp + ONE_DAY_TIMESTAMP + }) + const newStatus = + failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + let txHashTimestamp = 0 + let txHash = '' + // If validator signature failed, we cache the result to avoid doing future requests for a result that won't change + if (failedTransactions.length > 0) { + const failedTx = failedTransactions[0] + txHashTimestamp = parseInt(failedTx.timeStamp) + txHash = failedTx.hash + + validatorsCache.setData(validatorCacheKey, { + validator: validatorData.validator, + status: newStatus, + txHash, + timestamp: txHashTimestamp + }) + } + + return { + validator: validatorData.validator, + status: newStatus, + txHash, + timestamp: txHashTimestamp + } +} + +export const getValidatorPendingTransaction = ( + bridgeContract: Contract, + messageData: string, + getPendingTransactions: (args: GetPendingTransactionParams) => Promise +) => async (validatorData: BasicConfirmationParam): Promise => { + const failedTransactions = await getPendingTransactions({ + account: validatorData.validator, + to: bridgeContract.options.address, + messageData + }) + + const newStatus = + failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + + let timestamp = 0 + let txHash = '' + + if (failedTransactions.length > 0) { + const failedTx = failedTransactions[0] + timestamp = Math.floor(new Date().getTime() / 1000.0) + txHash = failedTx.hash + } + + return { + validator: validatorData.validator, + status: newStatus, + txHash, + timestamp + } +}