diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..05ba80e --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@tornado:registry=https://git.tornado.ws/api/packages/tornado-packages/npm/ \ No newline at end of file diff --git a/package.json b/package.json index 81610a1..b167206 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "relay", + "name": "classic-relay", "version": "5.0.0", "description": "Relayer for Tornado.cash privacy solution. https://tornado.cash", "scripts": { @@ -25,6 +25,7 @@ "@fastify/cors": "^8.0.0", "@fastify/helmet": "^9.1.0", "@fastify/sensible": "^5.1.0", + "@tornado/gas-price-oracle": "^0.5.3", "ajv": "^8.11.0", "bullmq": "^1.80.6", "compare-versions": "^4.1.3", @@ -32,7 +33,6 @@ "dotenv": "^8.2.0", "ethers": "^5.6.4", "fastify": "^4.2.0", - "gas-price-oracle": "^0.5.0", "ioredis": "^5.0.6", "json-schema-to-ts": "^2.2.0", "node-fetch": "2", diff --git a/src/modules/utils.ts b/src/modules/utils.ts index 32d17d7..08a9df6 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -1,3 +1,5 @@ +import { BigNumber, BigNumberish } from 'ethers'; + export const parseJSON = (str: string) => { let parsed = null; try { @@ -8,3 +10,9 @@ export const parseJSON = (str: string) => { return parsed; } }; + +export const bump = (value: BigNumberish, percent: number | BigNumber): BigNumber => { + const hundredPercents = BigNumber.from(100); + + return BigNumber.from(value).mul(hundredPercents.add(percent)).div(hundredPercents); +}; diff --git a/src/queue/relayer.processor.ts b/src/queue/relayer.processor.ts index 6be89bd..062a86e 100644 --- a/src/queue/relayer.processor.ts +++ b/src/queue/relayer.processor.ts @@ -23,8 +23,8 @@ export const relayerProcessor: RelayerProcessor = async (job) => { const txService = getTxService(); txService.currentJob = job; const withdrawalData = job.data; - await txService.checkTornadoFee(withdrawalData); const txData = await txService.prepareTxData(withdrawalData); + await txService.checkTornadoFee(withdrawalData, txData); return await txService.sendTx(txData); } catch (e) { if ((e instanceof ExecutionError && e.code === 'REVERTED') || job.attemptsMade === txJobAttempts) { diff --git a/src/services/config.service.ts b/src/services/config.service.ts index f84ce16..a5e0a51 100644 --- a/src/services/config.service.ts +++ b/src/services/config.service.ts @@ -27,7 +27,7 @@ import { availableIds, netIds, NetInstances } from 'torn-token'; import { getAddress } from 'ethers/lib/utils'; import { BigNumber, providers, Wallet } from 'ethers'; import { container, singleton } from 'tsyringe'; -import { FallbackGasPrices } from 'gas-price-oracle'; +import { FallbackGasPrices } from '@tornado/gas-price-oracle'; import { RedisStore } from '../modules/redis'; type relayerQueueName = `relayer_${availableIds}`; diff --git a/src/services/tx.service.ts b/src/services/tx.service.ts index 6fa92bd..a8cda3b 100644 --- a/src/services/tx.service.ts +++ b/src/services/tx.service.ts @@ -1,5 +1,5 @@ import { TransactionData, TxManager } from 'tx-manager'; -import { GasPriceOracle } from 'gas-price-oracle'; +import { GasPriceOracle } from '@tornado/gas-price-oracle'; import { Provider } from '@ethersproject/providers'; import { serialize } from '@ethersproject/transactions'; import { formatEther, parseUnits } from 'ethers/lib/utils'; @@ -12,7 +12,7 @@ import { Job } from 'bullmq'; import { RelayerJobData } from '../queue'; import { ConfigService } from './config.service'; import { container, injectable } from 'tsyringe'; -import { parseJSON } from '../modules/utils'; +import { parseJSON, bump } from '../modules/utils'; import { getOvmGasPriceOracle } from '../modules/contracts'; export type WithdrawalData = { @@ -50,6 +50,9 @@ export class TxService { const gasPriceOracleConfig = { defaultRpc: rpcUrl, chainId: netId, + minPriority: netId === ChainIds.ethereum || ChainIds.goerli ? 2 : 0.05, + percentile: 5, + blocksCount: 20, fallbackGasPrices: this.config?.fallbackGasPrices, }; this.txManager = new TxManager({ @@ -116,15 +119,64 @@ export class TxService { } } + async getGasPrice(): Promise { + let bumpPercent: number; + switch (netId) { + case ChainIds.goerli: + bumpPercent = 50; + break; + case ChainIds.polygon: + case ChainIds.avalanche: + case ChainIds.xdai: + bumpPercent = 30; + break; + default: + bumpPercent = 10; + } + + try { + const gasParams = await this.oracle.getTxGasParams({ + legacySpeed: 'fast', + bumpPercent, + }); + + console.log(BigNumber.from(gasParams['maxFeePerGas']).toString()); + + return BigNumber.from(gasParams['maxFeePerGas'] || gasParams['gasPrice']); + } catch (e) { + const feeData = await this.provider.getFeeData(); + return bump(feeData.maxFeePerGas, bumpPercent); + } + } + + async estimateGasLimit(txData: TransactionData): Promise { + try { + const fetchedGasLimit = await this.provider.estimateGas(txData); + const bumped = bump(fetchedGasLimit, 10); + console.log('Gas limit: ', bumped.toString()); + return bumped; + } catch (e) { + console.log('Estimation error: ', e); + return BigNumber.from(this.gasLimit); + } + } + async prepareTxData(data: WithdrawalData): Promise { const { contract, proof, args } = data; const calldata = this.tornadoProxy.interface.encodeFunctionData('withdraw', [contract, proof, ...args]); - return { + + const gasPrice = await this.getGasPrice(); + const incompleteTxData: TransactionData = { value: args[5], to: this.tornadoProxy.address, + from: this.txManager.address, // Required to estimate relayerRegistry.burn for Ethereum Mainnet withdrawals data: calldata, gasLimit: this.gasLimit, + gasPrice: gasPrice, }; + const gasLimit = await this.estimateGasLimit(incompleteTxData); + + return Object.assign(incompleteTxData, { gasLimit }); } async getL1Fee(data: WithdrawalData, gasPrice: BigNumber) { @@ -145,18 +197,14 @@ export class TxService { return await ovmOracle.getL1Fee(tx); } - async checkTornadoFee(data: WithdrawalData) { + async checkTornadoFee(data: WithdrawalData, txData: TransactionData) { const { contract, args } = data; const instance = this.config.getInstance(contract); if (!instance) throw new Error('Instance not found'); const { currency, amount, decimals } = instance; const [fee, refund] = [args[4], args[5]].map(BigNumber.from); - const gasPrice = await this.getGasPrice(); - let gasLimit = this.gasLimit; - if (!this.config.isLightMode) { - gasLimit = gasLimits[RelayerJobType.TORNADO_WITHDRAW]; - } - let operationCost = gasPrice.mul(gasLimit); + const gasPrice = BigNumber.from(txData.gasPrice); + let operationCost = gasPrice.mul(txData.gasLimit); if (netId === ChainIds.optimism) { const l1Fee = await this.getL1Fee(data, gasPrice); @@ -183,15 +231,6 @@ export class TxService { throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.'); } } - - async getGasPrice(): Promise { - const gasPrices = await this.oracle.gasPrices(); - let gasPrice = gasPrices['fast']; - if ('maxFeePerGas' in gasPrices) { - gasPrice = gasPrices['maxFeePerGas']; - } - return parseUnits(String(gasPrice), 'gwei'); - } } export default () => container.resolve(TxService); diff --git a/yarn.lock b/yarn.lock index 05a160c..56f9345 100644 --- a/yarn.lock +++ b/yarn.lock @@ -857,6 +857,15 @@ dependencies: defer-to-connect "^1.0.1" +"@tornado/gas-price-oracle@^0.5.3": + version "0.5.3" + resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fgas-price-oracle/-/0.5.3/gas-price-oracle-0.5.3.tgz#fb5423dddee2f52edbc16174c5ddce90bea5413d" + integrity sha512-LpVfPiPIz3FOmJdiqJf/yoeO5n9/Pd5jgtdY+6hB9lNW0AiWhylhpScojICViS+3OL9QC8CoTlgr+kbfGeO9pQ== + dependencies: + axios "^0.21.2" + bignumber.js "^9.0.0" + node-cache "^5.1.2" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -1835,6 +1844,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@2.x: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + cluster-key-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" @@ -4427,6 +4441,13 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-cache@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" + integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== + dependencies: + clone "2.x" + node-fetch@2, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"