diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index 7b2713f4..e79c8389 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -313,7 +313,7 @@ export const useMessageConfirmations = ({ // Sets the message status based in the collected information useEffect( () => { - if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) { + if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) { const newStatus = executionData.executionResult ? CONFIRMATIONS_STATUS.SUCCESS : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED diff --git a/alm/src/services/BlockNumberProvider.ts b/alm/src/services/BlockNumberProvider.ts index a6606c87..686cf003 100644 --- a/alm/src/services/BlockNumberProvider.ts +++ b/alm/src/services/BlockNumberProvider.ts @@ -33,7 +33,7 @@ export class BlockNumberProvider { } stop() { - this.running = this.running - 1 + this.running = this.running > 0 ? this.running - 1 : 0 if (!this.running) { clearTimeout(this.ref) diff --git a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts index 66080d2c..4a91266b 100644 --- a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts +++ b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts @@ -105,13 +105,13 @@ describe('getConfirmationsForTx', () => { expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1) - expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(0) expect(getValidatorPendingTransaction).toBeCalledTimes(0) expect(setPendingConfirmations).toBeCalledTimes(0) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, @@ -243,13 +243,13 @@ describe('getConfirmationsForTx', () => { expect(getValidatorSuccessTransaction).toBeCalledTimes(1) expect(setSignatureCollected).toBeCalledTimes(1) - expect(getValidatorFailedTransaction).toBeCalledTimes(0) + expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(0) expect(getValidatorPendingTransaction).toBeCalledTimes(0) expect(setPendingConfirmations).toBeCalledTimes(0) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, @@ -264,6 +264,93 @@ describe('getConfirmationsForTx', () => { ]) ) }) + test('should set validator confirmations status, validator transactions, keep failed found transaction and not retry', async () => { + const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3' + const validatorList = [validator1, validator2, validator3, validator4] + getValidatorConfirmation.mockImplementation(() => async (validator: string) => ({ + validator, + status: + validator !== validator3 && validator !== validator4 + ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + })) + getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '', + timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0 + })) + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: + validatorData.validator === validator3 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: validatorData.validator === validator3 ? '0x123' : '', + timestamp: validatorData.validator === validator3 ? 123 : 0 + })) + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + validator: validatorData.validator, + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, + txHash: '', + timestamp: 0 + })) + + const setResult = jest.fn() + const setSignatureCollected = jest.fn() + const setFailedConfirmations = jest.fn() + const setPendingConfirmations = jest.fn() + + await getConfirmationsForTx( + messageData, + web3, + validatorList, + bridgeContract, + confirmationContractMethod, + setResult, + requiredSignatures, + setSignatureCollected, + waitingBlocksResolved, + subscriptions, + timestamp, + getFailedTransactions, + setFailedConfirmations, + getPendingTransactions, + setPendingConfirmations, + getSuccessTransactions + ) + + unsubscribe() + + expect(subscriptions.length).toEqual(0) + expect(setResult).toBeCalledTimes(2) + expect(getValidatorConfirmation).toBeCalledTimes(1) + expect(getValidatorSuccessTransaction).toBeCalledTimes(1) + expect(setSignatureCollected).toBeCalledTimes(1) + + expect(getValidatorFailedTransaction).toBeCalledTimes(1) + expect(setFailedConfirmations).toBeCalledTimes(0) + + expect(getValidatorPendingTransaction).toBeCalledTimes(0) + expect(setPendingConfirmations).toBeCalledTimes(0) + + expect(setResult.mock.calls[0][0]()).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } + ]) + ) + expect(setResult.mock.calls[1][0]).toEqual( + expect.arrayContaining([ + { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + ]) + ) + }) test('should look for failed and pending transactions for not confirmed validators', async () => { // Validator1 success // Validator2 failed @@ -335,7 +422,7 @@ describe('getConfirmationsForTx', () => { expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, @@ -419,7 +506,7 @@ describe('getConfirmationsForTx', () => { expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(0) - expect(setResult.mock.calls[0][0]).toEqual( + expect(setResult.mock.calls[0][0]()).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index 7e83cd1b..ab1f423e 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -13,6 +13,7 @@ import { getValidatorPendingTransaction, getValidatorSuccessTransaction } from './validatorConfirmationHelpers' +import { ConfirmationParam } from '../hooks/useMessageConfirmations' export const getConfirmationsForTx = async ( messageData: string, @@ -43,9 +44,21 @@ export const getConfirmationsForTx = async ( const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) + setResult((prevConfirmations: ConfirmationParam[]) => { + if (prevConfirmations && prevConfirmations.length) { + successConfirmations.forEach(validatorData => { + const index = prevConfirmations.findIndex(e => e.validator === validatorData.validator) + validatorConfirmations[index] = validatorData + }) + return prevConfirmations + } else { + return validatorConfirmations + } + }) + const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS) - // If signatures not collected, it needs to retry in the next blocks + // If signatures not collected, look for pending transactions if (successConfirmations.length !== requiredSignatures) { // Check if confirmation is pending const validatorPendingConfirmationsChecks = await Promise.all( @@ -63,49 +76,49 @@ export const getConfirmationsForTx = async ( if (validatorPendingConfirmations.length > 0) { setPendingConfirmations(true) } + } - const undefinedConfirmations = validatorConfirmations.filter( - c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED - ) - // Check if confirmation failed - const validatorFailedConfirmationsChecks = await Promise.all( - undefinedConfirmations.map( - getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) - ) - ) - const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( - c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED - ) - validatorFailedConfirmations.forEach(validatorData => { - const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) - validatorConfirmations[index] = validatorData - }) - const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures - if (messageConfirmationsFailed) { - setFailedConfirmations(true) - } + const undefinedConfirmations = validatorConfirmations.filter( + c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED + ) - const missingConfirmations = validatorConfirmations.filter( - c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING + // Check if confirmation failed + const validatorFailedConfirmationsChecks = await Promise.all( + undefinedConfirmations.map( + getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) ) + ) + const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( + c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED + ) + validatorFailedConfirmations.forEach(validatorData => { + const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator) + validatorConfirmations[index] = validatorData + }) + const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures + if (messageConfirmationsFailed) { + setFailedConfirmations(true) + } - if (missingConfirmations.length > 0) { - shouldRetry = true - } - } else { - // If signatures collected, it should set other signatures as not required - const notRequiredConfirmations = notSuccessConfirmations.map(c => ({ + const missingConfirmations = validatorConfirmations.filter( + c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING + ) + + if (successConfirmations.length !== requiredSignatures && missingConfirmations.length > 0) { + shouldRetry = true + } + + if (successConfirmations.length === requiredSignatures) { + // If signatures collected, it should set other signatures not found as not required + const notRequiredConfirmations = missingConfirmations.map(c => ({ validator: c.validator, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED })) - validatorConfirmations = [...successConfirmations, ...notRequiredConfirmations] + validatorConfirmations = [...validatorConfirmations, ...notRequiredConfirmations] setSignatureCollected(true) } - // Set confirmations to update UI and continue requesting the transactions for the signatures - setResult(validatorConfirmations) - // get transactions from success signatures const successConfirmationWithData = await Promise.all( validatorConfirmations