diff --git a/Dockerfile.e2e b/Dockerfile.e2e index b2affcb8..715a4cd0 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -19,6 +19,7 @@ COPY --from=contracts /mono/contracts/build ./contracts/build COPY commons/package.json ./commons/ COPY oracle-e2e/package.json ./oracle-e2e/ COPY monitor-e2e/package.json ./monitor-e2e/ +COPY oracle/src/utils/constants.js ./oracle/src/utils/constants.js COPY yarn.lock . RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production diff --git a/oracle-e2e/test/amb.js b/oracle-e2e/test/amb.js index b69ff3e3..ef0c2026 100644 --- a/oracle-e2e/test/amb.js +++ b/oracle-e2e/test/amb.js @@ -1,5 +1,6 @@ const Web3 = require('web3') const assert = require('assert') +const { ASYNC_CALL_ERRORS } = require('../../oracle/src/utils/constants') const { user, homeRPC, foreignRPC, amb, validator } = require('../../e2e-commons/constants.json') const { uniformRetry } = require('../../e2e-commons/utils') const { BOX_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI, ambInformationSignatures } = require('../../commons') @@ -26,6 +27,45 @@ const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox, opts) const homeBridge = new homeWeb3.eth.Contract(HOME_AMB_ABI, amb.home, opts) const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, amb.foreign, opts) +function validateBlock(web3, serialized, block) { + assert.strictEqual(serialized.length, 2 + 64 * 12) + const values = web3.eth.abi.decodeParameter( + '(uint256,bytes32,address,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256)', + serialized + ) + assert.strictEqual(values[0], block.number.toString(), 'wrong block number returned') + assert.strictEqual(values[1], block.hash, 'wrong block hash returned') + assert.strictEqual(values[2], block.miner, 'wrong block miner returned') + assert.strictEqual(values[3], block.gasUsed.toString(), 'wrong block gasUsed returned') + assert.strictEqual(values[4], block.gasLimit.toString(), 'wrong block gasLimit returned') + assert.strictEqual(values[5], block.parentHash, 'wrong block parentHash returned') + assert.strictEqual(values[6], block.receiptsRoot, 'wrong block receiptsRoot returned') + assert.strictEqual(values[7], block.stateRoot, 'wrong block stateRoot returned') + assert.strictEqual(values[8], block.transactionsRoot, 'wrong block transactionsRoot returned') + assert.strictEqual(values[9], block.timestamp.toString(), 'wrong block timestamp returned') + assert.strictEqual(values[10], block.difficulty, 'wrong block difficulty returned') + assert.strictEqual(values[11], block.totalDifficulty, 'wrong block totalDifficulty returned') +} + +function validateTransaction(web3, serialized, tx) { + assert.strictEqual(serialized.length, 64 * 13 + tx.input.length + 56) + const values = web3.eth.abi.decodeParameter( + '(bytes32,uint256,bytes32,uint256,address,address,uint256,uint256,uint256,uint256,bytes)', + serialized + ) + assert.strictEqual(values[0], tx.hash, 'wrong txHash returned') + assert.strictEqual(values[1], tx.blockNumber.toString(), 'wrong tx blockNumber returned') + assert.strictEqual(values[2], tx.blockHash.toString(), 'wrong tx blockHash returned') + assert.strictEqual(values[3], tx.transactionIndex.toString(), 'wrong tx transactionIndex returned') + assert.strictEqual(values[4], tx.from, 'wrong tx from returned') + assert.strictEqual(values[5], tx.to, 'wrong tx to returned') + assert.strictEqual(values[6], tx.value, 'wrong tx value returned') + assert.strictEqual(values[7], tx.nonce.toString(), 'wrong tx nonce returned') + assert.strictEqual(values[8], tx.gas.toString(), 'wrong tx gas returned') + assert.strictEqual(values[9], tx.gasPrice, 'wrong tx gasPrice returned') + assert.strictEqual(values[10], tx.input, 'wrong tx data returned') +} + describe('arbitrary message bridging', () => { let requiredSignatures = 1 before(async () => { @@ -264,7 +304,7 @@ describe('arbitrary message bridging', () => { await makeAsyncCall(selector, data2) assert(!(await homeBox.methods.status().call()), 'status is true') - assert.strictEqual(await homeBox.methods.data().call(), null, 'returned data is incorrect') + assert.strictEqual(await homeBox.methods.data().call(), ASYNC_CALL_ERRORS.REVERT, 'returned data is incorrect') const data3 = homeWeb3.eth.abi.encodeParameters( ['address', 'address', 'uint256', 'bytes'], @@ -274,7 +314,7 @@ describe('arbitrary message bridging', () => { await makeAsyncCall(selector, data3) assert(!(await homeBox.methods.status().call()), 'status is true') - assert.strictEqual(await homeBox.methods.data().call(), null, 'returned data is incorrect') + assert.strictEqual(await homeBox.methods.data().call(), ASYNC_CALL_ERRORS.REVERT, 'returned data is incorrect') }) it('should make async eth_call for specific block', async () => { @@ -315,6 +355,11 @@ describe('arbitrary message bridging', () => { await makeAsyncCall(selector, data3) assert(!(await homeBox.methods.status().call()), 'status is true') + assert.strictEqual( + await homeBox.methods.data().call(), + ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE, + 'returned data is incorrect' + ) }) it('should make async eth_blockNumber', async () => { @@ -334,15 +379,8 @@ describe('arbitrary message bridging', () => { assert(await homeBox.methods.status().call(), 'status is false') const data = await homeBox.methods.data().call() - assert.strictEqual(data.length, 2 + 64 * 3) - const { 0: number, 1: hash, 2: miner } = homeWeb3.eth.abi.decodeParameters( - ['uint256', 'bytes32', 'address'], - data - ) const block = await foreignWeb3.eth.getBlock(blockNumber) - assert.strictEqual(number, blockNumber, 'wrong block number returned') - assert.strictEqual(hash, block.hash, 'wrong block hash returned') - assert.strictEqual(miner, block.miner, 'wrong block miner returned') + validateBlock(homeWeb3, data, block) }) it('should make async eth_getBlockByNumber and return latest block', async () => { @@ -352,7 +390,7 @@ describe('arbitrary message bridging', () => { assert(await homeBox.methods.status().call(), 'status is false') const data = await homeBox.methods.data().call() - assert.strictEqual(data.length, 2 + 64 * 3) + assert.strictEqual(data.length, 2 + 64 * 12) }) it('should make async eth_getBlockByHash', async () => { @@ -364,16 +402,7 @@ describe('arbitrary message bridging', () => { assert(await homeBox.methods.status().call(), 'status is false') const data = await homeBox.methods.data().call() - assert.strictEqual(data.length, 2 + 64 * 3) - - const { 0: number, 1: hash, 2: miner } = homeWeb3.eth.abi.decodeParameters( - ['uint256', 'bytes32', 'address'], - data - ) - - assert.strictEqual(number, blockNumber, 'wrong block number returned') - assert.strictEqual(hash, block.hash, 'wrong block hash returned') - assert.strictEqual(miner, block.miner, 'wrong block miner returned') + validateBlock(homeWeb3, data, block) }) it('should make async eth_getBalance', async () => { @@ -469,28 +498,7 @@ describe('arbitrary message bridging', () => { assert(await homeBox.methods.status().call(), 'status is false') const data = await homeBox.methods.data().call() - const dataTypes = [ - 'bytes32', - 'uint256', - 'address', - 'address', - 'uint256', - 'uint256', - 'uint256', - 'uint256', - 'bytes' - ] - const values = homeWeb3.eth.abi.decodeParameters(dataTypes, data) - - assert.strictEqual(values[0], txHash, 'wrong txHash returned') - assert.strictEqual(values[1], tx.blockNumber.toString(), 'wrong tx blockNumber returned') - assert.strictEqual(values[2], tx.from, 'wrong tx from returned') - assert.strictEqual(values[3], tx.to, 'wrong tx to returned') - assert.strictEqual(values[4], tx.value, 'wrong tx value returned') - assert.strictEqual(values[5], tx.nonce.toString(), 'wrong tx nonce returned') - assert.strictEqual(values[6], tx.gas.toString(), 'wrong tx gas returned') - assert.strictEqual(values[7], tx.gasPrice, 'wrong tx gasPrice returned') - assert.strictEqual(values[8], tx.input, 'wrong tx data returned') + validateTransaction(homeWeb3, data, tx) }) it('should make async eth_getTransactionReceipt', async () => { @@ -502,18 +510,25 @@ describe('arbitrary message bridging', () => { assert(await homeBox.methods.status().call(), 'status is false') const data = await homeBox.methods.data().call() - const dataTypes = ['bytes32', 'uint256', 'bool', '(address,bytes32[],bytes)[]'] - const values = homeWeb3.eth.abi.decodeParameters(dataTypes, data) + const values = homeWeb3.eth.abi.decodeParameter( + '(bytes32,uint256,bytes32,uint256,address,address,uint256,bool,(address,bytes32[],bytes)[])', + data + ) assert.strictEqual(values[0], txHash, 'wrong txHash returned') assert.strictEqual(values[1], receipt.blockNumber.toString(), 'wrong tx blockNumber returned') - assert.strictEqual(values[2], receipt.status, 'wrong tx status returned') - assert.strictEqual(values[3].length, 1, 'wrong logs length returned') - assert.strictEqual(values[3][0][0], receipt.logs[0].address, 'wrong log address returned') - assert.strictEqual(values[3][0][1].length, 2, 'wrong log topics length returned') - assert.strictEqual(values[3][0][1][0], receipt.logs[0].topics[0], 'wrong event signature returned') - assert.strictEqual(values[3][0][1][1], receipt.logs[0].topics[1], 'wrong message id returned') - assert.strictEqual(values[3][0][2], receipt.logs[0].data, 'wrong log data returned') + assert.strictEqual(values[2], receipt.blockHash, 'wrong tx blockHash returned') + assert.strictEqual(values[3], receipt.transactionIndex.toString(), 'wrong tx transactionIndex returned') + assert.strictEqual(values[4].toLowerCase(), receipt.from, 'wrong tx from returned') + assert.strictEqual(values[5].toLowerCase(), receipt.to, 'wrong tx to returned') + assert.strictEqual(values[6], receipt.gasUsed.toString(), 'wrong gasUsed to returned') + assert.strictEqual(values[7], receipt.status, 'wrong tx status returned') + assert.strictEqual(values[8].length, 1, 'wrong logs length returned') + assert.strictEqual(values[8][0][0], receipt.logs[0].address, 'wrong log address returned') + assert.strictEqual(values[8][0][1].length, 2, 'wrong log topics length returned') + assert.strictEqual(values[8][0][1][0], receipt.logs[0].topics[0], 'wrong event signature returned') + assert.strictEqual(values[8][0][1][1], receipt.logs[0].topics[1], 'wrong message id returned') + assert.strictEqual(values[8][0][2], receipt.logs[0].data, 'wrong log data returned') }) it('should make async eth_getStorageAt', async () => { diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethCall.js b/oracle/src/events/processAMBInformationRequests/calls/ethCall.js index 50c8d5e7..4fc0a6fe 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethCall.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethCall.js @@ -1,6 +1,7 @@ const { toBN } = require('web3').utils -const { zipToObject } = require('../../../utils/utils') +const { ASYNC_CALL_ERRORS, ASYNC_ETH_CALL_MAX_GAS_LIMIT } = require('../../../utils/constants') +const { zipToObject, isRevertError } = require('../../../utils/utils') const argTypes = { to: 'address', @@ -17,14 +18,23 @@ function makeCall(argNames) { const { blockNumber, ...opts } = zipToObject(argNames, args) if (blockNumber && toBN(blockNumber).gt(toBN(foreignBlock.number))) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE] } - const [status, result] = await web3.eth - .call(opts, blockNumber || foreignBlock.number) - .then(result => [true, result], err => [false, err.data]) + // different clients might use different default gas limits, so it makes sense to limit it by some large number + if (!opts.gas || toBN(opts.gas).gt(toBN(ASYNC_ETH_CALL_MAX_GAS_LIMIT))) { + opts.gas = ASYNC_ETH_CALL_MAX_GAS_LIMIT + } - return [status, web3.eth.abi.encodeParameter('bytes', result)] + return web3.eth + .call(opts, blockNumber || foreignBlock.number) + .then(result => [true, web3.eth.abi.encodeParameter('bytes', result)]) + .catch(e => { + if (isRevertError(e)) { + return [false, ASYNC_CALL_ERRORS.REVERT] + } + throw e + }) } } diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetBalance.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetBalance.js index b301696c..ad1a8f06 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethGetBalance.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetBalance.js @@ -1,5 +1,7 @@ const { toBN } = require('web3').utils +const { ASYNC_CALL_ERRORS } = require('../../../utils/constants') + async function call(web3, data, foreignBlock) { const address = web3.eth.abi.decodeParameter('address', data) @@ -12,7 +14,7 @@ async function callArchive(web3, data, foreignBlock) { const { 0: address, 1: blockNumber } = web3.eth.abi.decodeParameters(['address', 'uint256'], data) if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE] } const balance = await web3.eth.getBalance(address, blockNumber) diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByHash.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByHash.js index 523e428c..a62c3174 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByHash.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByHash.js @@ -1,3 +1,4 @@ +const { ASYNC_CALL_ERRORS } = require('../../../utils/constants') const { serializeBlock } = require('./serializers') async function call(web3, data, foreignBlock) { @@ -6,7 +7,7 @@ async function call(web3, data, foreignBlock) { const block = await web3.eth.getBlock(blockHash) if (block === null || block.number > foreignBlock.number) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.NOT_FOUND] } return [true, serializeBlock(web3, block)] diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByNumber.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByNumber.js index 030e3333..517f31f2 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByNumber.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetBlockByNumber.js @@ -1,12 +1,13 @@ const { toBN } = require('web3').utils +const { ASYNC_CALL_ERRORS } = require('../../../utils/constants') const { serializeBlock } = require('./serializers') async function call(web3, data, foreignBlock) { const blockNumber = web3.eth.abi.decodeParameter('uint256', data) if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE] } const block = await web3.eth.getBlock(blockNumber) diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetStorageAt.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetStorageAt.js index 7f6c7ddc..abddf062 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethGetStorageAt.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetStorageAt.js @@ -1,5 +1,7 @@ const { toBN } = require('web3').utils +const { ASYNC_CALL_ERRORS } = require('../../../utils/constants') + async function call(web3, data, foreignBlock) { const { 0: address, 1: slot } = web3.eth.abi.decodeParameters(['address', 'bytes32'], data) @@ -12,7 +14,7 @@ async function callArchive(web3, data, foreignBlock) { const { 0: address, 1: slot, 2: blockNumber } = web3.eth.abi.decodeParameters(['address', 'bytes32', 'uint256'], data) if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE] } const value = await web3.eth.getStorageAt(address, slot, blockNumber) diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionByHash.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionByHash.js index 039db023..6e0f8dfb 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionByHash.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionByHash.js @@ -1,3 +1,4 @@ +const { ASYNC_CALL_ERRORS } = require('../../../utils/constants') const { serializeTx } = require('./serializers') async function call(web3, data, foreignBlock) { @@ -6,7 +7,7 @@ async function call(web3, data, foreignBlock) { const tx = await web3.eth.getTransaction(hash) if (tx === null || tx.blockNumber > foreignBlock.number) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.NOT_FOUND] } return [true, serializeTx(web3, tx)] diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionCount.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionCount.js index a20c8cac..0643e237 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionCount.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionCount.js @@ -1,5 +1,7 @@ const { toBN } = require('web3').utils +const { ASYNC_CALL_ERRORS } = require('../../../utils/constants') + async function call(web3, data, foreignBlock) { const address = web3.eth.abi.decodeParameter('address', data) @@ -12,7 +14,7 @@ async function callArchive(web3, data, foreignBlock) { const { 0: address, 1: blockNumber } = web3.eth.abi.decodeParameters(['address', 'uint256'], data) if (toBN(blockNumber).gt(toBN(foreignBlock.number))) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE] } const nonce = await web3.eth.getTransactionCount(address, blockNumber) diff --git a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionReceipt.js b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionReceipt.js index c794e5d2..a0a00f54 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionReceipt.js +++ b/oracle/src/events/processAMBInformationRequests/calls/ethGetTransactionReceipt.js @@ -1,3 +1,4 @@ +const { ASYNC_CALL_ERRORS } = require('../../../utils/constants') const { serializeReceipt } = require('./serializers') async function call(web3, data, foreignBlock) { @@ -6,7 +7,7 @@ async function call(web3, data, foreignBlock) { const receipt = await web3.eth.getTransactionReceipt(hash) if (receipt === null || receipt.blockNumber > foreignBlock.number) { - return [false, '0x'] + return [false, ASYNC_CALL_ERRORS.NOT_FOUND] } return [true, serializeReceipt(web3, receipt)] diff --git a/oracle/src/events/processAMBInformationRequests/calls/serializers.js b/oracle/src/events/processAMBInformationRequests/calls/serializers.js index 22d701b5..ecc6d527 100644 --- a/oracle/src/events/processAMBInformationRequests/calls/serializers.js +++ b/oracle/src/events/processAMBInformationRequests/calls/serializers.js @@ -1,15 +1,30 @@ const { ZERO_ADDRESS } = require('../../../../../commons') const serializeBlock = (web3, block) => { - const args = [block.number, block.hash, block.miner] - const types = ['uint256', 'bytes32', 'address'] - return web3.eth.abi.encodeParameters(types, args) + const args = [ + block.number, + block.hash, + block.miner, + block.gasUsed, + block.gasLimit, + block.parentHash, + block.receiptsRoot, + block.stateRoot, + block.transactionsRoot, + block.timestamp, + block.difficulty, + block.totalDifficulty + ] + const type = '(uint256,bytes32,address,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256)' + return web3.eth.abi.encodeParameter(type, args) } const serializeTx = (web3, tx) => { const args = [ tx.hash, tx.blockNumber, + tx.blockHash, + tx.transactionIndex, tx.from, tx.to || ZERO_ADDRESS, tx.value, @@ -18,16 +33,26 @@ const serializeTx = (web3, tx) => { tx.gasPrice, tx.input ] - const types = ['bytes32', 'uint256', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'] - return web3.eth.abi.encodeParameters(types, args) + const type = '(bytes32,uint256,bytes32,uint256,address,address,uint256,uint256,uint256,uint256,bytes)' + return web3.eth.abi.encodeParameter(type, args) } const normalizeLog = log => [log.address, log.topics, log.data] const serializeReceipt = (web3, receipt) => { - const args = [receipt.transactionHash, receipt.blockNumber, receipt.status, receipt.logs.map(normalizeLog)] - const types = ['bytes32', 'uint256', 'bool', '(address,bytes32[],bytes)[]'] - return web3.eth.abi.encodeParameters(types, args) + const args = [ + receipt.transactionHash, + receipt.blockNumber, + receipt.blockHash, + receipt.transactionIndex, + receipt.from, + receipt.to || ZERO_ADDRESS, + receipt.gasUsed, + receipt.status, + receipt.logs.map(normalizeLog) + ] + const type = '(bytes32,uint256,bytes32,uint256,address,address,uint256,bool,(address,bytes32[],bytes)[])' + return web3.eth.abi.encodeParameter(type, args) } module.exports = { diff --git a/oracle/src/events/processAMBInformationRequests/estimateGas.js b/oracle/src/events/processAMBInformationRequests/estimateGas.js index d163fb16..b08668ed 100644 --- a/oracle/src/events/processAMBInformationRequests/estimateGas.js +++ b/oracle/src/events/processAMBInformationRequests/estimateGas.js @@ -6,7 +6,16 @@ const logger = require('../../services/logger').child({ const { strip0x } = require('../../../../commons') const { AMB_AFFIRMATION_REQUEST_EXTRA_GAS_ESTIMATOR: estimateExtraGas } = require('../../utils/constants') -async function estimateGas({ web3, homeBridge, validatorContract, messageId, status, result, address }) { +async function estimateGas({ + web3, + homeBridge, + validatorContract, + messageId, + status, + result, + address, + homeBlockNumber +}) { try { const gasEstimate = await homeBridge.methods.confirmInformation(messageId, status, result).estimateGas({ from: address @@ -51,6 +60,20 @@ async function estimateGas({ web3, homeBridge, validatorContract, messageId, sta throw new InvalidValidatorError(`${address} is not a validator`) } + logger.debug('Check if InformationRetrieved event for this message already exists') + const logs = await homeBridge.getPastEvents('InformationRetrieved', { + fromBlock: homeBlockNumber, + toBlock: 'latest', + filter: { messageId } + }) + if (logs.length > 0) { + logger.warn( + 'This particular message was already signed and processed by other validators.' + + 'However, evaluated async call result is different from the one recorded on-chain.' + ) + throw new AlreadyProcessedError(e.message) + } + throw new Error('Unknown error while processing message') } } diff --git a/oracle/src/events/processAMBInformationRequests/index.js b/oracle/src/events/processAMBInformationRequests/index.js index 5b433c9d..b3711b06 100644 --- a/oracle/src/events/processAMBInformationRequests/index.js +++ b/oracle/src/events/processAMBInformationRequests/index.js @@ -4,7 +4,13 @@ const { soliditySha3 } = require('web3').utils const { HttpListProviderError } = require('../../services/HttpListProvider') const rootLogger = require('../../services/logger') const makeBlockFinder = require('../../services/blockFinder') -const { EXIT_CODES, MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants') +const { + EXIT_CODES, + MAX_CONCURRENT_EVENTS, + EXTRA_GAS_ABSOLUTE, + ASYNC_CALL_ERRORS, + MAX_ASYNC_CALL_RESULT_LENGTH +} = require('../../utils/constants') const estimateGas = require('./estimateGas') const { getValidatorContract, getBlock, getBlockNumber, getRequiredBlockConfirmations } = require('../../tx/web3') const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors') @@ -79,12 +85,17 @@ function processInformationRequestsBuilder(config) { logger.info({ requestSelector, method: asyncCallMethod, data }, 'Processing async request') const call = asyncCalls[asyncCallMethod] - const [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => { + let [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => { if (e instanceof HttpListProviderError) { throw e } - return [false, '0x'] + logger.error({ error: e.message }, 'Unknown error during async call execution') + throw e }) + if (result.length > 2 + MAX_ASYNC_CALL_RESULT_LENGTH * 2) { + status = false + result = ASYNC_CALL_ERRORS.RESULT_IS_TOO_LONG + } logger.info({ requestSelector, method: asyncCallMethod, status, result }, 'Request result obtained') let gasEstimate @@ -97,7 +108,8 @@ function processInformationRequestsBuilder(config) { messageId, status, result, - address: config.validatorAddress + address: config.validatorAddress, + homeBlockNumber: homeBlock.number }) logger.debug({ gasEstimate }, 'Gas estimated') } catch (e) { diff --git a/oracle/src/utils/constants.js b/oracle/src/utils/constants.js index a99c300c..63c8ef98 100644 --- a/oracle/src/utils/constants.js +++ b/oracle/src/utils/constants.js @@ -27,5 +27,17 @@ module.exports = { FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000, SENDER_QUEUE_MAX_PRIORITY: 10, SENDER_QUEUE_SEND_PRIORITY: 5, - SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1 + SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1, + ASYNC_CALL_ERRORS: { + // requested transaction/block/receipt does not exist + NOT_FOUND: '0x0000000000000000000000000000000000000000000000000000000000000000', + // requested custom block does not exist yet or its timestamp is greater than the home block timestamp + BLOCK_IS_IN_THE_FUTURE: '0x0000000000000000000000000000000000000000000000000000000000000001', + // eth_call has reverted or finished with OOG error + REVERT: '0x0000000000000000000000000000000000000000000000000000000000000002', + // evaluated output length exceeds allowed length of 64 KB + RESULT_IS_TOO_LONG: '0x0000000000000000000000000000000000000000000000000000000000000003' + }, + MAX_ASYNC_CALL_RESULT_LENGTH: 64 * 1024, + ASYNC_ETH_CALL_MAX_GAS_LIMIT: 100000000 } diff --git a/oracle/src/utils/utils.js b/oracle/src/utils/utils.js index e5ef1e64..44826a33 100644 --- a/oracle/src/utils/utils.js +++ b/oracle/src/utils/utils.js @@ -123,6 +123,18 @@ function isNonceError(e) { ) } +function isRevertError(e) { + const message = e.message.toLowerCase() + // OE and NE returns "VM execution error"/"Transaction execution error" + // Geth returns "out of gas"/"intrinsic gas too low"/"execution reverted" + return ( + message.includes('execution error') || + message.includes('intrinsic gas too low') || + message.includes('out of gas') || + message.includes('execution reverted') + ) +} + // Promise.all rejects on the first rejected Promise or fulfills with the list of results // inverted Promise.all fulfills with the first obtained result or rejects with the list of errors const invert = p => new Promise((res, rej) => p.then(rej, res)) @@ -174,6 +186,7 @@ module.exports = { isSameTransactionError, isInsufficientBalanceError, isNonceError, + isRevertError, getRetrySequence, promiseAny, readAccessListFile,