From 296e5c5a22530eefe16f7d3624d845275c31d6aa Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 3 Jan 2022 19:32:38 +0700 Subject: [PATCH 1/4] Refetch old logs ranges to see if there are missed events (#627) --- CONFIGURATION.md | 6 + e2e-commons/components-envs/oracle-amb.env | 6 + .../components-envs/oracle-erc20-native.env | 6 + oracle/config/base.config.js | 22 +++- oracle/config/foreign-sender.config.js | 1 - oracle/config/home-sender.config.js | 1 - oracle/src/sender.js | 1 - oracle/src/services/amqpClient.js | 17 +-- oracle/src/tx/web3.js | 2 +- oracle/src/utils/constants.js | 1 + oracle/src/watcher.js | 120 +++++++++++++++++- 11 files changed, 153 insertions(+), 30 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 35708b897..d953abba5 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -53,6 +53,12 @@ ORACLE_SHUTDOWN_CONTRACT_METHOD | Method signature to be used in the side chain ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Foreign chain. Infinite, if not provided. | `integer` ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Home chain. Infinite, if not provided. | `integer` ORACLE_JSONRPC_ERROR_CODES | Override default JSON rpc error codes that can trigger RPC fallback to the next URL from the list (or a retry in case of a single RPC URL). Default is `-32603,-32002,-32005`. Should be a comma-separated list of negative integers. | `string` +ORACLE_HOME_EVENTS_REPROCESSING | If set to `true`, home events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool` +ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the home chain. Defaults to `1000` | `integer` +ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the home chain. Defaults to `500` | `integer` +ORACLE_FOREIGN_EVENTS_REPROCESSING | If set to `true`, foreign events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool` +ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the foreign chain. Defaults to `500` | `integer` +ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the foreign chain. Defaults to `250` | `integer` ## Monitor configuration diff --git a/e2e-commons/components-envs/oracle-amb.env b/e2e-commons/components-envs/oracle-amb.env index a6b5cc08f..bb3a0036d 100644 --- a/e2e-commons/components-envs/oracle-amb.env +++ b/e2e-commons/components-envs/oracle-amb.env @@ -23,3 +23,9 @@ ORACLE_HOME_START_BLOCK=1 ORACLE_FOREIGN_START_BLOCK=1 ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=/mono/oracle/access-lists/block_list.txt ORACLE_FOREIGN_ARCHIVE_RPC_URL=http://parity2:8545 +ORACLE_HOME_EVENTS_REPROCESSING=false +ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE=10 +ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY=10 +ORACLE_FOREIGN_EVENTS_REPROCESSING=true +ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE=10 +ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY=10 diff --git a/e2e-commons/components-envs/oracle-erc20-native.env b/e2e-commons/components-envs/oracle-erc20-native.env index e12f4f5d8..134746878 100644 --- a/e2e-commons/components-envs/oracle-erc20-native.env +++ b/e2e-commons/components-envs/oracle-erc20-native.env @@ -22,3 +22,9 @@ ORACLE_ALLOW_HTTP_FOR_RPC=yes ORACLE_HOME_START_BLOCK=1 ORACLE_FOREIGN_START_BLOCK=1 ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=/mono/oracle/access-lists/block_list.txt +ORACLE_HOME_EVENTS_REPROCESSING=true +ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE=10 +ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY=10 +ORACLE_FOREIGN_EVENTS_REPROCESSING=true +ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE=10 +ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY=10 diff --git a/oracle/config/base.config.js b/oracle/config/base.config.js index 6dc94902e..35e527064 100644 --- a/oracle/config/base.config.js +++ b/oracle/config/base.config.js @@ -23,7 +23,13 @@ const { ORACLE_HOME_START_BLOCK, ORACLE_FOREIGN_START_BLOCK, ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, - ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT + ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT, + ORACLE_HOME_EVENTS_REPROCESSING, + ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE, + ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY, + ORACLE_FOREIGN_EVENTS_REPROCESSING, + ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE, + ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY } = process.env let homeAbi @@ -61,7 +67,12 @@ const homeConfig = { blockPollingLimit: parseInt(ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, 10), web3: web3Home, bridgeContract: homeContract, - eventContract: homeContract + eventContract: homeContract, + reprocessingOptions: { + enabled: ORACLE_HOME_EVENTS_REPROCESSING === 'true', + batchSize: parseInt(ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE, 10) || 1000, + blockDelay: parseInt(ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY, 10) || 500 + } } const foreignContract = new web3Foreign.eth.Contract(foreignAbi, COMMON_FOREIGN_BRIDGE_ADDRESS) @@ -74,7 +85,12 @@ const foreignConfig = { blockPollingLimit: parseInt(ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT, 10), web3: web3Foreign, bridgeContract: foreignContract, - eventContract: foreignContract + eventContract: foreignContract, + reprocessingOptions: { + enabled: ORACLE_FOREIGN_EVENTS_REPROCESSING === 'true', + batchSize: parseInt(ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE, 10) || 500, + blockDelay: parseInt(ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY, 10) || 250 + } } const maxProcessingTime = diff --git a/oracle/config/foreign-sender.config.js b/oracle/config/foreign-sender.config.js index 990ac78df..7971c7459 100644 --- a/oracle/config/foreign-sender.config.js +++ b/oracle/config/foreign-sender.config.js @@ -8,7 +8,6 @@ const { ORACLE_FOREIGN_TX_RESEND_INTERVAL } = process.env module.exports = { ...baseConfig, queue: 'foreign-prioritized', - oldQueue: 'foreign', id: 'foreign', name: 'sender-foreign', web3: web3Foreign, diff --git a/oracle/config/home-sender.config.js b/oracle/config/home-sender.config.js index 0220efd1a..70ac51a85 100644 --- a/oracle/config/home-sender.config.js +++ b/oracle/config/home-sender.config.js @@ -8,7 +8,6 @@ const { ORACLE_HOME_TX_RESEND_INTERVAL } = process.env module.exports = { ...baseConfig, queue: 'home-prioritized', - oldQueue: 'home', id: 'home', name: 'sender-home', web3: web3Home, diff --git a/oracle/src/sender.js b/oracle/src/sender.js index 7484c9d21..8eacfb615 100644 --- a/oracle/src/sender.js +++ b/oracle/src/sender.js @@ -55,7 +55,6 @@ async function initialize() { function connectQueue() { connectSenderToQueue({ queueName: config.queue, - oldQueueName: config.oldQueue, resendInterval: config.resendInterval, cb: options => { if (config.maxProcessingTime) { diff --git a/oracle/src/services/amqpClient.js b/oracle/src/services/amqpClient.js index b3cc147f2..b2a4a1770 100644 --- a/oracle/src/services/amqpClient.js +++ b/oracle/src/services/amqpClient.js @@ -40,23 +40,9 @@ function connectWatcherToQueue({ queueName, cb }) { cb({ sendToQueue, channel: channelWrapper }) } -function connectSenderToQueue({ queueName, oldQueueName, cb, resendInterval }) { +function connectSenderToQueue({ queueName, cb, resendInterval }) { const deadLetterExchange = `${queueName}-retry` - async function resendMessagesToNewQueue(channel) { - logger.info(`Trying to check messages in the old non-priority queue ${queueName}`) - while (true) { - const msg = await channel.get(oldQueueName) - if (msg === false) { - logger.info(`No messages in the old queue ${oldQueueName} left`) - break - } - logger.debug(`Message in the old queue ${oldQueueName} was found, redirecting it to the new queue ${queueName}`) - await channel.sendToQueue(queueName, msg.content, { persistent: true, priority: SENDER_QUEUE_SEND_PRIORITY }) - await channel.ack(msg) - } - } - const channelWrapper = connection.createChannel({ json: true }) @@ -64,7 +50,6 @@ function connectSenderToQueue({ queueName, oldQueueName, cb, resendInterval }) { channelWrapper.addSetup(async channel => { await channel.assertExchange(deadLetterExchange, 'fanout', { durable: true }) await channel.assertQueue(queueName, { durable: true, maxPriority: SENDER_QUEUE_MAX_PRIORITY }) - await channel.assertQueue(oldQueueName, { durable: true }).then(() => resendMessagesToNewQueue(channel)) await channel.bindQueue(queueName, deadLetterExchange) await channel.prefetch(1) await channel.consume(queueName, msg => diff --git a/oracle/src/tx/web3.js b/oracle/src/tx/web3.js index 33f98d4bb..8ea58fbf7 100644 --- a/oracle/src/tx/web3.js +++ b/oracle/src/tx/web3.js @@ -87,7 +87,7 @@ async function getEvents({ contract, event, fromBlock, toBlock, filter }) { ) const pastEvents = await contract.getPastEvents(event, { fromBlock, toBlock, filter }) logger.debug({ contractAddress, event, count: pastEvents.length }, 'Past events obtained') - return pastEvents + return pastEvents.sort((a, b) => a.blockNumber - b.blockNumber || a.transactionIndex - b.transactionIndex) } catch (e) { logger.error(e.message) throw new Error(`${event} events cannot be obtained`) diff --git a/oracle/src/utils/constants.js b/oracle/src/utils/constants.js index bed0d136b..40ec0cb81 100644 --- a/oracle/src/utils/constants.js +++ b/oracle/src/utils/constants.js @@ -5,6 +5,7 @@ module.exports = { MIN_AMB_HEADER_LENGTH: 32 + 20 + 20 + 4 + 2 + 1 + 2, MAX_GAS_LIMIT: 10000000, MAX_CONCURRENT_EVENTS: 50, + MAX_HISTORY_BLOCK_TO_REPROCESS: 10000, RETRY_CONFIG: { retries: 20, factor: 1.4, diff --git a/oracle/src/watcher.js b/oracle/src/watcher.js index e06270424..22bb91ada 100644 --- a/oracle/src/watcher.js +++ b/oracle/src/watcher.js @@ -6,7 +6,11 @@ const logger = require('./services/logger') const { getShutdownFlag } = require('./services/shutdownState') const { getBlockNumber, getRequiredBlockConfirmations, getEvents } = require('./tx/web3') const { checkHTTPS, watchdog } = require('./utils/utils') -const { EXIT_CODES, BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT } = require('./utils/constants') +const { + EXIT_CODES, + BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT, + MAX_HISTORY_BLOCK_TO_REPROCESS +} = require('./utils/constants') if (process.argv.length < 3) { logger.error('Please check the number of arguments, config file was not provided') @@ -26,9 +30,12 @@ const processAMBInformationRequests = require('./events/processAMBInformationReq const { getTokensState } = require('./utils/tokenState') -const { web3, bridgeContract, eventContract, startBlock, pollingInterval, chain } = config.main +const { web3, bridgeContract, eventContract, startBlock, pollingInterval, chain, reprocessingOptions } = config.main const lastBlockRedisKey = `${config.id}:lastProcessedBlock` +const lastReprocessedBlockRedisKey = `${config.id}:lastReprocessedBlock` +const seenEventsRedisKey = `${config.id}:seenEvents` let lastProcessedBlock = Math.max(startBlock - 1, 0) +let lastReprocessedBlock let lastSeenBlockNumber = 0 let sameBlockNumberCounter = 0 @@ -39,6 +46,8 @@ async function initialize() { web3.currentProvider.urls.forEach(checkHttps(chain)) await getLastProcessedBlock() + await getLastReprocessedBlock() + await checkConditions() connectWatcherToQueue({ queueName: config.queue, cb: runMain @@ -76,11 +85,34 @@ async function getLastProcessedBlock() { lastProcessedBlock = result ? parseInt(result, 10) : lastProcessedBlock } +async function getLastReprocessedBlock() { + if (reprocessingOptions.enabled) { + const result = await redis.get(lastReprocessedBlockRedisKey) + if (result) { + lastReprocessedBlock = Math.max(parseInt(result, 10), lastProcessedBlock - MAX_HISTORY_BLOCK_TO_REPROCESS) + } else { + lastReprocessedBlock = lastProcessedBlock + } + logger.debug({ block: lastReprocessedBlock }, 'Last reprocessed block obtained') + } else { + // when reprocessing is being enabled not for the first time, + // we do not want to process blocks for which we didn't recorded seen events, + // instead, we want to start from the current block. + // Thus we should delete this reprocessing pointer once it is disabled. + await redis.del(lastReprocessedBlockRedisKey) + } +} + function updateLastProcessedBlock(lastBlockNumber) { lastProcessedBlock = lastBlockNumber return redis.set(lastBlockRedisKey, lastProcessedBlock) } +function updateLastReprocessedBlock(lastBlockNumber) { + lastReprocessedBlock = lastBlockNumber + return redis.set(lastReprocessedBlockRedisKey, lastReprocessedBlock) +} + function processEvents(events) { switch (config.id) { case 'erc-native-signature-request': @@ -114,6 +146,71 @@ async function checkConditions() { } } +const eventKey = e => `${e.transactionHash}-${e.logIndex}` + +async function reprocessOldLogs(sendToQueue) { + const fromBlock = lastReprocessedBlock + 1 + let toBlock = lastReprocessedBlock + reprocessingOptions.batchSize + const events = await getEvents({ + contract: eventContract, + event: config.event, + fromBlock, + toBlock, + filter: config.eventFilter + }) + const alreadySeenEvents = await getSeenEvents(fromBlock, toBlock) + const missingEvents = events.filter(e => !alreadySeenEvents[eventKey(e)]) + if (missingEvents.length === 0) { + logger.debug('No missed events were found') + } else { + logger.info(`Found ${missingEvents.length} ${config.event} missed events`) + let job + if (config.id === 'amb-information-request') { + // obtain block number and events from the earliest block + const batchBlockNumber = missingEvents[0].blockNumber + const batchEvents = missingEvents.filter(event => event.blockNumber === batchBlockNumber) + + // if there are some other events in the later blocks, + // adjust lastReprocessedBlock so that these events will be processed again on the next iteration + if (batchEvents.length < missingEvents.length) { + // pick event outside from the batch + toBlock = missingEvents[batchEvents.length].blockNumber - 1 + } + + job = await processAMBInformationRequests(batchEvents) + if (job === null) { + return + } + } else { + job = await processEvents(missingEvents) + } + logger.info('Missed events transactions to send:', job.length) + if (job.length) { + await sendToQueue(job) + } + } + + await updateLastReprocessedBlock(toBlock) + await deleteSeenEvents(0, toBlock) +} + +async function getSeenEvents(fromBlock, toBlock) { + const keys = await redis.zrangebyscore(seenEventsRedisKey, fromBlock, toBlock) + const res = {} + keys.forEach(k => { + res[k] = true + }) + return res +} + +function deleteSeenEvents(fromBlock, toBlock) { + return redis.zremrangebyscore(seenEventsRedisKey, fromBlock, toBlock) +} + +function addSeenEvents(events) { + return redis.zadd(seenEventsRedisKey, ...events.flatMap(e => [e.blockNumber, eventKey(e)])) +} + async function getLastBlockToProcess(web3, bridgeContract) { const [lastBlockNumber, requiredBlockConfirmations] = await Promise.all([ getBlockNumber(web3), @@ -158,24 +255,29 @@ async function main({ sendToQueue }) { const lastBlockToProcess = await getLastBlockToProcess(web3, bridgeContract) + if (reprocessingOptions.enabled) { + if (lastReprocessedBlock + reprocessingOptions.batchSize + reprocessingOptions.blockDelay < lastBlockToProcess) { + await reprocessOldLogs(sendToQueue) + return + } + } + if (lastBlockToProcess <= lastProcessedBlock) { logger.debug('All blocks already processed') return } - await checkConditions() - const fromBlock = lastProcessedBlock + 1 const rangeEndBlock = config.blockPollingLimit ? fromBlock + config.blockPollingLimit : lastBlockToProcess let toBlock = Math.min(lastBlockToProcess, rangeEndBlock) - const events = (await getEvents({ + let events = await getEvents({ contract: eventContract, event: config.event, fromBlock, toBlock, filter: config.eventFilter - })).sort((a, b) => a.blockNumber - b.blockNumber) + }) logger.info(`Found ${events.length} ${config.event} events`) if (events.length) { @@ -192,9 +294,10 @@ async function main({ sendToQueue }) { if (batchEvents.length < events.length) { // pick event outside from the batch toBlock = events[batchEvents.length].blockNumber - 1 + events = batchEvents } - job = await processAMBInformationRequests(batchEvents) + job = await processAMBInformationRequests(events) if (job === null) { return } @@ -206,6 +309,9 @@ async function main({ sendToQueue }) { if (job.length) { await sendToQueue(job) } + if (reprocessingOptions.enabled) { + await addSeenEvents(events) + } } logger.debug({ lastProcessedBlock: toBlock.toString() }, 'Updating last processed block') From 8d732adba1c203bf0b86b0b08e091a7fc7c090f8 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 3 Jan 2022 21:58:36 +0700 Subject: [PATCH 2/4] Add support for EIP1559 gas price oracle (#631) --- CONFIGURATION.md | 4 +- commons/package.json | 1 + commons/utils.js | 19 ++- oracle/package.json | 2 +- oracle/src/confirmRelay.js | 12 +- oracle/src/sender.js | 20 ++- oracle/src/services/gasPrice.js | 28 +-- oracle/src/tx/sendTx.js | 7 +- oracle/test/gasPrice.test.js | 18 +- package.json | 3 + yarn.lock | 294 ++++++++++++++++++++++++++++++++ 11 files changed, 359 insertions(+), 49 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index d953abba5..01c09c39f 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -8,11 +8,11 @@ COMMON_HOME_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in t COMMON_FOREIGN_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s) COMMON_HOME_BRIDGE_ADDRESS | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x" COMMON_FOREIGN_BRIDGE_ADDRESS | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x" -COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | URL +COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL COMMON_HOME_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_HOME_GAS_PRICE_SUPPLIER_URL` is not used. | `instant` / `fast` / `standard` / `slow` COMMON_HOME_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer COMMON_HOME_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer -COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. | URL +COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL`is not used. | `instant` / `fast` / `standard` / `slow` COMMON_FOREIGN_GAS_PRICE_FALLBACK | The gas price (in Wei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer diff --git a/commons/package.json b/commons/package.json index cb865a077..022635411 100644 --- a/commons/package.json +++ b/commons/package.json @@ -8,6 +8,7 @@ "test": "NODE_ENV=test mocha" }, "dependencies": { + "@mycrypto/gas-estimation": "^1.1.0", "gas-price-oracle": "^0.1.5", "web3-utils": "^1.3.0", "node-fetch": "^2.1.2" diff --git a/commons/utils.js b/commons/utils.js index 3463d3d66..8579be52a 100644 --- a/commons/utils.js +++ b/commons/utils.js @@ -1,5 +1,6 @@ const { toWei, toBN, BN } = require('web3-utils') const { GasPriceOracle } = require('gas-price-oracle') +const { estimateFees } = require('@mycrypto/gas-estimation') const fetch = require('node-fetch') const { BRIDGE_MODES } = require('./constants') const { REWARDABLE_VALIDATORS_ABI } = require('./abis') @@ -176,12 +177,20 @@ const gasPriceWithinLimits = (gasPrice, limits) => { const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => { let gasPrice = oracleGasPrice * factor gasPrice = gasPriceWithinLimits(gasPrice, limits) - return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei')) + return toWei(gasPrice.toFixed(2).toString(), 'gwei') } -const gasPriceFromSupplier = async (url, options = {}) => { +const gasPriceFromSupplier = async (web3, url, options = {}) => { try { let json + if (url === 'eip1559-gas-estimation') { + const { maxFeePerGas, maxPriorityFeePerGas } = await estimateFees(web3) + const res = { maxFeePerGas: maxFeePerGas.toString(10), maxPriorityFeePerGas: maxPriorityFeePerGas.toString(10) } + options.logger && + options.logger.debug && + options.logger.debug(res, 'Gas price updated using eip1559-gas-estimation') + return res + } if (url === 'gas-price-oracle') { json = await gasPriceOracle.fetchGasPricesOffChain() } else if (url) { @@ -205,7 +214,7 @@ const gasPriceFromSupplier = async (url, options = {}) => { options.logger.debug && options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API') - return normalizedGasPrice + return { gasPrice: normalizedGasPrice } } catch (e) { options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`) } @@ -214,11 +223,11 @@ const gasPriceFromSupplier = async (url, options = {}) => { const gasPriceFromContract = async (bridgeContract, options = {}) => { try { - const gasPrice = await bridgeContract.methods.gasPrice().call() + const gasPrice = (await bridgeContract.methods.gasPrice().call()).toString() options.logger && options.logger.debug && options.logger.debug({ gasPrice }, 'Gas price updated using the contracts') - return gasPrice + return { gasPrice } } catch (e) { options.logger && options.logger.error && diff --git a/oracle/package.json b/oracle/package.json index 50f17b74f..d1f05b652 100644 --- a/oracle/package.json +++ b/oracle/package.json @@ -38,7 +38,7 @@ "pino-pretty": "^2.0.1", "promise-limit": "^2.7.0", "promise-retry": "^1.1.1", - "web3": "^1.3.0" + "web3": "^1.6.0" }, "devDependencies": { "bn-chai": "^1.0.1", diff --git a/oracle/src/confirmRelay.js b/oracle/src/confirmRelay.js index af081e6a0..577b92ab5 100644 --- a/oracle/src/confirmRelay.js +++ b/oracle/src/confirmRelay.js @@ -153,11 +153,11 @@ async function main({ sendJob, txHashes }) { } async function sendJobTx(jobs) { - await GasPrice.start(chain, true) - const gasPrice = GasPrice.getPrice().toString(10) - const { web3 } = config.sender === 'foreign' ? config.foreign : config.home + await GasPrice.start(chain, web3, true) + const gasPriceOptions = GasPrice.gasPriceOptions() + const chainId = await getChainId(web3) let nonce = await getNonce(web3, config.validatorAddress) @@ -174,13 +174,13 @@ async function sendJobTx(jobs) { const txHash = await sendTx({ data: job.data, nonce, - gasPrice, amount: '0', gasLimit, privateKey: config.validatorPrivateKey, to: job.to, chainId, - web3 + web3, + gasPriceOptions }) nonce++ @@ -197,7 +197,7 @@ async function sendJobTx(jobs) { if (e.message.toLowerCase().includes('insufficient funds')) { const currentBalance = await web3.eth.getBalance(config.validatorAddress) - const minimumBalance = gasLimit.multipliedBy(gasPrice) + const minimumBalance = gasLimit.multipliedBy(gasPriceOptions.gasPrice || gasPriceOptions.maxFeePerGas) logger.error( `Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.` ) diff --git a/oracle/src/sender.js b/oracle/src/sender.js index 8eacfb615..95786411e 100644 --- a/oracle/src/sender.js +++ b/oracle/src/sender.js @@ -42,7 +42,7 @@ async function initialize() { web3.currentProvider.urls.forEach(checkHttps(config.id)) - GasPrice.start(config.id) + GasPrice.start(config.id, web3) chainId = await getChainId(web3) connectQueue() @@ -120,7 +120,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT const txArray = JSON.parse(msg.content) logger.debug(`Msg received with ${txArray.length} Tx to send`) - const gasPrice = GasPrice.getPrice().toString(10) + const gasPriceOptions = GasPrice.gasPriceOptions() let nonce let insufficientFunds = false @@ -158,24 +158,26 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT nonce = await readNonce(true) } - logger.info(`Transaction ${job.txHash} was not mined, updating gasPrice: ${job.gasPrice} -> ${gasPrice}`) + const oldGasPrice = JSON.stringify(job.gasPriceOptions) + const newGasPrice = JSON.stringify(gasPriceOptions) + logger.info(`Transaction ${job.txHash} was not mined, updating gasPrice: ${oldGasPrice} -> ${newGasPrice}`) } logger.info(`Sending transaction with nonce ${nonce}`) const txHash = await sendTx({ data: job.data, nonce, - gasPrice, amount: '0', gasLimit, privateKey: config.validatorPrivateKey, to: job.to, chainId, - web3: web3Redundant + web3: web3Redundant, + gasPriceOptions }) const resendJob = { - ...job, txHash, - gasPrice + gasPriceOptions, + ...job } resendJobs.push(resendJob) @@ -193,7 +195,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT if (isGasPriceError(e)) { logger.info('Replacement transaction underpriced, forcing gas price update') - GasPrice.start(config.id) + GasPrice.start(config.id, web3) failedTx.push(job) } else if (isResend || isSameTransactionError(e)) { resendJobs.push(job) @@ -207,7 +209,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT if (isInsufficientBalanceError(e)) { insufficientFunds = true const currentBalance = await web3.eth.getBalance(config.validatorAddress) - minimumBalance = gasLimit.multipliedBy(gasPrice) + minimumBalance = gasLimit.multipliedBy(gasPriceOptions.gasPrice || gasPriceOptions.maxFeePerGas) logger.error( `Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.` ) diff --git a/oracle/src/services/gasPrice.js b/oracle/src/services/gasPrice.js index 45c24f317..23f09c5ad 100644 --- a/oracle/src/services/gasPrice.js +++ b/oracle/src/services/gasPrice.js @@ -20,21 +20,21 @@ const { COMMON_HOME_GAS_PRICE_FACTOR } = process.env -let cachedGasPrice = null +let cachedGasPriceOptions = null let fetchGasPriceInterval = null -const fetchGasPrice = async (speedType, factor, bridgeContract, gasPriceSupplierUrl) => { +const fetchGasPrice = async (speedType, factor, web3, bridgeContract, gasPriceSupplierUrl) => { const contractOptions = { logger } const supplierOptions = { speedType, factor, limits: GAS_PRICE_BOUNDARIES, logger } - cachedGasPrice = - (await gasPriceFromSupplier(gasPriceSupplierUrl, supplierOptions)) || + cachedGasPriceOptions = + (await gasPriceFromSupplier(web3, gasPriceSupplierUrl, supplierOptions)) || (await gasPriceFromContract(bridgeContract, contractOptions)) || - cachedGasPrice - return cachedGasPrice + cachedGasPriceOptions + return cachedGasPriceOptions } -async function start(chainId, fetchOnce) { +async function start(chainId, web3, fetchOnce) { clearInterval(fetchGasPriceInterval) let contract = null @@ -49,7 +49,7 @@ async function start(chainId, fetchOnce) { updateInterval = ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL || DEFAULT_UPDATE_INTERVAL factor = Number(COMMON_HOME_GAS_PRICE_FACTOR) || DEFAULT_GAS_PRICE_FACTOR - cachedGasPrice = COMMON_HOME_GAS_PRICE_FALLBACK + cachedGasPriceOptions = { gasPrice: COMMON_HOME_GAS_PRICE_FALLBACK } } else if (chainId === 'foreign') { contract = foreign.bridgeContract gasPriceSupplierUrl = COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL @@ -57,7 +57,7 @@ async function start(chainId, fetchOnce) { updateInterval = ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL || DEFAULT_UPDATE_INTERVAL factor = Number(COMMON_FOREIGN_GAS_PRICE_FACTOR) || DEFAULT_GAS_PRICE_FACTOR - cachedGasPrice = COMMON_FOREIGN_GAS_PRICE_FALLBACK + cachedGasPriceOptions = { gasPrice: COMMON_FOREIGN_GAS_PRICE_FALLBACK } } else { throw new Error(`Unrecognized chainId '${chainId}'`) } @@ -67,21 +67,21 @@ async function start(chainId, fetchOnce) { } if (fetchOnce) { - await fetchGasPrice(speedType, factor, contract, gasPriceSupplierUrl) + await fetchGasPrice(speedType, factor, web3, contract, gasPriceSupplierUrl) } else { fetchGasPriceInterval = await setIntervalAndRun( - () => fetchGasPrice(speedType, factor, contract, gasPriceSupplierUrl), + () => fetchGasPrice(speedType, factor, web3, contract, gasPriceSupplierUrl), updateInterval ) } } -function getPrice() { - return cachedGasPrice +function gasPriceOptions() { + return cachedGasPriceOptions } module.exports = { start, - getPrice, + gasPriceOptions, fetchGasPrice } diff --git a/oracle/src/tx/sendTx.js b/oracle/src/tx/sendTx.js index e9782212a..ab660ad43 100644 --- a/oracle/src/tx/sendTx.js +++ b/oracle/src/tx/sendTx.js @@ -1,6 +1,7 @@ const { toWei } = require('web3').utils -async function sendTx({ privateKey, data, nonce, gasPrice, amount, gasLimit, to, chainId, web3 }) { +async function sendTx({ privateKey, data, nonce, gasPrice, gasPriceOptions, amount, gasLimit, to, chainId, web3 }) { + const gasOpts = gasPriceOptions || { gasPrice } const serializedTx = await web3.eth.accounts.signTransaction( { nonce: Number(nonce), @@ -8,8 +9,8 @@ async function sendTx({ privateKey, data, nonce, gasPrice, amount, gasLimit, to, to, data, value: toWei(amount), - gasPrice, - gas: gasLimit + gas: gasLimit, + ...gasOpts }, privateKey ) diff --git a/oracle/test/gasPrice.test.js b/oracle/test/gasPrice.test.js index 649d57318..e0e0c472f 100644 --- a/oracle/test/gasPrice.test.js +++ b/oracle/test/gasPrice.test.js @@ -71,10 +71,10 @@ describe('gasPrice', () => { await gasPrice.start('home') // when - await gasPrice.fetchGasPrice('standard', 1, null, null) + await gasPrice.fetchGasPrice('standard', 1, null, null, null) // then - expect(gasPrice.getPrice()).to.equal('101000000000') + expect(gasPrice.gasPriceOptions()).to.eql({ gasPrice: '101000000000' }) }) it('should fetch gas from supplier', async () => { @@ -82,10 +82,10 @@ describe('gasPrice', () => { await gasPrice.start('home') // when - await gasPrice.fetchGasPrice('standard', 1, null, 'url') + await gasPrice.fetchGasPrice('standard', 1, null, null, 'url') // then - expect(gasPrice.getPrice().toString()).to.equal('103000000000') + expect(gasPrice.gasPriceOptions()).to.eql({ gasPrice: '103000000000' }) }) it('should fetch gas from contract', async () => { @@ -101,10 +101,10 @@ describe('gasPrice', () => { } // when - await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, null) + await gasPrice.fetchGasPrice('standard', 1, null, bridgeContractMock, null) // then - expect(gasPrice.getPrice().toString()).to.equal('102000000000') + expect(gasPrice.gasPriceOptions()).to.eql({ gasPrice: '102000000000' }) }) it('should fetch the gas price from the oracle first', async () => { @@ -120,10 +120,10 @@ describe('gasPrice', () => { } // when - await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, 'url') + await gasPrice.fetchGasPrice('standard', 1, null, bridgeContractMock, 'url') // then - expect(gasPrice.getPrice().toString()).to.equal('103000000000') + expect(gasPrice.gasPriceOptions()).to.eql({ gasPrice: '103000000000' }) }) it('log error using the logger', async () => { @@ -131,7 +131,7 @@ describe('gasPrice', () => { await gasPrice.start('home') // when - await gasPrice.fetchGasPrice('standard', 1, null, null) + await gasPrice.fetchGasPrice('standard', 1, null, null, null) // then expect(fakeLogger.warn.calledOnce).to.equal(true) // one warning diff --git a/package.json b/package.json index de5821ea3..262972de7 100644 --- a/package.json +++ b/package.json @@ -45,5 +45,8 @@ "compile:contracts": "yarn workspace tokenbridge-contracts run compile", "install:deploy": "cd contracts/deploy && npm install --unsafe-perm --silent", "postinstall": "test -n \"$NOYARNPOSTINSTALL\" || ln -sf $(pwd)/node_modules/openzeppelin-solidity/ contracts/node_modules/openzeppelin-solidity" + }, + "resolutions": { + "**/@mycrypto/eth-scan": "3.5.3" } } diff --git a/yarn.lock b/yarn.lock index 179e3a119..f55ac82ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1268,6 +1268,14 @@ crc-32 "^1.2.0" ethereumjs-util "^7.1.0" +"@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.0.tgz#feb96fb154da41ee2cc2c5df667621a440f36348" + integrity sha512-Cq2qS0FTu6O2VU1sgg+WyU9Ps0M6j/BEMHN+hRaECXCV/r0aI78u4N6p52QW/BDVhwWZpCdrvG8X7NJdzlpNUA== + dependencies: + crc-32 "^1.2.0" + ethereumjs-util "^7.1.3" + "@ethereumjs/tx@^3.2.1": version "3.3.0" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.0.tgz#14ed1b7fa0f28e1cd61e3ecbdab824205f6a4378" @@ -1276,6 +1284,14 @@ "@ethereumjs/common" "^2.4.0" ethereumjs-util "^7.1.0" +"@ethereumjs/tx@^3.3.2": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.4.0.tgz#7eb1947eefa55eb9cf05b3ca116fb7a3dbd0bce7" + integrity sha512-WWUwg1PdjHKZZxPPo274ZuPsJCWV3SqATrEKQP1n2DrVYVP1aZIYpo/mFaA0BDoE0tIQmBeimRCEA0Lgil+yYw== + dependencies: + "@ethereumjs/common" "^2.6.0" + ethereumjs-util "^7.1.3" + "@ethersproject/abi@5.0.0-beta.142": version "5.0.0-beta.142" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.0-beta.142.tgz#cde0ced7daa2fbc98e35a2c31203331907e84a39" @@ -1555,6 +1571,11 @@ unique-filename "^1.1.1" which "^1.3.1" +"@findeth/abi@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@findeth/abi/-/abi-0.7.1.tgz#60d0801cb252e587dc3228f00c00581bb748aebc" + integrity sha512-9uNu+/UxeuIibxIB7slf7BGG2PWjgBZr+rKzohhLb7VuoZjmlCcKZkenqwErROxkPdsap7OGO/o1DuYMvObMvw== + "@graphql-tools/batch-delegate@^6.2.4", "@graphql-tools/batch-delegate@^6.2.6": version "6.2.6" resolved "https://registry.yarnpkg.com/@graphql-tools/batch-delegate/-/batch-delegate-6.2.6.tgz#fbea98dc825f87ef29ea5f3f371912c2a2aa2f2c" @@ -2812,6 +2833,22 @@ resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw== +"@mycrypto/eth-scan@3.5.2", "@mycrypto/eth-scan@3.5.3": + version "3.5.3" + resolved "https://registry.yarnpkg.com/@mycrypto/eth-scan/-/eth-scan-3.5.3.tgz#c2c9cff253b4d2821f77ab0ba73b8f5d7dab1e4c" + integrity sha512-CbMHc+RUCANQMGKt2T65UDZY5kKOHx8lSvupq5fB4UzglcINeyeEekd6TsbW0/1G+7lUXe6fELQmtGSqEPQSvg== + dependencies: + "@findeth/abi" "^0.7.1" + isomorphic-unfetch "^3.1.0" + nanoid "^3.1.28" + +"@mycrypto/gas-estimation@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mycrypto/gas-estimation/-/gas-estimation-1.1.0.tgz#3f7af47aa487a6eb5d324db5ee11a66845c20c1e" + integrity sha512-/riP5G6DYp3uQBhC9vpWqbK+g6Uebs7AYjaivRF6V581h+PoObh0LqIZuKSzWZ5M+zPCxrrZHO43yR/jtpdq1w== + dependencies: + "@mycrypto/eth-scan" "3.5.2" + "@nodefactory/filsnap-adapter@^0.2.1": version "0.2.2" resolved "https://registry.yarnpkg.com/@nodefactory/filsnap-adapter/-/filsnap-adapter-0.2.2.tgz#0e182150ce3825b6c26b8512ab9355ab7759b498" @@ -10720,6 +10757,17 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0: ethjs-util "0.1.6" rlp "^2.2.4" +ethereumjs-util@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.3.tgz#b55d7b64dde3e3e45749e4c41288238edec32d23" + integrity sha512-y+82tEbyASO0K0X1/SRhbJJoAlfcvq8JbrG4a5cjrOks7HS/36efU/0j2flxCPOUM++HFahk33kr/ZxyC4vNuw== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6" @@ -14006,6 +14054,14 @@ isobject@^4.0.0: resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== +isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + isomorphic-ws@4.0.1, isomorphic-ws@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" @@ -16663,6 +16719,11 @@ nanoid@^3.1.12, nanoid@^3.1.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== +nanoid@^3.1.28: + version "3.1.30" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" + integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -23073,6 +23134,11 @@ underscore@~1.4.4: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ= +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -23712,6 +23778,15 @@ web3-bzz@1.5.2: got "9.6.0" swarm-js "^0.1.40" +web3-bzz@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.6.1.tgz#8430eb3cbb69baaee4981d190b840748c37a9ec2" + integrity sha512-JbnFNbRlwwHJZPtVuCxo7rC4U4OTg+mPsyhjgPQJJhS0a6Y54OgVWYk9UA/95HqbmTJwTtX329gJoSsseEfrng== + dependencies: + "@types/node" "^12.12.6" + got "9.6.0" + swarm-js "^0.1.40" + web3-core-helpers@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.1.tgz#f5f32d71c60a4a3bd14786118e633ce7ca6d5d0d" @@ -23756,6 +23831,14 @@ web3-core-helpers@1.5.2: web3-eth-iban "1.5.2" web3-utils "1.5.2" +web3-core-helpers@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.6.1.tgz#cb21047306871f4cf0fedfece7d47ea2aa96141b" + integrity sha512-om2PZvK1uoWcgMq6JfcSx3241LEIVF6qi2JuHz2SLKiKEW5UsBUaVx0mNCmcZaiuYQCyOsLS3r33q5AdM+v8ng== + dependencies: + web3-eth-iban "1.6.1" + web3-utils "1.6.1" + web3-core-method@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.1.tgz#9df1bafa2cd8be9d9937e01c6a47fc768d15d90a" @@ -23814,6 +23897,17 @@ web3-core-method@1.5.2: web3-core-subscriptions "1.5.2" web3-utils "1.5.2" +web3-core-method@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.6.1.tgz#4ae91c639bf1da85ebfd8b99595da6a2235d7b98" + integrity sha512-szH5KyIWIaULQDBdDvevQUCHV9lsExJ/oV0ePqK+w015D2SdMPMuhii0WB+HCePaksWO+rr/GAypvV9g2T3N+w== + dependencies: + "@ethersproject/transactions" "^5.0.0-beta.135" + web3-core-helpers "1.6.1" + web3-core-promievent "1.6.1" + web3-core-subscriptions "1.6.1" + web3-utils "1.6.1" + web3-core-promievent@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.1.tgz#003e8a3eb82fb27b6164a6d5b9cad04acf733838" @@ -23851,6 +23945,13 @@ web3-core-promievent@1.5.2: dependencies: eventemitter3 "4.0.4" +web3-core-promievent@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.6.1.tgz#f650dea9361e2edf02691015b213fcc8ea499992" + integrity sha512-byJ5s2MQxrWdXd27pWFmujfzsTZK4ik8rDgIV1RFDFc+rHZ2nZhq+VWk7t/Nkrj7EaVXncEgTdPEHc18nx+ocQ== + dependencies: + eventemitter3 "4.0.4" + web3-core-requestmanager@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.1.tgz#fa2e2206c3d738db38db7c8fe9c107006f5c6e3d" @@ -23907,6 +24008,17 @@ web3-core-requestmanager@1.5.2: web3-providers-ipc "1.5.2" web3-providers-ws "1.5.2" +web3-core-requestmanager@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.6.1.tgz#d9c08b0716c9cda546a0c02767b7e08deb04448a" + integrity sha512-4y7etYEUtkfflyYVBfN1oJtCbVFNhNX1omlEYzezhTnPj3/dT7n+dhUXcqvIhx9iKA13unGfpFge80XNFfcB8A== + dependencies: + util "^0.12.0" + web3-core-helpers "1.6.1" + web3-providers-http "1.6.1" + web3-providers-ipc "1.6.1" + web3-providers-ws "1.6.1" + web3-core-subscriptions@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.1.tgz#8c2368a839d4eec1c01a4b5650bbeb82d0e4a099" @@ -23951,6 +24063,14 @@ web3-core-subscriptions@1.5.2: eventemitter3 "4.0.4" web3-core-helpers "1.5.2" +web3-core-subscriptions@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.6.1.tgz#4dfc1f74137354d4ac9eaa628aa916c5e2cc8741" + integrity sha512-WZwxsYttIojyGQ5RqxuQcKg0IJdDCFpUe4EncS3QKZwxPqWzGmgyLwE0rm7tP+Ux1waJn5CUaaoSCBxWGSun1g== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.6.1" + web3-core@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.1.tgz#7278b58fb6495065e73a77efbbce781a7fddf1a9" @@ -24012,6 +24132,19 @@ web3-core@1.5.2: web3-core-requestmanager "1.5.2" web3-utils "1.5.2" +web3-core@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.6.1.tgz#b41f08fdc9ea1082d15384a3d6fa93a47c3fc1b4" + integrity sha512-m+b7UfYvU5cQUAh6NRfxRzH/5B3to1AdEQi1HIQt570cDWlObOOmoO9tY6iJnI5w4acxIO19LqjDMqEJGBYyRQ== + dependencies: + "@types/bn.js" "^4.11.5" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.6.1" + web3-core-method "1.6.1" + web3-core-requestmanager "1.6.1" + web3-utils "1.6.1" + web3-eth-abi@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.1.tgz#9b915b1c9ebf82f70cca631147035d5419064689" @@ -24056,6 +24189,14 @@ web3-eth-abi@1.5.2: "@ethersproject/abi" "5.0.7" web3-utils "1.5.2" +web3-eth-abi@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.6.1.tgz#15b937e3188570754d50bbac51a4bb0578600d1d" + integrity sha512-svhYrAlXP9XQtV7poWKydwDJq2CaNLMtmKydNXoOBLcQec6yGMP+v20pgrxF2H6wyTK+Qy0E3/5ciPOqC/VuoQ== + dependencies: + "@ethersproject/abi" "5.0.7" + web3-utils "1.6.1" + web3-eth-accounts@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.1.tgz#2741a8ef337a7219d57959ac8bd118b9d68d63cf" @@ -24142,6 +24283,23 @@ web3-eth-accounts@1.5.2: web3-core-method "1.5.2" web3-utils "1.5.2" +web3-eth-accounts@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.6.1.tgz#aeb0dfb52c4391773550569732975b471212583f" + integrity sha512-rGn3jwnuOKwaQRu4SiShz0YAQ87aVDBKs4HO43+XTCI1q1Y1jn3NOsG3BW9ZHaOckev4+zEyxze/Bsh2oEk24w== + dependencies: + "@ethereumjs/common" "^2.5.0" + "@ethereumjs/tx" "^3.3.2" + crypto-browserify "3.12.0" + eth-lib "0.2.8" + ethereumjs-util "^7.0.10" + scrypt-js "^3.0.1" + uuid "3.3.2" + web3-core "1.6.1" + web3-core-helpers "1.6.1" + web3-core-method "1.6.1" + web3-utils "1.6.1" + web3-eth-contract@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.1.tgz#3542424f3d341386fd9ff65e78060b85ac0ea8c4" @@ -24215,6 +24373,20 @@ web3-eth-contract@1.5.2: web3-eth-abi "1.5.2" web3-utils "1.5.2" +web3-eth-contract@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.6.1.tgz#4b0a2c0b37015d70146e54c7cb3f035a58fbeec0" + integrity sha512-GXqTe3mF6kpbOAakiNc7wtJ120/gpuKMTZjuGFKeeY8aobRLfbfgKzM9IpyqVZV2v5RLuGXDuurVN2KPgtu3hQ== + dependencies: + "@types/bn.js" "^4.11.5" + web3-core "1.6.1" + web3-core-helpers "1.6.1" + web3-core-method "1.6.1" + web3-core-promievent "1.6.1" + web3-core-subscriptions "1.6.1" + web3-eth-abi "1.6.1" + web3-utils "1.6.1" + web3-eth-ens@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.1.tgz#a0e52eee68c42a8b9865ceb04e5fb022c2d971d5" @@ -24287,6 +24459,20 @@ web3-eth-ens@1.5.2: web3-eth-contract "1.5.2" web3-utils "1.5.2" +web3-eth-ens@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.6.1.tgz#801bd5fb5237377ec2ed8517a9fe4634f2269c7a" + integrity sha512-ngprtbnoRgxg8s1wXt9nXpD3h1P+p7XnKXrp/8GdFI9uDmrbSQPRfzBw86jdZgOmy78hAnWmrHI6pBInmgi2qQ== + dependencies: + content-hash "^2.5.2" + eth-ens-namehash "2.0.8" + web3-core "1.6.1" + web3-core-helpers "1.6.1" + web3-core-promievent "1.6.1" + web3-eth-abi "1.6.1" + web3-eth-contract "1.6.1" + web3-utils "1.6.1" + web3-eth-iban@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.1.tgz#2c3801718946bea24e9296993a975c80b5acf880" @@ -24327,6 +24513,14 @@ web3-eth-iban@1.5.2: bn.js "^4.11.9" web3-utils "1.5.2" +web3-eth-iban@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.6.1.tgz#20bbed75723e3e9ff98e624979629d26329462b6" + integrity sha512-91H0jXZnWlOoXmc13O9NuQzcjThnWyAHyDn5Yf7u6mmKOhpJSGF/OHlkbpXt1Y4v2eJdEPaVFa+6i8aRyagE7Q== + dependencies: + bn.js "^4.11.9" + web3-utils "1.6.1" + web3-eth-personal@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.1.tgz#244e9911b7b482dc17c02f23a061a627c6e47faf" @@ -24386,6 +24580,18 @@ web3-eth-personal@1.5.2: web3-net "1.5.2" web3-utils "1.5.2" +web3-eth-personal@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.6.1.tgz#9b524fb9f92b51163f46920ee2663d34a4897c8d" + integrity sha512-ItsC89Ln02+irzJjK6ALcLrMZfbVUCqVbmb/ieDKJ+eLW3pNkBNwoUzaydh92d5NzxNZgNxuQWVdlFyYX2hkEw== + dependencies: + "@types/node" "^12.12.6" + web3-core "1.6.1" + web3-core-helpers "1.6.1" + web3-core-method "1.6.1" + web3-net "1.6.1" + web3-utils "1.6.1" + web3-eth@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.1.tgz#b9989e2557c73a9e8ffdc107c6dafbe72c79c1b0" @@ -24480,6 +24686,24 @@ web3-eth@1.5.2: web3-net "1.5.2" web3-utils "1.5.2" +web3-eth@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.6.1.tgz#a25aba1ac213d872ecf3f81c7b4ab8072ecae224" + integrity sha512-kOV1ZgCKypSo5BQyltRArS7ZC3bRpIKAxSgzl7pUFinUb/MxfbM9KGeNxUXoCfTSErcCQJaDjcS6bSre5EMKuQ== + dependencies: + web3-core "1.6.1" + web3-core-helpers "1.6.1" + web3-core-method "1.6.1" + web3-core-subscriptions "1.6.1" + web3-eth-abi "1.6.1" + web3-eth-accounts "1.6.1" + web3-eth-contract "1.6.1" + web3-eth-ens "1.6.1" + web3-eth-iban "1.6.1" + web3-eth-personal "1.6.1" + web3-net "1.6.1" + web3-utils "1.6.1" + web3-net@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.1.tgz#edd249503315dd5ab4fa00220f6509d95bb7ab10" @@ -24525,6 +24749,15 @@ web3-net@1.5.2: web3-core-method "1.5.2" web3-utils "1.5.2" +web3-net@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.6.1.tgz#7a630a804ec9f81908ae52ccbb4ebbb9530b3906" + integrity sha512-gpnqKEIwfUHh5ik7wsQFlCje1DfcmGv+Sk7LCh1hCqn++HEDQxJ/mZCrMo11ZZpZHCH7c87imdxTg96GJnRxDw== + dependencies: + web3-core "1.6.1" + web3-core-method "1.6.1" + web3-utils "1.6.1" + web3-provider-engine@14.1.0: version "14.1.0" resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.1.0.tgz#91590020f8b8c1b65846321310cbfdb039090fc6" @@ -24644,6 +24877,14 @@ web3-providers-http@1.5.2: web3-core-helpers "1.5.2" xhr2-cookies "1.1.0" +web3-providers-http@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.6.1.tgz#b59b14eefef23b98c327806f5f566303a73bd435" + integrity sha512-xBoKOJxu10+kO3ikamXmBfrWZ/xpQOGy0ocdp7Y81B17En5TXELwlmMXt1UlIgWiyYDhjq4OwlH/VODYqHXy3A== + dependencies: + web3-core-helpers "1.6.1" + xhr2-cookies "1.1.0" + web3-providers-ipc@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.1.tgz#017bfc687a8fc5398df2241eb98f135e3edd672c" @@ -24688,6 +24929,14 @@ web3-providers-ipc@1.5.2: oboe "2.1.5" web3-core-helpers "1.5.2" +web3-providers-ipc@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.6.1.tgz#7ba460589d46896bb3d124288deed1b6a72d517e" + integrity sha512-anyoIZlpMzwEQI4lwylTzDrHsVp20v0QUtSTp2B5jInBinmQtyCE7vnbX20jEQ4j5uPwfJabKNtoJsk6a3O4WQ== + dependencies: + oboe "2.1.5" + web3-core-helpers "1.6.1" + web3-providers-ws@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.1.tgz#2d941eaf3d5a8caa3214eff8dc16d96252b842cb" @@ -24735,6 +24984,15 @@ web3-providers-ws@1.5.2: web3-core-helpers "1.5.2" websocket "^1.0.32" +web3-providers-ws@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.6.1.tgz#f7ee71f158971102b865e99ea7911f483e0507e9" + integrity sha512-FWMEFYb4rYFYRgSFBf/O1Ex4p/YKSlN+JydCtdlJwRimd89qm95CTfs4xGjCskwvXMjV2sarH+f1NPwJXicYpg== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.6.1" + websocket "^1.0.32" + web3-shh@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.1.tgz#4460e3c1e07faf73ddec24ccd00da46f89152b0c" @@ -24775,6 +25033,16 @@ web3-shh@1.5.2: web3-core-subscriptions "1.5.2" web3-net "1.5.2" +web3-shh@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.6.1.tgz#eebaab2e5e6be80fe2585c6c094fa10a03349ca7" + integrity sha512-oP00HbAtybLCGlLOZUYXOdeB9xq88k2l0TtStvKBtmFqRt+zVk5TxEeuOnVPRxNhcA2Un8RUw6FtvgZlWStu9A== + dependencies: + web3-core "1.6.1" + web3-core-method "1.6.1" + web3-core-subscriptions "1.6.1" + web3-net "1.6.1" + web3-utils@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.1.tgz#21466e38291551de0ab34558de21512ac4274534" @@ -24843,6 +25111,19 @@ web3-utils@1.5.2, web3-utils@^1.2.6: randombytes "^2.1.0" utf8 "3.0.0" +web3-utils@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.6.1.tgz#befcb23922b00603ab56d8c5b4158468dc494aca" + integrity sha512-RidGKv5kOkcerI6jQqDFDoTllQQqV+rPhTzZHhmbqtFObbYpU93uc+yG1LHivRTQhA6llIx67iudc/vzisgO+w== + dependencies: + bn.js "^4.11.9" + ethereum-bloom-filters "^1.0.6" + ethereumjs-util "^7.1.0" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + web3@*, web3@1.3.5, web3@^1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/web3/-/web3-1.3.5.tgz#ef4c3a2241fdd74f2f7794e839f30bc6f9814e46" @@ -24895,6 +25176,19 @@ web3@1.5.2: web3-shh "1.5.2" web3-utils "1.5.2" +web3@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.6.1.tgz#c9e68fe7b3073adddf35393441f950ec69b92735" + integrity sha512-c299lLiyb2/WOcxh7TinwvbATaMmrgNIeAzbLbmOKHI0LcwyfsB1eu2ReOIrfrCYDYRW2KAjYr7J7gHawqDNPQ== + dependencies: + web3-bzz "1.6.1" + web3-core "1.6.1" + web3-eth "1.6.1" + web3-eth-personal "1.6.1" + web3-net "1.6.1" + web3-shh "1.6.1" + web3-utils "1.6.1" + webidl-conversions@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506" From 8ec11d0476937939be061df7f623daa3d3596ec8 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 17 Jan 2022 16:51:29 +0300 Subject: [PATCH 3/4] Fix eip1559 transaction sending problems (#632) --- oracle/src/sender.js | 15 ++++++----- oracle/src/utils/constants.js | 1 + oracle/src/utils/utils.js | 51 +++++++++++++++++++++++++++++++++-- oracle/test/utils.test.js | 47 +++++++++++++++++++++++++++++++- 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/oracle/src/sender.js b/oracle/src/sender.js index 95786411e..529e95d7d 100644 --- a/oracle/src/sender.js +++ b/oracle/src/sender.js @@ -9,6 +9,8 @@ const { sendTx } = require('./tx/sendTx') const { getNonce, getChainId } = require('./tx/web3') const { addExtraGas, + applyMinGasFeeBump, + chooseGasPriceOptions, checkHTTPS, syncForEach, waitForFunds, @@ -19,7 +21,7 @@ const { isInsufficientBalanceError, isNonceError } = require('./utils/utils') -const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants') +const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT, MIN_GAS_PRICE_BUMP_FACTOR } = require('./utils/constants') const { ORACLE_TX_REDUNDANCY } = process.env @@ -146,6 +148,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT } try { + const newGasPriceOptions = chooseGasPriceOptions(gasPriceOptions, job.gasPriceOptions) if (isResend) { const tx = await web3Fallback.eth.getTransaction(job.txHash) @@ -159,7 +162,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT } const oldGasPrice = JSON.stringify(job.gasPriceOptions) - const newGasPrice = JSON.stringify(gasPriceOptions) + const newGasPrice = JSON.stringify(newGasPriceOptions) logger.info(`Transaction ${job.txHash} was not mined, updating gasPrice: ${oldGasPrice} -> ${newGasPrice}`) } logger.info(`Sending transaction with nonce ${nonce}`) @@ -172,12 +175,12 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT to: job.to, chainId, web3: web3Redundant, - gasPriceOptions + gasPriceOptions: newGasPriceOptions }) const resendJob = { + ...job, txHash, - gasPriceOptions, - ...job + gasPriceOptions: newGasPriceOptions } resendJobs.push(resendJob) @@ -196,7 +199,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT if (isGasPriceError(e)) { logger.info('Replacement transaction underpriced, forcing gas price update') GasPrice.start(config.id, web3) - failedTx.push(job) + failedTx.push(applyMinGasFeeBump(job, MIN_GAS_PRICE_BUMP_FACTOR)) } else if (isResend || isSameTransactionError(e)) { resendJobs.push(job) } else { diff --git a/oracle/src/utils/constants.js b/oracle/src/utils/constants.js index 40ec0cb81..789e71fc4 100644 --- a/oracle/src/utils/constants.js +++ b/oracle/src/utils/constants.js @@ -24,6 +24,7 @@ module.exports = { MIN: 1, MAX: 1000 }, + MIN_GAS_PRICE_BUMP_FACTOR: 0.1, DEFAULT_TRANSACTION_RESEND_INTERVAL: 20 * 60 * 1000, FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000, BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT: 10, diff --git a/oracle/src/utils/utils.js b/oracle/src/utils/utils.js index d4e7a5485..a2a7901c3 100644 --- a/oracle/src/utils/utils.js +++ b/oracle/src/utils/utils.js @@ -2,6 +2,9 @@ const fs = require('fs') const BigNumber = require('bignumber.js') const promiseRetry = require('promise-retry') const Web3 = require('web3') +const { GAS_PRICE_BOUNDARIES } = require('./constants') + +const { toBN, toWei } = Web3.utils const retrySequence = [1, 2, 3, 5, 8, 13, 21, 34, 55, 60] @@ -34,8 +37,8 @@ const promiseRetryForever = f => promiseRetry(f, { forever: true, factor: 1 }) async function waitForFunds(web3, address, minimumBalance, cb, logger) { promiseRetryForever(async retry => { logger.debug('Getting balance of validator account') - const newBalance = web3.utils.toBN(await web3.eth.getBalance(address)) - if (newBalance.gte(web3.utils.toBN(minimumBalance.toString(10)))) { + const newBalance = toBN(await web3.eth.getBalance(address)) + if (newBalance.gte(toBN(minimumBalance.toString(10)))) { logger.debug({ balance: newBalance, minimumBalance }, 'Validator has minimum necessary balance') cb(newBalance) } else { @@ -64,6 +67,48 @@ function addExtraGas(gas, extraPercentage, maxGasLimit = Infinity) { return BigNumber.min(maxGasLimit, gasWithExtra) } +function applyMinGasFeeBump(job, bumpFactor = 0.1) { + if (!job.gasPriceOptions) { + return job + } + const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = job.gasPriceOptions + const maxGasPrice = toWei(GAS_PRICE_BOUNDARIES.MAX.toString(), 'gwei') + if (gasPrice) { + return { + ...job, + gasPriceOptions: { + gasPrice: addExtraGas(gasPrice, bumpFactor, maxGasPrice).toString() + } + } + } + if (maxFeePerGas && maxPriorityFeePerGas) { + return { + ...job, + gasPriceOptions: { + maxFeePerGas: addExtraGas(maxFeePerGas, bumpFactor, maxGasPrice).toString(), + maxPriorityFeePerGas: addExtraGas(maxPriorityFeePerGas, bumpFactor, maxGasPrice).toString() + } + } + } + return job +} + +function chooseGasPriceOptions(a, b) { + if (!a) { + return b + } + if (a && b && a.gasPrice && b.gasPrice) { + return { gasPrice: BigNumber.max(a.gasPrice, b.gasPrice).toString() } + } + if (a && b && a.maxFeePerGas && b.maxFeePerGas && a.maxPriorityFeePerGas && b.maxPriorityFeePerGas) { + return { + maxFeePerGas: BigNumber.max(a.maxFeePerGas, b.maxFeePerGas).toString(), + maxPriorityFeePerGas: BigNumber.max(a.maxPriorityFeePerGas, b.maxPriorityFeePerGas).toString() + } + } + return a +} + async function setIntervalAndRun(f, interval) { const handler = setInterval(f, interval) await f() @@ -183,6 +228,8 @@ module.exports = { waitForFunds, waitForUnsuspend, addExtraGas, + chooseGasPriceOptions, + applyMinGasFeeBump, setIntervalAndRun, watchdog, add0xPrefix, diff --git a/oracle/test/utils.test.js b/oracle/test/utils.test.js index 231ab3e0f..2914eb0ec 100644 --- a/oracle/test/utils.test.js +++ b/oracle/test/utils.test.js @@ -3,7 +3,13 @@ const chai = require('chai') const chaiAsPromised = require('chai-as-promised') const BigNumber = require('bignumber.js') const proxyquire = require('proxyquire') -const { addExtraGas, syncForEach, promiseAny } = require('../src/utils/utils') +const { + addExtraGas, + applyMinGasFeeBump, + chooseGasPriceOptions, + syncForEach, + promiseAny +} = require('../src/utils/utils') chai.use(chaiAsPromised) chai.should() @@ -173,4 +179,43 @@ describe('utils', () => { await promiseAny(array.map(f)).should.be.rejected }) }) + + describe('applyMinGasFeeBump', () => { + it('should bump pre-eip1559 fee', () => { + const job = { gasPriceOptions: { gasPrice: '100000000000' } } + const newJob = applyMinGasFeeBump(job) + expect(newJob.gasPriceOptions.gasPrice).to.be.equal('110000000000') + }) + + it('should bump eip1559 fee', () => { + const job = { gasPriceOptions: { maxFeePerGas: '100000000000', maxPriorityFeePerGas: '20000000000' } } + const newJob = applyMinGasFeeBump(job) + expect(newJob.gasPriceOptions.maxFeePerGas).to.be.equal('110000000000') + expect(newJob.gasPriceOptions.maxPriorityFeePerGas).to.be.equal('22000000000') + }) + }) + + describe('chooseGasPriceOptions', () => { + it('should choose max pre-eip1559 fee', () => { + const opts1 = { gasPrice: '100000000000' } + const opts2 = { gasPrice: '101000000000' } + expect(chooseGasPriceOptions(opts1, opts2).gasPrice).to.be.equal('101000000000') + expect(chooseGasPriceOptions(opts2, opts1).gasPrice).to.be.equal('101000000000') + expect(chooseGasPriceOptions(opts2, undefined).gasPrice).to.be.equal('101000000000') + expect(chooseGasPriceOptions(undefined, opts2).gasPrice).to.be.equal('101000000000') + }) + + it('should choose max eip1559 fee', () => { + const opts1 = { maxFeePerGas: '100000000000', maxPriorityFeePerGas: '21000000000' } + const opts2 = { maxFeePerGas: '101000000000', maxPriorityFeePerGas: '20000000000' } + expect(chooseGasPriceOptions(opts1, opts2).maxFeePerGas).to.be.equal('101000000000') + expect(chooseGasPriceOptions(opts1, opts2).maxPriorityFeePerGas).to.be.equal('21000000000') + expect(chooseGasPriceOptions(opts2, opts1).maxFeePerGas).to.be.equal('101000000000') + expect(chooseGasPriceOptions(opts2, opts1).maxPriorityFeePerGas).to.be.equal('21000000000') + expect(chooseGasPriceOptions(opts2, undefined).maxFeePerGas).to.be.equal('101000000000') + expect(chooseGasPriceOptions(opts2, undefined).maxPriorityFeePerGas).to.be.equal('20000000000') + expect(chooseGasPriceOptions(undefined, opts2).maxFeePerGas).to.be.equal('101000000000') + expect(chooseGasPriceOptions(undefined, opts2).maxPriorityFeePerGas).to.be.equal('20000000000') + }) + }) }) From 72f0d30b526e3ee7798d8d21fc3211a7ea3c3dbe Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 8 Feb 2022 00:32:41 +0400 Subject: [PATCH 4/4] CollectedSignatures AMB watcher for MEV bundling (#634) --- oracle/config/foreign-mev-sender.config.js | 37 ++ ...mev-collected-signatures-watcher.config.js | 30 ++ oracle/docker-compose-helpers.yml | 46 ++ oracle/package.json | 4 + oracle/scripts/erc20_to_native/sendForeign.js | 2 +- oracle/scripts/erc20_to_native/sendHome.js | 2 +- oracle/scripts/interestFetcher.js | 2 +- oracle/src/confirmRelay.js | 2 +- .../estimateGas.js | 9 +- .../processAMBCollectedSignaturesMEV/index.js | 183 ++++++++ .../processAffirmationRequests/estimateGas.js | 4 +- .../processCollectedSignatures/estimateGas.js | 3 +- .../processSignatureRequests/estimateGas.js | 3 +- oracle/src/mevSender.js | 159 +++++++ oracle/src/mevWatcher.js | 251 ++++++++++ oracle/src/sender.js | 2 +- oracle/src/services/web3.js | 16 +- oracle/src/tx/sendTx.js | 30 +- oracle/src/utils/mev.js | 43 ++ oracle/src/watcher.js | 13 +- yarn.lock | 444 +++++++++++++++++- 21 files changed, 1243 insertions(+), 42 deletions(-) create mode 100644 oracle/config/foreign-mev-sender.config.js create mode 100644 oracle/config/mev-collected-signatures-watcher.config.js create mode 100644 oracle/src/events/processAMBCollectedSignaturesMEV/index.js create mode 100644 oracle/src/mevSender.js create mode 100644 oracle/src/mevWatcher.js create mode 100644 oracle/src/utils/mev.js diff --git a/oracle/config/foreign-mev-sender.config.js b/oracle/config/foreign-mev-sender.config.js new file mode 100644 index 000000000..530d012d9 --- /dev/null +++ b/oracle/config/foreign-mev-sender.config.js @@ -0,0 +1,37 @@ +const baseConfig = require('./base.config') + +const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants') +const { MEV_HELPER_ABI } = require('../src/utils/mev') +const { web3Foreign, getFlashbotsProvider } = require('../src/services/web3') + +const { + ORACLE_FOREIGN_TX_RESEND_INTERVAL, + ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS, + ORACLE_MEV_FOREIGN_MIN_GAS_PRICE, + ORACLE_MEV_FOREIGN_FLAT_MINER_FEE, + ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS, + ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS, + ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE +} = process.env + +const contract = new baseConfig.foreign.web3.eth.Contract(MEV_HELPER_ABI, ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS) + +module.exports = { + ...baseConfig, + pollingInterval: baseConfig.foreign.pollingInterval, + mevForeign: { + contractAddress: ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS, + contract, + minGasPrice: ORACLE_MEV_FOREIGN_MIN_GAS_PRICE, + flatMinerFee: ORACLE_MEV_FOREIGN_FLAT_MINER_FEE, + maxPriorityFeePerGas: ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS, + maxFeePerGas: ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS, + bundlesPerIteration: Math.max(parseInt(ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE, 10) || 5, 1), + getFlashbotsProvider + }, + mevJobsRedisKey: `${baseConfig.id}-collected-signatures-mev:mevJobs`, + id: 'mev-sender-foreign', + name: 'mev-sender-foreign', + web3: web3Foreign, + resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL +} diff --git a/oracle/config/mev-collected-signatures-watcher.config.js b/oracle/config/mev-collected-signatures-watcher.config.js new file mode 100644 index 000000000..1093ffb45 --- /dev/null +++ b/oracle/config/mev-collected-signatures-watcher.config.js @@ -0,0 +1,30 @@ +const baseConfig = require('./base.config') +const { MEV_HELPER_ABI } = require('../src/utils/mev') + +const { + ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS, + ORACLE_MEV_FOREIGN_MIN_GAS_PRICE, + ORACLE_MEV_FOREIGN_FLAT_MINER_FEE, + ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS, + ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS +} = process.env + +const id = `${baseConfig.id}-collected-signatures-mev` + +const contract = new baseConfig.foreign.web3.eth.Contract(MEV_HELPER_ABI, ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS) + +module.exports = { + ...baseConfig, + mevForeign: { + contractAddress: ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS, + contract, + minGasPrice: ORACLE_MEV_FOREIGN_MIN_GAS_PRICE, + flatMinerFee: ORACLE_MEV_FOREIGN_FLAT_MINER_FEE, + maxPriorityFeePerGas: ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS, + maxFeePerGas: ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS + }, + main: baseConfig.home, + event: 'CollectedSignatures', + name: `watcher-${id}`, + id +} diff --git a/oracle/docker-compose-helpers.yml b/oracle/docker-compose-helpers.yml index 716a8a13d..8ea5f1261 100644 --- a/oracle/docker-compose-helpers.yml +++ b/oracle/docker-compose-helpers.yml @@ -1,6 +1,14 @@ --- version: '2.4' services: + redis: + cpus: 0.1 + mem_limit: 500m + command: [ redis-server, --appendonly, 'yes' ] + hostname: redis + image: redis:4 + restart: unless-stopped + volumes: [ '~/bridge_data/helpers/redis:/data' ] interestFetcher: cpus: 0.1 mem_limit: 500m @@ -13,3 +21,41 @@ services: INTERVAL: 300000 restart: unless-stopped entrypoint: yarn helper:interestFether + mevWatcher: + cpus: 0.1 + mem_limit: 500m + image: poanetwork/tokenbridge-oracle:latest + env_file: ./.env + environment: + NODE_ENV: production + ORACLE_VALIDATOR_ADDRESS: ${ORACLE_VALIDATOR_ADDRESS} + ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS: 'TBD' + ORACLE_MEV_FOREIGN_MIN_GAS_PRICE: '50000000000' # 50 gwei + ORACLE_MEV_FOREIGN_FLAT_MINER_FEE: '1500000000000000' # 0.0015 eth = 300k gas * 5 gwei + ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS: '0' # 0 gwei + ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS: '1000000000000' # 1000 gwei + ORACLE_FOREIGN_RPC_POLLING_INTERVAL: '15000' # CollectedSignatures event polling interval + ORACLE_HOME_START_BLOCK: 'TBD' + ORACLE_HOME_SKIP_MANUAL_LANE: 'true' + restart: unless-stopped + entrypoint: yarn mev:watcher:collected-signatures + mevSender: + cpus: 0.1 + mem_limit: 500m + image: poanetwork/tokenbridge-oracle:latest + env_file: ./.env + environment: + NODE_ENV: production + ORACLE_VALIDATOR_ADDRESS: ${ORACLE_VALIDATOR_ADDRESS} + ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: ${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY} + ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS: 'TBD' + ORACLE_MEV_FOREIGN_MIN_GAS_PRICE: '50000000000' # 50 gwei + ORACLE_MEV_FOREIGN_FLAT_MINER_FEE: '1500000000000000' # 0.0015 eth = 300k gas * 5 gwei + ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS: '0' # 0 gwei + ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS: '1000000000000' # 1000 gwei + ORACLE_MEV_FOREIGN_FLASHBOTS_RPC_URL: 'https://relay-goerli.flashbots.net' + ORACLE_MEV_FOREIGN_FLASHBOTS_AUTH_SIGNING_KEY: 82db7175932f4e6c8e45283b78b54fd5f195149378ec90d95b8fd0ec8bdadf1d + ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE: '5' + ORACLE_FOREIGN_RPC_POLLING_INTERVAL: '70000' # time between sending different batches of MEV bundles (~= 5 blocks * 14 seconds) + restart: unless-stopped + entrypoint: yarn mev:sender:foreign diff --git a/oracle/package.json b/oracle/package.json index d1f05b652..4bc3cd7b1 100644 --- a/oracle/package.json +++ b/oracle/package.json @@ -19,6 +19,8 @@ "confirm:information-request": "./scripts/start-worker.sh confirmRelay information-request-watcher", "manager:shutdown": "./scripts/start-worker.sh shutdownManager shutdown-manager", "helper:interestFether": "node ./scripts/interestFetcher.js", + "mev:watcher:collected-signatures": "./scripts/start-worker.sh mevWatcher mev-collected-signatures-watcher", + "mev:sender:foreign": "./scripts/start-worker.sh mevSender foreign-mev-sender", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer, sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn sender:home' 'yarn sender:foreign'", "test": "NODE_ENV=test mocha", "test:watch": "NODE_ENV=test mocha --watch --reporter=min", @@ -28,10 +30,12 @@ "author": "", "license": "ISC", "dependencies": { + "@flashbots/ethers-provider-bundle": "^0.4.3", "amqp-connection-manager": "^2.0.0", "amqplib": "^0.5.2", "bignumber.js": "^7.2.1", "dotenv": "^5.0.1", + "ethers": "^5.5.3", "ioredis": "^3.2.2", "node-fetch": "^2.1.2", "pino": "^4.17.3", diff --git a/oracle/scripts/erc20_to_native/sendForeign.js b/oracle/scripts/erc20_to_native/sendForeign.js index d6922908c..3875b196d 100644 --- a/oracle/scripts/erc20_to_native/sendForeign.js +++ b/oracle/scripts/erc20_to_native/sendForeign.js @@ -36,7 +36,7 @@ async function main() { data, nonce, gasPrice: FOREIGN_TEST_TX_GAS_PRICE, - amount: '0', + value: '0', gasLimit, to: bridgeableTokenAddress, web3: web3Foreign, diff --git a/oracle/scripts/erc20_to_native/sendHome.js b/oracle/scripts/erc20_to_native/sendHome.js index 327c695f0..1741ca2e3 100644 --- a/oracle/scripts/erc20_to_native/sendHome.js +++ b/oracle/scripts/erc20_to_native/sendHome.js @@ -29,7 +29,7 @@ async function main() { data: '0x', nonce, gasPrice: HOME_TEST_TX_GAS_PRICE, - amount: HOME_MIN_AMOUNT_PER_TX, + value: web3Home.utils.toWei(HOME_MIN_AMOUNT_PER_TX), gasLimit: 100000, to: COMMON_HOME_BRIDGE_ADDRESS, web3: web3Home, diff --git a/oracle/scripts/interestFetcher.js b/oracle/scripts/interestFetcher.js index 1e30773f7..7f90e2dd4 100644 --- a/oracle/scripts/interestFetcher.js +++ b/oracle/scripts/interestFetcher.js @@ -54,7 +54,7 @@ async function main() { nonce, gasPrice, gasLimit: Math.round(gasLimit * 1.5), - amount: '0', + value: '0', chainId, web3: web3Home }) diff --git a/oracle/src/confirmRelay.js b/oracle/src/confirmRelay.js index 577b92ab5..48f4d8a00 100644 --- a/oracle/src/confirmRelay.js +++ b/oracle/src/confirmRelay.js @@ -174,7 +174,7 @@ async function sendJobTx(jobs) { const txHash = await sendTx({ data: job.data, nonce, - amount: '0', + value: '0', gasLimit, privateKey: config.validatorPrivateKey, to: job.to, diff --git a/oracle/src/events/processAMBCollectedSignatures/estimateGas.js b/oracle/src/events/processAMBCollectedSignatures/estimateGas.js index 897326763..65ac5c171 100644 --- a/oracle/src/events/processAMBCollectedSignatures/estimateGas.js +++ b/oracle/src/events/processAMBCollectedSignatures/estimateGas.js @@ -4,7 +4,6 @@ const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError const logger = require('../../services/logger').child({ module: 'processCollectedSignatures:estimateGas' }) -const { parseAMBHeader } = require('../../utils/message') const web3 = new Web3() const { toBN } = Web3.utils @@ -22,15 +21,9 @@ async function estimateGas({ address }) { try { - const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({ + return await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({ from: address }) - const msgGasLimit = parseAMBHeader(message).gasLimit - - // + estimateExtraGas(len) - // is not needed here, since estimateGas will already take into account gas - // needed for memory expansion, message processing, etc. - return gasEstimate + msgGasLimit } catch (e) { if (e instanceof HttpListProviderError) { throw e diff --git a/oracle/src/events/processAMBCollectedSignaturesMEV/index.js b/oracle/src/events/processAMBCollectedSignaturesMEV/index.js new file mode 100644 index 000000000..9415f6eb3 --- /dev/null +++ b/oracle/src/events/processAMBCollectedSignaturesMEV/index.js @@ -0,0 +1,183 @@ +require('dotenv').config() +const promiseLimit = require('promise-limit') +const { HttpListProviderError } = require('../../services/HttpListProvider') +const { getValidatorContract } = require('../../tx/web3') +const rootLogger = require('../../services/logger') +const { signatureToVRS, packSignatures } = require('../../utils/message') +const { readAccessListFile, isRevertError } = require('../../utils/utils') +const { parseAMBMessage } = require('../../../../commons') +const estimateGas = require('../processAMBCollectedSignatures/estimateGas') +const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors') +const { MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants') + +const limit = promiseLimit(MAX_CONCURRENT_EVENTS) + +const { ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST, ORACLE_HOME_TO_FOREIGN_BLOCK_LIST } = process.env +const ORACLE_HOME_SKIP_MANUAL_LANE = process.env.ORACLE_HOME_SKIP_MANUAL_LANE === 'true' + +function processCollectedSignaturesBuilder(config) { + const { home, foreign, mevForeign } = config + + let validatorContract = null + + return async function processCollectedSignatures(signatures) { + const txToSend = [] + + if (validatorContract === null) { + validatorContract = await getValidatorContract(foreign.bridgeContract, foreign.web3) + } + + rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`) + const callbacks = signatures + .map(colSignature => async () => { + const { messageHash, NumberOfCollectedSignatures } = colSignature.returnValues + + const logger = rootLogger.child({ + eventTransactionHash: colSignature.transactionHash + }) + + logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`) + const message = await home.bridgeContract.methods.message(messageHash).call() + const parsedMessage = parseAMBMessage(message) + + if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST || ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) { + const sender = parsedMessage.sender.toLowerCase() + const executor = parsedMessage.executor.toLowerCase() + + if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST) { + const allowanceList = await readAccessListFile(ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST, logger) + if (!allowanceList.includes(executor) && !allowanceList.includes(sender)) { + logger.info( + { sender, executor }, + 'Validator skips a message. Neither sender nor executor addresses are in the allowance list.' + ) + return + } + } else if (ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) { + const blockList = await readAccessListFile(ORACLE_HOME_TO_FOREIGN_BLOCK_LIST, logger) + if (blockList.includes(executor)) { + logger.info({ executor }, 'Validator skips a message. Executor address is in the block list.') + return + } + if (blockList.includes(sender)) { + logger.info({ sender }, 'Validator skips a message. Sender address is in the block list.') + return + } + } + } + + if (ORACLE_HOME_SKIP_MANUAL_LANE && parsedMessage.decodedDataType.manualLane) { + logger.info( + { dataType: parsedMessage.dataType }, + 'Validator skips a message. Message was forwarded to the manual lane by the extension' + ) + return + } + + logger.debug({ NumberOfCollectedSignatures }, 'Number of signatures to get') + + const requiredSignatures = [] + requiredSignatures.length = NumberOfCollectedSignatures + requiredSignatures.fill(0) + + const signaturesArray = [] + const [v, r, s] = [[], [], []] + logger.debug('Getting message signatures') + const signaturePromises = requiredSignatures.map(async (el, index) => { + logger.debug({ index }, 'Getting message signature') + const signature = await home.bridgeContract.methods.signature(messageHash, index).call() + const vrs = signatureToVRS(signature) + v.push(vrs.v) + r.push(vrs.r) + s.push(vrs.s) + signaturesArray.push(vrs) + }) + + await Promise.all(signaturePromises) + const signatures = packSignatures(signaturesArray) + logger.info(`Processing messageId: ${parsedMessage.messageId}`) + + let gasEstimate + try { + logger.debug('Estimate gas') + gasEstimate = await estimateGas({ + foreignBridge: foreign.bridgeContract, + validatorContract, + v, + r, + s, + signatures, + message, + numberOfCollectedSignatures: NumberOfCollectedSignatures, + messageId: parsedMessage.messageId, + address: config.validatorAddress + }) + logger.debug({ gasEstimate }, 'Gas estimated') + } catch (e) { + if (e instanceof HttpListProviderError) { + throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.') + } else if (e instanceof AlreadyProcessedError) { + logger.info(`Already processed CollectedSignatures ${colSignature.transactionHash}`) + return + } else if (e instanceof IncompatibleContractError || e instanceof InvalidValidatorError) { + logger.error(`The message couldn't be processed; skipping: ${e.message}`) + return + } else { + logger.error(e, 'Unknown error while processing transaction') + throw e + } + } + + const executeData = foreign.bridgeContract.methods.executeSignatures(message, signatures).encodeABI() + const profit = await estimateProfit( + mevForeign.contract, + mevForeign.minGasPrice, + executeData, + mevForeign.flatMinerFee + ) + if (profit === '0') { + logger.error('No MEV opportunity found when testing with min gas price, skipping job') + return + } + logger.info(`Estimated profit of ${profit} when simulating with ${mevForeign.minGasPrice} gas price`) + + txToSend.push({ + profit, + executeData, + data: mevForeign.contract.methods.execute(executeData).encodeABI(), + gasEstimate, + extraGas: EXTRA_GAS_ABSOLUTE, + maxFeePerGas: mevForeign.maxFeePerGas, + maxPriorityFeePerGas: mevForeign.maxPriorityFeePerGas, + transactionReference: colSignature.transactionHash, + to: mevForeign.contractAddress, + value: mevForeign.flatMinerFee + }) + }) + .map(promise => limit(promise)) + + await Promise.all(callbacks) + + return txToSend + } +} + +async function estimateProfit(contract, gasPrice, data, minerFee) { + return contract.methods + .estimateProfit(gasPrice, data) + .call({ value: minerFee }) + .then( + res => res.toString(), + e => { + if (isRevertError(e)) { + return '0' + } + throw e + } + ) +} + +module.exports = { + processCollectedSignaturesBuilder, + estimateProfit +} diff --git a/oracle/src/events/processAffirmationRequests/estimateGas.js b/oracle/src/events/processAffirmationRequests/estimateGas.js index f49ae4e5e..6ab9de04c 100644 --- a/oracle/src/events/processAffirmationRequests/estimateGas.js +++ b/oracle/src/events/processAffirmationRequests/estimateGas.js @@ -6,11 +6,9 @@ const logger = require('../../services/logger').child({ async function estimateGas({ web3, homeBridge, validatorContract, recipient, value, txHash, address }) { try { - const gasEstimate = await homeBridge.methods.executeAffirmation(recipient, value, txHash).estimateGas({ + return await homeBridge.methods.executeAffirmation(recipient, value, txHash).estimateGas({ from: address }) - - return gasEstimate } catch (e) { if (e instanceof HttpListProviderError) { throw e diff --git a/oracle/src/events/processCollectedSignatures/estimateGas.js b/oracle/src/events/processCollectedSignatures/estimateGas.js index dae223258..08c690dec 100644 --- a/oracle/src/events/processCollectedSignatures/estimateGas.js +++ b/oracle/src/events/processCollectedSignatures/estimateGas.js @@ -20,8 +20,7 @@ async function estimateGas({ signatures }) { try { - const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas() - return gasEstimate + return await foreignBridge.methods.executeSignatures(message, signatures).estimateGas() } catch (e) { if (e instanceof HttpListProviderError) { throw e diff --git a/oracle/src/events/processSignatureRequests/estimateGas.js b/oracle/src/events/processSignatureRequests/estimateGas.js index 374e0817a..238f24023 100644 --- a/oracle/src/events/processSignatureRequests/estimateGas.js +++ b/oracle/src/events/processSignatureRequests/estimateGas.js @@ -6,10 +6,9 @@ const logger = require('../../services/logger').child({ async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) { try { - const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({ + return await homeBridge.methods.submitSignature(signature, message).estimateGas({ from: address }) - return gasEstimate } catch (e) { if (e instanceof HttpListProviderError) { throw e diff --git a/oracle/src/mevSender.js b/oracle/src/mevSender.js new file mode 100644 index 000000000..03dbc09e9 --- /dev/null +++ b/oracle/src/mevSender.js @@ -0,0 +1,159 @@ +require('../env') +const path = require('path') +const BigNumber = require('bignumber.js') +const { redis } = require('./services/redisClient') +const logger = require('./services/logger') +const { sendTx } = require('./tx/sendTx') +const { getNonce, getChainId, getBlock } = require('./tx/web3') +const { addExtraGas, checkHTTPS, watchdog } = require('./utils/utils') +const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants') +const { estimateProfit } = require('./events/processAMBCollectedSignaturesMEV') + +if (process.argv.length < 3) { + logger.error('Please check the number of arguments, config file was not provided') + process.exit(EXIT_CODES.GENERAL_ERROR) +} + +const config = require(path.join('../config/', process.argv[2])) + +const { web3, mevForeign, validatorAddress } = config + +let chainId = 0 +let flashbotsProvider + +async function initialize() { + try { + const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) + + web3.currentProvider.urls.forEach(checkHttps(config.id)) + + chainId = await getChainId(web3) + flashbotsProvider = await mevForeign.getFlashbotsProvider(chainId) + return runMain() + } catch (e) { + logger.error(e.message) + process.exit(EXIT_CODES.GENERAL_ERROR) + } +} + +async function runMain() { + try { + if (redis.status === 'ready') { + if (config.maxProcessingTime) { + await watchdog(main, config.maxProcessingTime, () => { + logger.fatal('Max processing time reached') + process.exit(EXIT_CODES.MAX_TIME_REACHED) + }) + } else { + await main() + } + } + } catch (e) { + logger.error(e) + } + + setTimeout(runMain, config.pollingInterval) +} + +async function main() { + try { + const jobs = Object.values(await redis.hgetall(config.mevJobsRedisKey)).map(JSON.parse) + const totalJobs = jobs.length + + if (totalJobs === 0) { + logger.debug('Nothing to process') + return + } + + const { baseFeePerGas: pendingBaseFee, number: pendingBlockNumber } = await getBlock(web3, 'pending') + const bestJob = pickBestJob(jobs, pendingBaseFee) + + if (!bestJob) { + logger.info({ totalJobs, pendingBaseFee }, 'No suitable job was found, waiting for a lower gas price') + return + } + + const jobLogger = logger.child({ eventTransactionHash: bestJob.transactionReference }) + + const maxProfit = await estimateProfit( + mevForeign.contract, + mevForeign.minGasPrice, + bestJob.executeData, + bestJob.value + ) + + if (maxProfit === '0') { + jobLogger.info(`No MEV opportunity found when testing with min gas price ${mevForeign.minGasPrice}, removing job`) + await redis.hdel(config.mevJobsRedisKey, bestJob.transactionReference) + return + } + jobLogger.info(`Estimated profit of ${maxProfit} when simulating with ${mevForeign.minGasPrice} gas price`) + bestJob.profit = maxProfit + + if (new BigNumber(pendingBaseFee).gt(mevForeign.minGasPrice)) { + const profit = await estimateProfit(mevForeign.contract, pendingBaseFee, bestJob.executeData, bestJob.value) + if (profit === '0') { + jobLogger.info( + `No MEV opportunity found when testing with current gas price ${pendingBaseFee}, waiting for lower gas price` + ) + bestJob.maxFeePerGas = pendingBaseFee + await redis.hset(config.mevJobsRedisKey, bestJob.transactionReference, JSON.stringify(bestJob)) + return + } + jobLogger.info(`Estimated profit of ${profit} when simulating with ${pendingBaseFee} gas price`) + } + + let gasLimit + if (typeof bestJob.extraGas === 'number') { + gasLimit = addExtraGas(bestJob.gasEstimate + bestJob.extraGas, 0, MAX_GAS_LIMIT) + } else { + gasLimit = addExtraGas(bestJob.gasEstimate, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT) + } + + const nonce = await getNonce(web3, validatorAddress) + jobLogger.info( + { nonce, fromBlock: pendingBlockNumber, toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1 }, + 'Sending MEV bundles' + ) + const txHash = await sendTx({ + data: bestJob.data, + nonce, + value: bestJob.value, + gasLimit, + privateKey: config.validatorPrivateKey, + to: bestJob.to, + chainId, + web3, + gasPriceOptions: { + maxFeePerGas: bestJob.maxFeePerGas, + maxPriorityFeePerGas: bestJob.maxPriorityFeePerGas + }, + mevOptions: { + provider: flashbotsProvider, + fromBlock: pendingBlockNumber, + toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1, + logger + } + }) + + jobLogger.info({ txHash }, `Tx generated ${txHash} for event Tx ${bestJob.transactionReference}`) + + await redis.hset(config.mevJobsRedisKey, bestJob.transactionReference, JSON.stringify(bestJob)) + jobLogger.debug(`Finished processing msg`) + } catch (e) { + logger.error(e) + } +} + +function pickBestJob(jobs, feePerGas) { + const feePerGasBN = new BigNumber(feePerGas) + let best = null + jobs.forEach(job => { + if (feePerGasBN.lt(job.maxFeePerGas) && (!best || new BigNumber(best.profit).lt(job.profit))) { + best = job + } + }) + return best +} + +initialize() diff --git a/oracle/src/mevWatcher.js b/oracle/src/mevWatcher.js new file mode 100644 index 000000000..2df086e72 --- /dev/null +++ b/oracle/src/mevWatcher.js @@ -0,0 +1,251 @@ +require('../env') +const path = require('path') +const { redis } = require('./services/redisClient') +const logger = require('./services/logger') +const { getBlockNumber, getRequiredBlockConfirmations, getEvents } = require('./tx/web3') +const { checkHTTPS, watchdog, syncForEach } = require('./utils/utils') +const { processCollectedSignaturesBuilder } = require('./events/processAMBCollectedSignaturesMEV') +const { + EXIT_CODES, + BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT, + MAX_HISTORY_BLOCK_TO_REPROCESS +} = require('./utils/constants') + +if (process.argv.length < 3) { + logger.error('Please check the number of arguments, config file was not provided') + process.exit(EXIT_CODES.GENERAL_ERROR) +} + +const config = require(path.join('../config/', process.argv[2])) + +const processAMBCollectedSignaturesMEV = processCollectedSignaturesBuilder(config) + +const { + web3, + bridgeContract, + eventContract, + startBlock, + pollingInterval, + chain, + reprocessingOptions, + blockPollingLimit +} = config.main +const lastBlockRedisKey = `${config.id}:lastProcessedBlock` +const lastReprocessedBlockRedisKey = `${config.id}:lastReprocessedBlock` +const seenEventsRedisKey = `${config.id}:seenEvents` +const mevJobsRedisKey = `${config.id}:mevJobs` +let lastProcessedBlock = Math.max(startBlock - 1, 0) +let lastReprocessedBlock +let lastSeenBlockNumber = 0 +let sameBlockNumberCounter = 0 + +async function initialize() { + try { + const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) + + web3.currentProvider.urls.forEach(checkHttps(chain)) + + await getLastProcessedBlock() + await getLastReprocessedBlock() + runMain({ sendToQueue: saveJobsToRedis }) + } catch (e) { + logger.error(e) + process.exit(EXIT_CODES.GENERAL_ERROR) + } +} + +async function runMain({ sendToQueue }) { + try { + if (redis.status === 'ready') { + if (config.maxProcessingTime) { + await watchdog(() => main({ sendToQueue }), config.maxProcessingTime, () => { + logger.fatal('Max processing time reached') + process.exit(EXIT_CODES.MAX_TIME_REACHED) + }) + } else { + await main({ sendToQueue }) + } + } + } catch (e) { + logger.error(e) + } + + setTimeout(() => { + runMain({ sendToQueue }) + }, pollingInterval) +} + +async function saveJobsToRedis(jobs) { + return syncForEach(jobs, job => redis.hset(mevJobsRedisKey, job.transactionReference, JSON.stringify(job))) +} + +async function getLastProcessedBlock() { + const result = await redis.get(lastBlockRedisKey) + logger.debug({ fromRedis: result, fromConfig: lastProcessedBlock }, 'Last Processed block obtained') + lastProcessedBlock = result ? parseInt(result, 10) : lastProcessedBlock +} + +async function getLastReprocessedBlock() { + if (reprocessingOptions.enabled) { + const result = await redis.get(lastReprocessedBlockRedisKey) + if (result) { + lastReprocessedBlock = Math.max(parseInt(result, 10), lastProcessedBlock - MAX_HISTORY_BLOCK_TO_REPROCESS) + } else { + lastReprocessedBlock = lastProcessedBlock + } + logger.debug({ block: lastReprocessedBlock }, 'Last reprocessed block obtained') + } else { + // when reprocessing is being enabled not for the first time, + // we do not want to process blocks for which we didn't recorded seen events, + // instead, we want to start from the current block. + // Thus we should delete this reprocessing pointer once it is disabled. + await redis.del(lastReprocessedBlockRedisKey) + } +} + +function updateLastProcessedBlock(lastBlockNumber) { + lastProcessedBlock = lastBlockNumber + return redis.set(lastBlockRedisKey, lastProcessedBlock) +} + +function updateLastReprocessedBlock(lastBlockNumber) { + lastReprocessedBlock = lastBlockNumber + return redis.set(lastReprocessedBlockRedisKey, lastReprocessedBlock) +} + +function processEvents(events) { + switch (config.id) { + case 'amb-collected-signatures-mev': + return processAMBCollectedSignaturesMEV(events) + default: + return [] + } +} + +const eventKey = e => `${e.transactionHash}-${e.logIndex}` + +async function reprocessOldLogs(sendToQueue) { + const fromBlock = lastReprocessedBlock + 1 + const toBlock = lastReprocessedBlock + reprocessingOptions.batchSize + const events = await getEvents({ + contract: eventContract, + event: config.event, + fromBlock, + toBlock, + filter: config.eventFilter + }) + const alreadySeenEvents = await getSeenEvents(fromBlock, toBlock) + const missingEvents = events.filter(e => !alreadySeenEvents[eventKey(e)]) + if (missingEvents.length === 0) { + logger.debug('No missed events were found') + } else { + logger.info(`Found ${missingEvents.length} ${config.event} missed events`) + const job = await processEvents(missingEvents) + logger.info('Missed events transactions to send:', job.length) + if (job.length) { + await sendToQueue(job) + } + } + + await updateLastReprocessedBlock(toBlock) + await deleteSeenEvents(0, toBlock) +} + +async function getSeenEvents(fromBlock, toBlock) { + const keys = await redis.zrangebyscore(seenEventsRedisKey, fromBlock, toBlock) + const res = {} + keys.forEach(k => { + res[k] = true + }) + return res +} + +function deleteSeenEvents(fromBlock, toBlock) { + return redis.zremrangebyscore(seenEventsRedisKey, fromBlock, toBlock) +} + +function addSeenEvents(events) { + return redis.zadd(seenEventsRedisKey, ...events.flatMap(e => [e.blockNumber, eventKey(e)])) +} + +async function getLastBlockToProcess(web3, bridgeContract) { + const [lastBlockNumber, requiredBlockConfirmations] = await Promise.all([ + getBlockNumber(web3), + getRequiredBlockConfirmations(bridgeContract) + ]) + + if (lastBlockNumber < lastSeenBlockNumber) { + sameBlockNumberCounter = 0 + logger.warn({ lastBlockNumber, lastSeenBlockNumber }, 'Received block number less than already seen block') + web3.currentProvider.switchToFallbackRPC() + } else if (lastBlockNumber === lastSeenBlockNumber) { + sameBlockNumberCounter++ + if (sameBlockNumberCounter > 1) { + logger.info({ lastBlockNumber, sameBlockNumberCounter }, 'Received the same block number more than twice') + if (sameBlockNumberCounter >= BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT) { + sameBlockNumberCounter = 0 + logger.warn( + { lastBlockNumber, n: BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT }, + 'Received the same block number for too many times. Probably node is not synced anymore' + ) + web3.currentProvider.switchToFallbackRPC() + } + } + } else { + sameBlockNumberCounter = 0 + lastSeenBlockNumber = lastBlockNumber + } + return lastBlockNumber - requiredBlockConfirmations +} + +async function main({ sendToQueue }) { + try { + const lastBlockToProcess = await getLastBlockToProcess(web3, bridgeContract) + + if (reprocessingOptions.enabled) { + if (lastReprocessedBlock + reprocessingOptions.batchSize + reprocessingOptions.blockDelay < lastBlockToProcess) { + await reprocessOldLogs(sendToQueue) + return + } + } + + if (lastBlockToProcess <= lastProcessedBlock) { + logger.debug('All blocks already processed') + return + } + + const fromBlock = lastProcessedBlock + 1 + const rangeEndBlock = blockPollingLimit ? fromBlock + blockPollingLimit : lastBlockToProcess + const toBlock = Math.min(lastBlockToProcess, rangeEndBlock) + + const events = await getEvents({ + contract: eventContract, + event: config.event, + fromBlock, + toBlock, + filter: config.eventFilter + }) + logger.info(`Found ${events.length} ${config.event} events`) + + if (events.length) { + const job = await processEvents(events) + logger.info('Transactions to send:', job.length) + + if (job.length) { + await sendToQueue(job) + } + if (reprocessingOptions.enabled) { + await addSeenEvents(events) + } + } + + logger.debug({ lastProcessedBlock: toBlock.toString() }, 'Updating last processed block') + await updateLastProcessedBlock(toBlock) + } catch (e) { + logger.error(e) + } + + logger.debug('Finished') +} + +initialize() diff --git a/oracle/src/sender.js b/oracle/src/sender.js index 529e95d7d..46aa607bc 100644 --- a/oracle/src/sender.js +++ b/oracle/src/sender.js @@ -169,7 +169,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT const txHash = await sendTx({ data: job.data, nonce, - amount: '0', + value: '0', gasLimit, privateKey: config.validatorPrivateKey, to: job.to, diff --git a/oracle/src/services/web3.js b/oracle/src/services/web3.js index 0d0c49df0..0f060de42 100644 --- a/oracle/src/services/web3.js +++ b/oracle/src/services/web3.js @@ -1,4 +1,6 @@ const Web3 = require('web3') +const ethers = require('ethers') +const flashbots = require('@flashbots/ethers-provider-bundle') const { HttpListProvider } = require('./HttpListProvider') const { SafeEthLogsProvider } = require('./SafeEthLogsProvider') const { RedundantHttpListProvider } = require('./RedundantHttpListProvider') @@ -9,6 +11,8 @@ const { COMMON_FOREIGN_RPC_URL, ORACLE_SIDE_RPC_URL, ORACLE_FOREIGN_ARCHIVE_RPC_URL, + ORACLE_MEV_FOREIGN_FLASHBOTS_RPC_URL, + ORACLE_MEV_FOREIGN_FLASHBOTS_AUTH_SIGNING_KEY, ORACLE_RPC_REQUEST_TIMEOUT, ORACLE_HOME_RPC_POLLING_INTERVAL, ORACLE_FOREIGN_RPC_POLLING_INTERVAL @@ -94,6 +98,15 @@ if (foreignUrls.length > 1) { web3ForeignRedundant = new Web3(redundantProvider) } +let getFlashbotsProvider +if (ORACLE_MEV_FOREIGN_FLASHBOTS_RPC_URL) { + const provider = new ethers.providers.JsonRpcProvider(foreignUrls[0]) + const authSigner = new ethers.Wallet(ORACLE_MEV_FOREIGN_FLASHBOTS_AUTH_SIGNING_KEY, provider) + + getFlashbotsProvider = chainId => + flashbots.FlashbotsBundleProvider.create(provider, authSigner, ORACLE_MEV_FOREIGN_FLASHBOTS_RPC_URL, chainId) +} + module.exports = { web3Home, web3Foreign, @@ -102,5 +115,6 @@ module.exports = { web3HomeRedundant, web3ForeignRedundant, web3HomeFallback, - web3ForeignFallback + web3ForeignFallback, + getFlashbotsProvider } diff --git a/oracle/src/tx/sendTx.js b/oracle/src/tx/sendTx.js index ab660ad43..88d95ccdb 100644 --- a/oracle/src/tx/sendTx.js +++ b/oracle/src/tx/sendTx.js @@ -1,6 +1,5 @@ -const { toWei } = require('web3').utils - -async function sendTx({ privateKey, data, nonce, gasPrice, gasPriceOptions, amount, gasLimit, to, chainId, web3 }) { +async function sendTx(opts) { + const { privateKey, data, nonce, gasPrice, gasPriceOptions, value, gasLimit, to, chainId, web3, mevOptions } = opts const gasOpts = gasPriceOptions || { gasPrice } const serializedTx = await web3.eth.accounts.signTransaction( { @@ -8,19 +7,32 @@ async function sendTx({ privateKey, data, nonce, gasPrice, gasPriceOptions, amou chainId, to, data, - value: toWei(amount), + value, gas: gasLimit, ...gasOpts }, privateKey ) - return new Promise((res, rej) => - web3.eth - .sendSignedTransaction(serializedTx.rawTransaction) - .once('transactionHash', res) - .once('error', rej) + if (!mevOptions) { + return new Promise((res, rej) => + web3.eth + .sendSignedTransaction(serializedTx.rawTransaction) + .once('transactionHash', res) + .once('error', rej) + ) + } + + mevOptions.logger.debug( + { rawTx: serializedTx.rawTransaction, txHash: serializedTx.transactionHash }, + 'Signed MEV helper transaction' ) + + for (let blockNumber = mevOptions.fromBlock; blockNumber <= mevOptions.toBlock; blockNumber++) { + mevOptions.logger.debug({ txHash: serializedTx.transactionHash, blockNumber }, 'Sending MEV bundle transaction') + await mevOptions.provider.sendRawBundle([serializedTx.rawTransaction], blockNumber) + } + return Promise.resolve(serializedTx.transactionHash) } module.exports = { diff --git a/oracle/src/utils/mev.js b/oracle/src/utils/mev.js new file mode 100644 index 000000000..32f3c0d2c --- /dev/null +++ b/oracle/src/utils/mev.js @@ -0,0 +1,43 @@ +const MEV_HELPER_ABI = [ + { + constant: false, + inputs: [ + { + name: '_data', + type: 'bytes' + } + ], + name: 'execute', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_gasPrice', + type: 'uint256' + }, + { + name: '_data', + type: 'bytes' + } + ], + name: 'estimateProfit', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: true, + stateMutability: 'nonpayable', + type: 'function' + } +] + +module.exports = { + MEV_HELPER_ABI +} diff --git a/oracle/src/watcher.js b/oracle/src/watcher.js index 22bb91ada..e7ef51ac6 100644 --- a/oracle/src/watcher.js +++ b/oracle/src/watcher.js @@ -30,7 +30,16 @@ const processAMBInformationRequests = require('./events/processAMBInformationReq const { getTokensState } = require('./utils/tokenState') -const { web3, bridgeContract, eventContract, startBlock, pollingInterval, chain, reprocessingOptions } = config.main +const { + web3, + bridgeContract, + eventContract, + startBlock, + pollingInterval, + chain, + reprocessingOptions, + blockPollingLimit +} = config.main const lastBlockRedisKey = `${config.id}:lastProcessedBlock` const lastReprocessedBlockRedisKey = `${config.id}:lastReprocessedBlock` const seenEventsRedisKey = `${config.id}:seenEvents` @@ -268,7 +277,7 @@ async function main({ sendToQueue }) { } const fromBlock = lastProcessedBlock + 1 - const rangeEndBlock = config.blockPollingLimit ? fromBlock + config.blockPollingLimit : lastBlockToProcess + const rangeEndBlock = blockPollingLimit ? fromBlock + blockPollingLimit : lastBlockToProcess let toBlock = Math.min(lastBlockToProcess, rangeEndBlock) let events = await getEvents({ diff --git a/yarn.lock b/yarn.lock index f55ac82ce..2b52489ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1337,6 +1337,34 @@ "@ethersproject/properties" "^5.0.3" "@ethersproject/strings" "^5.0.4" +"@ethersproject/abi@5.5.0", "@ethersproject/abi@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613" + integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w== + dependencies: + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + +"@ethersproject/abstract-provider@5.5.1", "@ethersproject/abstract-provider@^5.5.0": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5" + integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/web" "^5.5.0" + "@ethersproject/abstract-provider@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.1.0.tgz#1f24c56cda5524ef4ed3cfc562a01d6b6f8eeb0b" @@ -1350,6 +1378,17 @@ "@ethersproject/transactions" "^5.1.0" "@ethersproject/web" "^5.1.0" +"@ethersproject/abstract-signer@5.5.0", "@ethersproject/abstract-signer@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d" + integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA== + dependencies: + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/abstract-signer@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.1.0.tgz#744c7a2d0ebe3cc0bc38294d0f53d5ca3f4e49e3" @@ -1361,6 +1400,17 @@ "@ethersproject/logger" "^5.1.0" "@ethersproject/properties" "^5.1.0" +"@ethersproject/address@5.5.0", "@ethersproject/address@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f" + integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.4", "@ethersproject/address@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.1.0.tgz#3854fd7ebcb6af7597de66f847c3345dae735b58" @@ -1372,6 +1422,13 @@ "@ethersproject/logger" "^5.1.0" "@ethersproject/rlp" "^5.1.0" +"@ethersproject/base64@5.5.0", "@ethersproject/base64@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090" + integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/base64@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.1.0.tgz#27240c174d0a4e13f6eae87416fd876caf7f42b6" @@ -1379,6 +1436,23 @@ dependencies: "@ethersproject/bytes" "^5.1.0" +"@ethersproject/basex@5.5.0", "@ethersproject/basex@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.5.0.tgz#e40a53ae6d6b09ab4d977bd037010d4bed21b4d3" + integrity sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + +"@ethersproject/bignumber@5.5.0", "@ethersproject/bignumber@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527" + integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + bn.js "^4.11.9" + "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@^5.0.7", "@ethersproject/bignumber@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.1.1.tgz#84812695253ccbc639117f7ac49ee1529b68e637" @@ -1388,6 +1462,13 @@ "@ethersproject/logger" "^5.1.0" bn.js "^4.4.0" +"@ethersproject/bytes@5.5.0", "@ethersproject/bytes@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c" + integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog== + dependencies: + "@ethersproject/logger" "^5.5.0" + "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.1.0.tgz#55dfa9c4c21df1b1b538be3accb50fb76d5facfd" @@ -1395,6 +1476,13 @@ dependencies: "@ethersproject/logger" "^5.1.0" +"@ethersproject/constants@5.5.0", "@ethersproject/constants@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e" + integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.1.0.tgz#4e7da6367ea0e9be87585d8b09f3fccf384b1452" @@ -1402,6 +1490,36 @@ dependencies: "@ethersproject/bignumber" "^5.1.0" +"@ethersproject/contracts@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.5.0.tgz#b735260d4bd61283a670a82d5275e2a38892c197" + integrity sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + +"@ethersproject/hash@5.5.0", "@ethersproject/hash@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9" + integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg== + dependencies: + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.0.4": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.1.0.tgz#40961d64837d57f580b7b055e0d74174876d891e" @@ -1416,6 +1534,51 @@ "@ethersproject/properties" "^5.1.0" "@ethersproject/strings" "^5.1.0" +"@ethersproject/hdnode@5.5.0", "@ethersproject/hdnode@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.5.0.tgz#4a04e28f41c546f7c978528ea1575206a200ddf6" + integrity sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q== + dependencies: + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/basex" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/pbkdf2" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + "@ethersproject/signing-key" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/wordlists" "^5.5.0" + +"@ethersproject/json-wallets@5.5.0", "@ethersproject/json-wallets@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz#dd522d4297e15bccc8e1427d247ec8376b60e325" + integrity sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ== + dependencies: + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/hdnode" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/pbkdf2" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/random" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.5.0", "@ethersproject/keccak256@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492" + integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg== + dependencies: + "@ethersproject/bytes" "^5.5.0" + js-sha3 "0.8.0" + "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.3", "@ethersproject/keccak256@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.1.0.tgz#fdcd88fb13bfef4271b225cdd8dec4d315c8e60e" @@ -1424,11 +1587,23 @@ "@ethersproject/bytes" "^5.1.0" js-sha3 "0.5.7" +"@ethersproject/logger@5.5.0", "@ethersproject/logger@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d" + integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg== + "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.1.0.tgz#4cdeeefac029373349d5818f39c31b82cc6d9bbf" integrity sha512-wtUaD1lBX10HBXjjKV9VHCBnTdUaKQnQ2XSET1ezglqLdPdllNOIlLfhyCRqXm5xwcjExVI5ETokOYfjPtaAlw== +"@ethersproject/networks@5.5.2", "@ethersproject/networks@^5.5.0": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.2.tgz#784c8b1283cd2a931114ab428dae1bd00c07630b" + integrity sha512-NEqPxbGBfy6O3x4ZTISb90SjEDkWYDUbEeIFhJly0F7sZjoQMnj5KYzMSkMkLKZ+1fGpx00EDpHQCy6PrDupkQ== + dependencies: + "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.1.0.tgz#f537290cb05aa6dc5e81e910926c04cfd5814bca" @@ -1436,6 +1611,21 @@ dependencies: "@ethersproject/logger" "^5.1.0" +"@ethersproject/pbkdf2@5.5.0", "@ethersproject/pbkdf2@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz#e25032cdf02f31505d47afbf9c3e000d95c4a050" + integrity sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + +"@ethersproject/properties@5.5.0", "@ethersproject/properties@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995" + integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA== + dependencies: + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@^5.0.3", "@ethersproject/properties@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.1.0.tgz#9484bd6def16595fc6e4bdc26f29dff4d3f6ac42" @@ -1443,6 +1633,47 @@ dependencies: "@ethersproject/logger" "^5.1.0" +"@ethersproject/providers@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.2.tgz#131ccf52dc17afd0ab69ed444b8c0e3a27297d99" + integrity sha512-hkbx7x/MKcRjyrO4StKXCzCpWer6s97xnm34xkfPiarhtEUVAN4TBBpamM+z66WcTt7H5B53YwbRj1n7i8pZoQ== + dependencies: + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/basex" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/random" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/web" "^5.5.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.5.1", "@ethersproject/random@^5.5.0": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.1.tgz#7cdf38ea93dc0b1ed1d8e480ccdaf3535c555415" + integrity sha512-YaU2dQ7DuhL5Au7KbcQLHxcRHfgyNgvFV4sQOo0HrtW3Zkrc9ctWNz8wXQ4uCSfSDsqX2vcjhroxU5RQRV0nqA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/rlp@5.5.0", "@ethersproject/rlp@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0" + integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/rlp@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.1.0.tgz#700f4f071c27fa298d3c1d637485fefe919dd084" @@ -1451,6 +1682,27 @@ "@ethersproject/bytes" "^5.1.0" "@ethersproject/logger" "^5.1.0" +"@ethersproject/sha2@5.5.0", "@ethersproject/sha2@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.5.0.tgz#a40a054c61f98fd9eee99af2c3cc6ff57ec24db7" + integrity sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.5.0", "@ethersproject/signing-key@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0" + integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + bn.js "^4.11.9" + elliptic "6.5.4" + hash.js "1.1.7" + "@ethersproject/signing-key@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.1.0.tgz#6eddfbddb6826b597b9650e01acf817bf8991b9c" @@ -1462,6 +1714,27 @@ bn.js "^4.4.0" elliptic "6.5.4" +"@ethersproject/solidity@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.5.0.tgz#2662eb3e5da471b85a20531e420054278362f93f" + integrity sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + +"@ethersproject/strings@5.5.0", "@ethersproject/strings@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549" + integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.4", "@ethersproject/strings@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.1.0.tgz#0f95a56c3c8c9d5510a06c241d818779750e2da5" @@ -1471,6 +1744,21 @@ "@ethersproject/constants" "^5.1.0" "@ethersproject/logger" "^5.1.0" +"@ethersproject/transactions@5.5.0", "@ethersproject/transactions@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908" + integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA== + dependencies: + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ethersproject/signing-key" "^5.5.0" + "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.1.1.tgz#5a6bbb25fb062c3cc75eb0db12faefcdd3870813" @@ -1486,6 +1774,47 @@ "@ethersproject/rlp" "^5.1.0" "@ethersproject/signing-key" "^5.1.0" +"@ethersproject/units@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.5.0.tgz#104d02db5b5dc42cc672cc4587bafb87a95ee45e" + integrity sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/wallet@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.5.0.tgz#322a10527a440ece593980dca6182f17d54eae75" + integrity sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q== + dependencies: + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/hdnode" "^5.5.0" + "@ethersproject/json-wallets" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/random" "^5.5.0" + "@ethersproject/signing-key" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/wordlists" "^5.5.0" + +"@ethersproject/web@5.5.1", "@ethersproject/web@^5.5.0": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.1.tgz#cfcc4a074a6936c657878ac58917a61341681316" + integrity sha512-olvLvc1CB12sREc1ROPSHTdFCdvMh0J5GSJYiQg2D0hdD4QmJDy8QYDb1CvoqD/bF1c++aeKv2sR5uduuG9dQg== + dependencies: + "@ethersproject/base64" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/web@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.1.0.tgz#ed56bbe4e3d9a8ffe3b2ed882da5c62d3551381b" @@ -1497,6 +1826,17 @@ "@ethersproject/properties" "^5.1.0" "@ethersproject/strings" "^5.1.0" +"@ethersproject/wordlists@5.5.0", "@ethersproject/wordlists@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.5.0.tgz#aac74963aa43e643638e5172353d931b347d584f" + integrity sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -1576,6 +1916,14 @@ resolved "https://registry.yarnpkg.com/@findeth/abi/-/abi-0.7.1.tgz#60d0801cb252e587dc3228f00c00581bb748aebc" integrity sha512-9uNu+/UxeuIibxIB7slf7BGG2PWjgBZr+rKzohhLb7VuoZjmlCcKZkenqwErROxkPdsap7OGO/o1DuYMvObMvw== +"@flashbots/ethers-provider-bundle@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@flashbots/ethers-provider-bundle/-/ethers-provider-bundle-0.4.3.tgz#d8d23684eb02829181672176b110bc262195ec48" + integrity sha512-vH5XhdNjFDG3+m2rHa4TBf7ljv+LDdtWldZCEmwmCggZN70cb2J1x7DlNROT/tE4keTMUjydMs4CfAOOyKc6UQ== + dependencies: + ts-node "^9.1.0" + typescript "^4.1.2" + "@graphql-tools/batch-delegate@^6.2.4", "@graphql-tools/batch-delegate@^6.2.6": version "6.2.6" resolved "https://registry.yarnpkg.com/@graphql-tools/batch-delegate/-/batch-delegate-6.2.6.tgz#fbea98dc825f87ef29ea5f3f371912c2a2aa2f2c" @@ -6665,6 +7013,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + bech32@=1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd" @@ -8482,6 +8835,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-fetch@3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" @@ -10831,6 +11189,42 @@ ethers@^4.0.32, ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" +ethers@^5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.3.tgz#1e361516711c0c3244b6210e7e3ecabf0c75fca0" + integrity sha512-fTT4WT8/hTe/BLwRUtl7I5zlpF3XC3P/Xwqxc5AIP2HGlH15qpmjs0Ou78az93b1rLITzXLFxoNX63B8ZbUd7g== + dependencies: + "@ethersproject/abi" "5.5.0" + "@ethersproject/abstract-provider" "5.5.1" + "@ethersproject/abstract-signer" "5.5.0" + "@ethersproject/address" "5.5.0" + "@ethersproject/base64" "5.5.0" + "@ethersproject/basex" "5.5.0" + "@ethersproject/bignumber" "5.5.0" + "@ethersproject/bytes" "5.5.0" + "@ethersproject/constants" "5.5.0" + "@ethersproject/contracts" "5.5.0" + "@ethersproject/hash" "5.5.0" + "@ethersproject/hdnode" "5.5.0" + "@ethersproject/json-wallets" "5.5.0" + "@ethersproject/keccak256" "5.5.0" + "@ethersproject/logger" "5.5.0" + "@ethersproject/networks" "5.5.2" + "@ethersproject/pbkdf2" "5.5.0" + "@ethersproject/properties" "5.5.0" + "@ethersproject/providers" "5.5.2" + "@ethersproject/random" "5.5.1" + "@ethersproject/rlp" "5.5.0" + "@ethersproject/sha2" "5.5.0" + "@ethersproject/signing-key" "5.5.0" + "@ethersproject/solidity" "5.5.0" + "@ethersproject/strings" "5.5.0" + "@ethersproject/transactions" "5.5.0" + "@ethersproject/units" "5.5.0" + "@ethersproject/wallet" "5.5.0" + "@ethersproject/web" "5.5.1" + "@ethersproject/wordlists" "5.5.0" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -12601,7 +12995,7 @@ hash.js@1.1.3: inherits "^2.0.3" minimalistic-assert "^1.0.0" -hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -14709,16 +15103,16 @@ js-sha3@0.5.7, js-sha3@^0.5.7: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" integrity sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc= +js-sha3@0.8.0, js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + js-sha3@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.6.1.tgz#5b89f77a7477679877f58c4a075240934b1f95c0" integrity sha1-W4n3enR3Z5h39YxKB1JAk0sflcA= -js-sha3@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -16720,9 +17114,9 @@ nanoid@^3.1.12, nanoid@^3.1.3: integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== nanoid@^3.1.28: - version "3.1.30" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" - integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" + integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== nanomatch@^1.2.9: version "1.2.13" @@ -20950,7 +21344,7 @@ scrypt-js@2.0.4: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== -scrypt-js@^3.0.0, scrypt-js@^3.0.1: +scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== @@ -21580,6 +21974,14 @@ source-map-support@^0.5.16, source-map-support@^0.5.3, source-map-support@^0.5.6 buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@^0.5.17: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -22891,6 +23293,18 @@ ts-node@^8.0.2: source-map-support "^0.5.6" yn "3.1.1" +ts-node@^9.1.0: + version "9.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== + dependencies: + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + ts-pnp@1.1.2, ts-pnp@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.2.tgz#be8e4bfce5d00f0f58e0666a82260c34a57af552" @@ -23051,6 +23465,11 @@ typescript@^3.5.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a" integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ== +typescript@^4.1.2: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + ua-parser-js@^0.7.18: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" @@ -25772,6 +26191,11 @@ ws@7.4.5, ws@^7.2.1, ws@^7.2.3, ws@^7.3.1, ws@^7.4.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"