Add Alm unit tests (#388)

This commit is contained in:
Gerardo Nardelli 2020-07-09 07:20:49 -03:00 committed by GitHub
parent 2edd8f2783
commit 4c44aa5fcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1558 additions and 190 deletions

@ -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.
})

@ -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
})
})
})

@ -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 })
})
})

@ -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<any>
const getValidatorConfirmation = helpers.getValidatorConfirmation as jest.Mock<any>
const getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock<any>
const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock<any>
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<number> = []
const timestamp = 1594045859
const getFailedTransactions = (): Promise<APITransaction[]> => Promise.resolve([])
const getPendingTransactions = (): Promise<APIPendingTransaction[]> => Promise.resolve([])
const getSuccessTransactions = (): Promise<APITransaction[]> => 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 }
])
)
})
})

@ -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<number> = []
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)
})
})

@ -217,14 +217,11 @@ export const getValidatorSuccessTransactionsForMessage = async ({
return filterValidatorSignatureTransaction(transactions, messageData)
}
export const getExecutionFailedTransactionForMessage = async ({
account,
to,
messageData,
startTimestamp,
endTimestamp
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
const failedTransactions = await getFailedTransactions(
export const getExecutionFailedTransactionForMessage = async (
{ account, to, messageData, startTimestamp, endTimestamp }: GetFailedTransactionParams,
getFailedTransactionsMethod = getFailedTransactions
): Promise<APITransaction[]> => {
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<APIPendingTransaction[]> => {
const pendingTransactions = await fetchPendingTransactions({
export const getValidatorPendingTransactionsForMessage = async (
{ account, to, messageData }: GetPendingTransactionParams,
fetchPendingTransactionsMethod = fetchPendingTransactions
): Promise<APIPendingTransaction[]> => {
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<APIPendingTransaction[]> => {
const pendingTransactions = await fetchPendingTransactions({
export const getExecutionPendingTransactionsForMessage = async (
{ account, to, messageData }: GetPendingTransactionParams,
fetchPendingTransactionsMethod = fetchPendingTransactions
): Promise<APIPendingTransaction[]> => {
const pendingTransactions = await fetchPendingTransactionsMethod({
account,
api: FOREIGN_EXPLORER_API
})

@ -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<BasicConfirmationParam> => {
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<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
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<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
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<APIPendingTransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
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,

@ -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<BasicConfirmationParam> => {
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<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
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<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
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<APIPendingTransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
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
}
}