From e3e2ed677fab4c4ed95ae7f7ae5ccae3debab50b Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Mon, 2 Oct 2023 11:50:04 +0100 Subject: [PATCH 01/10] Try to support EIP1559 in packages --- .../MetatransactionBroadcaster.js | 12 +-- packages/package-utils/getFeeData.js | 87 +++++++++++++++++++ packages/package-utils/index.js | 2 +- packages/package-utils/updateGasEstimate.js | 55 ------------ packages/reputation-miner/ReputationMiner.js | 28 ++---- .../reputation-miner/ReputationMinerClient.js | 42 ++++----- 6 files changed, 124 insertions(+), 102 deletions(-) create mode 100644 packages/package-utils/getFeeData.js delete mode 100644 packages/package-utils/updateGasEstimate.js diff --git a/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js b/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js index b093f99961..90a1c0059f 100644 --- a/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js +++ b/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js @@ -4,7 +4,7 @@ const sqlite = require("sqlite"); const sqlite3 = require("sqlite3"); const queue = require("express-queue"); const NonceManager = require("./ExtendedNonceManager"); -const { colonyIOCors, ConsoleAdapter, updateGasEstimate } = require("../package-utils"); +const { colonyIOCors, ConsoleAdapter, getFeeData } = require("../package-utils"); const ETHEREUM_BRIDGE_ADDRESS = "0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59"; const BINANCE_BRIDGE_ADDRESS = "0x162E898bD0aacB578C8D5F8d6ca588c13d2A383F"; @@ -81,7 +81,7 @@ class MetatransactionBroadcaster { const colonyNetworkDef = await this.loader.load({ contractName: "IColonyNetwork" }, { abi: true, address: false }); this.colonyNetwork = new ethers.Contract(colonyNetworkAddress, colonyNetworkDef.abi, this.wallet); - this.gasPrice = await updateGasEstimate("safeLow", this.chainId, this.adapter); + this.feeData = await getFeeData("safeLow", this.chainId, this.adapter, this.provider); this.tokenLockingAddress = await this.colonyNetwork.getTokenLocking(); this.metaTxDef = await this.loader.load({ contractName: "IBasicMetaTransaction" }, { abi: true, address: false }); @@ -441,14 +441,14 @@ class MetatransactionBroadcaster { try { const { target, userAddress, payload, r, s, v } = req.body; const contract = new ethers.Contract(target, this.metaTxDef.abi, this.nonceManager); - this.gasPrice = await updateGasEstimate("safeLow", this.chainId, this.adapter); + this.feeData = await getFeeData("safeLow", this.chainId, this.adapter, this.provider); return this.processTransactionLogic(req, res, contract.estimateGas.executeMetaTransaction, contract.executeMetaTransaction, [ userAddress, payload, r, s, v, - { gasPrice: this.gasPrice, gasLimit: this.gasLimit }, + { ...this.feeData, gasLimit: this.gasLimit }, ]); } catch (err) { return res.status(500).send({ @@ -462,7 +462,7 @@ class MetatransactionBroadcaster { try { const { target, owner, spender, value, deadline, r, s, v } = req.body; const contract = new ethers.Contract(target, this.metaTxTokenDef.abi, this.nonceManager); - this.gasPrice = await updateGasEstimate("safeLow", this.chainId, this.adapter); + this.feeData = await getFeeData("safeLow", this.chainId, this.adapter, this.provider); return this.processTransactionLogic(req, res, contract.estimateGas.permit, contract.permit, [ owner, spender, @@ -471,7 +471,7 @@ class MetatransactionBroadcaster { v, r, s, - { gasPrice: this.gasPrice, gasLimit: this.gasLimit }, + { ...this.feeData, gasLimit: this.gasLimit }, ]); } catch (err) { return res.status(500).send({ diff --git a/packages/package-utils/getFeeData.js b/packages/package-utils/getFeeData.js new file mode 100644 index 0000000000..b0a8e0aaf3 --- /dev/null +++ b/packages/package-utils/getFeeData.js @@ -0,0 +1,87 @@ +const ethers = require("ethers"); +const axios = require("axios"); + +/** + * Update the gas estimate + * @param {string} Transaction speed (fastest, fast, safeLow) + * @param {number} Chain ID + * @param {object} Adapter + * @param {object} Provider + * @return {Promise} + */ +const getFeeData = async function (_type, chainId, adapter, provider) { + let defaultGasPrice; + let factor; + let feeData; + + let type = _type; + const options = { + headers: { + "User-Agent": "Request-Promise", + }, + json: true, // Automatically parses the JSON string in the response + }; + + if (chainId === 100) { + options.url = "https://blockscout.com/xdai/mainnet/api/v1/gas-price-oracle"; + defaultGasPrice = ethers.BigNumber.from(2000000000); + factor = 1; + // This oracle presents the information slightly differently from ethgasstation. + if (_type === "safeLow") { + type = "slow"; + } + } else if (chainId === 1) { + options.url = "https://ethgasstation.info/json/ethgasAPI.json"; + defaultGasPrice = ethers.BigNumber.from(2000000000); + factor = 10; + } else { + // We don't have an oracle, so just use the provided fee data + adapter.log(`During gas estimation: unknown chainid ${chainId}`); + feeData = await provider.getFeeData(); + delete feeData.lastBaseFeePerGas; + if (feeData.maxFeePerGas) { + delete feeData.gasPrice; + } + return feeData; + } + + try { + feeData = await provider.getFeeData(); + delete feeData.lastBaseFeePerGas; + // Update gas prices from whichever oracle + try { + const request = await axios.request(options); + const gasEstimates = request.data; + + if (feeData.maxFeePerGas) { + // Update the EIP1559 fee data based on the type + const ratio = gasEstimates[type] / gasEstimates.average; + // Increase the priority fee by this ratio + const newMaxPriorityFeePerGas = ethers.BigNumber.from(Math.floor(feeData.maxPriorityFeePerGas * 1000)) + .mul(Math.floor(ratio * 1000)) + .div(1000 * 1000); + // Increase the max fee per gas by the same amount (not the same ratio) + feeData.maxFeePerGas = feeData.maxFeePerGas.add(newMaxPriorityFeePerGas).sub(feeData.maxPriorityFeePerGas); + feeData.maxPriorityFeePerGas = newMaxPriorityFeePerGas; + delete feeData.gasPrice; + return feeData; + } + + // If we get here, chain is not EIP1559, so just update gasPrice + if (gasEstimates[type]) { + feeData.gasPrice = ethers.BigNumber.from(gasEstimates[type] * 1e9).div(factor); + } else { + feeData.gasPrice = defaultGasPrice; + } + } catch (err) { + adapter.error(`Error during gas estimation: ${err}`); + feeData = { gasPrice: defaultGasPrice }; + } + } catch (err) { + adapter.error(err); + feeData = { gasPrice: defaultGasPrice }; + } + return feeData; +}; + +module.exports = getFeeData; diff --git a/packages/package-utils/index.js b/packages/package-utils/index.js index 07cc7dc990..2b5ffbf94e 100644 --- a/packages/package-utils/index.js +++ b/packages/package-utils/index.js @@ -1,5 +1,5 @@ exports.colonyIOCors = require("./colonyIOCors"); -exports.updateGasEstimate = require("./updateGasEstimate"); +exports.getFeeData = require("./getFeeData"); exports.DiscordAdapter = require("./adapters/discord"); exports.SlackAdapter = require("./adapters/slack"); exports.ConsoleAdapter = require("./adapters/console"); diff --git a/packages/package-utils/updateGasEstimate.js b/packages/package-utils/updateGasEstimate.js deleted file mode 100644 index 84c088e71e..0000000000 --- a/packages/package-utils/updateGasEstimate.js +++ /dev/null @@ -1,55 +0,0 @@ -const ethers = require("ethers"); -const axios = require("axios"); - -/** - * Update the gas estimate - * @param {string} Transaction speed (fastest, fast, safeLow) - * @return {Promise} - */ -const updateGasEstimate = async function (_type, chainId, adapter) { - let type = _type; - const options = { - headers: { - "User-Agent": "Request-Promise", - }, - json: true, // Automatically parses the JSON string in the response - }; - let defaultGasPrice; - let factor; - - if (chainId === 100) { - options.url = "https://blockscout.com/xdai/mainnet/api/v1/gas-price-oracle"; - defaultGasPrice = ethers.utils.hexlify(2000000000); - factor = 1; - // This oracle presents the information slightly differently from ethgasstation. - if (_type === "safeLow") { - type = "slow"; - } - } else if (chainId === 1) { - options.url = "https://ethgasstation.info/json/ethgasAPI.json"; - defaultGasPrice = ethers.utils.hexlify(20000000000); - factor = 10; - } else { - adapter.error(`Error during gas estimation: unknown chainid ${chainId}`); - const gasPrice = ethers.utils.hexlify(20000000000); - return gasPrice; - } - - // Get latest from whichever oracle - try { - const request = await axios.request(options); - const gasEstimates = request.data; - let gasPrice; - if (gasEstimates[type]) { - gasPrice = ethers.utils.hexlify(ethers.BigNumber.from(Math.floor((gasEstimates[type] * 1e9) / factor))); - } else { - gasPrice = defaultGasPrice; - } - return gasPrice; - } catch (err) { - adapter.error(`Error during gas estimation: ${err}`); - return defaultGasPrice; - } -}; - -module.exports = updateGasEstimate; diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index ec76812b68..cd8e1256a5 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -96,7 +96,7 @@ class ReputationMiner { this.nReputations = ethers.constants.Zero; this.reputations = {}; - this.gasPrice = ethers.utils.hexlify(20000000000); + this.feeData = {}; const repCycle = await this.getActiveRepCycle(); await this.updatePeriodLength(repCycle); this.db = new Database(this.dbPath, { }); @@ -763,7 +763,7 @@ class ReputationMiner { } // Submit that entry - return repCycle.submitRootHash(hash, nLeaves, jrh, entryIndex, { gasLimit: gasEstimate, gasPrice: this.gasPrice }); + return repCycle.submitRootHash(hash, nLeaves, jrh, entryIndex, { gasLimit: gasEstimate, ...this.feeData }); } async getEntryIndex(startIndex = 1) { @@ -1017,7 +1017,7 @@ class ReputationMiner { index, siblings1, siblings2, - { gasLimit: gasEstimate, gasPrice: this.gasPrice } + { gasLimit: gasEstimate, ...this.feeData } ); } @@ -1107,7 +1107,7 @@ class ReputationMiner { siblings, { gasLimit: gasEstimate, - gasPrice: this.gasPrice + ...this.feeData } ); } @@ -1139,7 +1139,7 @@ class ReputationMiner { return repCycle.confirmBinarySearchResult(round, index, intermediateReputationHash, siblings, { gasLimit: gasEstimate, - gasPrice: this.gasPrice + ...this.feeData }); } @@ -1243,7 +1243,7 @@ class ReputationMiner { } return repCycle.respondToChallenge(...functionArgs, - { gasLimit: gasEstimate, gasPrice: this.gasPrice } + { gasLimit: gasEstimate, ...this.feeData } ); } @@ -1263,7 +1263,7 @@ class ReputationMiner { } catch (err){ gasEstimate = ethers.BigNumber.from(4000000); } - return repCycle.confirmNewHash(round, { gasLimit: gasEstimate, gasPrice: this.gasPrice }); + return repCycle.confirmNewHash(round, { gasLimit: gasEstimate, ...this.feeData }); } @@ -1463,18 +1463,8 @@ class ReputationMiner { } } - // Gas price should be a hex string - async setGasPrice(_gasPrice){ - if (!ethers.utils.isHexString(_gasPrice)){ - throw new Error("Passed gas price was not a hex string") - } - const passedPrice = ethers.BigNumber.from(_gasPrice); - const minimumPrice = ethers.BigNumber.from("1100000000"); - if (passedPrice.lt(minimumPrice)){ - this.gasPrice = minimumPrice.toHexString(); - } else { - this.gasPrice = _gasPrice; - } + async setFeeData(_feeData){ + this.feeData = _feeData; } async saveCurrentState() { diff --git a/packages/reputation-miner/ReputationMinerClient.js b/packages/reputation-miner/ReputationMinerClient.js index 22954dbe2a..8558c94b8a 100644 --- a/packages/reputation-miner/ReputationMinerClient.js +++ b/packages/reputation-miner/ReputationMinerClient.js @@ -4,7 +4,7 @@ const path = require("path"); const apicache = require("apicache") const ReputationMiner = require("./ReputationMiner"); -const { ConsoleAdapter, updateGasEstimate } = require("../package-utils"); +const { ConsoleAdapter, getFeeData } = require("../package-utils"); const minStake = ethers.BigNumber.from(10).pow(18).mul(2000); // eslint-disable-line prettier/prettier const MINUTE_IN_SECONDS = 60; @@ -442,8 +442,8 @@ class ReputationMinerClient { const canSubmit = await this._miner.submissionPossible(entryIndex); if (canSubmit) { this._adapter.log("⏰ Looks like it's time to submit an entry to the current cycle"); - const gasPrice = await updateGasEstimate("average", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); + const feeData = await getFeeData("average", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); await this.submitEntry(entryIndex); this.submissionIndex += 1; this.endDoBlockChecks(); @@ -494,8 +494,8 @@ class ReputationMinerClient { return; } } - const gasPrice = await updateGasEstimate("fast", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); + const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); this._adapter.log("Invalidating pseudo-opponent in dispute"); await repCycle.invalidateHash(round, oppIndex, {"gasPrice": this._miner.gasPrice}); @@ -509,8 +509,8 @@ class ReputationMinerClient { if (submission.jrhNLeaves.eq(0)) { const responsePossible = await repCycle.getResponsePossible(disputeStages.CONFIRM_JRH, entry.lastResponseTimestamp); if (responsePossible){ - const gasPrice = await updateGasEstimate("fast", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); + const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); this._adapter.log("Confirming JRH in dispute"); const tx = await this._miner.confirmJustificationRootHash(); await tx.wait(); @@ -524,9 +524,9 @@ class ReputationMinerClient { if (oppEntry.challengeStepCompleted.gte(entry.challengeStepCompleted)) { const responsePossible = await repCycle.getResponsePossible(disputeStages.BINARY_SEARCH_RESPONSE, entry.lastResponseTimestamp); if (responsePossible){ - const gasPrice = await updateGasEstimate("fast", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); - this._adapter.log("Responding to binary search in dispute"); + const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); + this._adapter.log("Responding to binary search in dispute"); const tx = await this._miner.respondToBinarySearchForChallenge(); await tx.wait(); } @@ -542,9 +542,9 @@ class ReputationMinerClient { { const responsePossible = await repCycle.getResponsePossible(disputeStages.BINARY_SEARCH_CONFIRM, entry.lastResponseTimestamp); if (responsePossible){ - const gasPrice = await updateGasEstimate("fast", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); - this._adapter.log("Confirming binary search in dispute"); + const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); + this._adapter.log("Confirming binary search in dispute"); const tx = await this._miner.confirmBinarySearchResult(); await tx.wait(); } @@ -560,9 +560,9 @@ class ReputationMinerClient { { const responsePossible = await repCycle.getResponsePossible(disputeStages.RESPOND_TO_CHALLENGE, entry.lastResponseTimestamp); if (responsePossible){ - const gasPrice = await updateGasEstimate("fast", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); - this._adapter.log("Responding to challenge in dispute"); + const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); + this._adapter.log("Responding to challenge in dispute"); const tx = await this._miner.respondToChallenge(); await tx.wait(); } @@ -578,9 +578,9 @@ class ReputationMinerClient { ); if (responsePossible) { // If so, invalidate them. - const gasPrice = await updateGasEstimate("fast", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); - this._adapter.log("Invalidating opponent in dispute"); + const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); + this._adapter.log("Invalidating opponent in dispute"); await repCycle.invalidateHash(round, oppIndex, {"gasPrice": this._miner.gasPrice}); this.endDoBlockChecks(); return; @@ -764,8 +764,8 @@ class ReputationMinerClient { // Confirm hash if possible const [round] = await this._miner.getMySubmissionRoundAndIndex(); if (round && round.gte(0)) { - const gasPrice = await updateGasEstimate("average", this.chainId, this._adapter); - await this._miner.setGasPrice(gasPrice); + const feeData = await getFeeData("average", this.chainId, this._adapter, this._miner.realProvider); + await this._miner.setFeeData(feeData); const confirmNewHashTx = await this._miner.confirmNewHash(); From 4818a629966e896b941dd77edd28ec8980dd7efd Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Thu, 5 Oct 2023 16:19:11 +0100 Subject: [PATCH 02/10] Make MTX broadcaster more gas(limit) efficient --- packages/metatransaction-broadcaster/Dockerfile | 2 +- .../metatransaction-broadcaster/MetatransactionBroadcaster.js | 4 ++++ test/packages/metaTransactionBroadcaster.js | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/metatransaction-broadcaster/Dockerfile b/packages/metatransaction-broadcaster/Dockerfile index 2ec973f8cf..dc8fabe731 100644 --- a/packages/metatransaction-broadcaster/Dockerfile +++ b/packages/metatransaction-broadcaster/Dockerfile @@ -3,7 +3,7 @@ COPY ./packages ./packages COPY ./package.json ./ COPY ./package-lock.json ./ COPY ./build ./build -RUN npm i +RUN npm ci RUN cd ./packages/metatransaction-broadcaster/ && npm i RUN cd ./packages/package-utils/ && npm i EXPOSE 3000 diff --git a/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js b/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js index 90a1c0059f..d89547849e 100644 --- a/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js +++ b/packages/metatransaction-broadcaster/MetatransactionBroadcaster.js @@ -336,6 +336,10 @@ class MetatransactionBroadcaster { try { gasEstimate = await estimateGas(...args); + if (ethers.BigNumber.from(args[args.length - 1].gasLimit).gt(gasEstimate.mul(11).div(10))) { + // eslint-disable-next-line + args[args.length - 1].gasLimit = gasEstimate.mul(11).div(10); + } } catch (err) { let reason; try { diff --git a/test/packages/metaTransactionBroadcaster.js b/test/packages/metaTransactionBroadcaster.js index 8f3a158af2..456c25bd0d 100644 --- a/test/packages/metaTransactionBroadcaster.js +++ b/test/packages/metaTransactionBroadcaster.js @@ -10,6 +10,7 @@ const axios = require("axios"); const { TruffleLoader } = require("../../packages/package-utils"); const { setupEtherRouter } = require("../../helpers/upgradable-contracts"); const { UINT256_MAX } = require("../../helpers/constants"); +const { web3GetTransaction } = require("../../helpers/test-helper"); const MetatransactionBroadcaster = require("../../packages/metatransaction-broadcaster/MetatransactionBroadcaster"); const { getMetaTransactionParameters, getPermitParameters, setupColony } = require("../../helpers/test-data-generator"); @@ -242,6 +243,9 @@ contract("Metatransaction broadcaster", (accounts) => { expect(balanceAccount1).to.eq.BN(1200000); const balanceAccount2 = await metaTxToken.balanceOf(colony.address); expect(balanceAccount2).to.eq.BN(300000); + + const tx = await web3GetTransaction(txHash); + expect(tx.gas).to.be.lt.BN(500000); }); it("valid transactions broadcast near-simultaneously are still mined", async function () { From 6593dfb45dd6832f856bf1aa3d1a97e35336d06c Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Wed, 13 Sep 2023 14:24:04 +0100 Subject: [PATCH 03/10] Page previous mining cycles in miner --- packages/reputation-miner/ReputationMiner.js | 16 +++++++++++++--- .../client-sync-functionality.js | 4 +++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index cd8e1256a5..99824b74ce 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -16,6 +16,8 @@ const minStake = ethers.BigNumber.from(10).pow(18).mul(2000); const DAY_IN_SECONDS = 60 * 60 * 24; +const BLOCK_PAGING_SIZE = 1000000; + class ReputationMiner { /** * Constructor for ReputationMiner @@ -1359,10 +1361,17 @@ class ReputationMiner { if (!blockNumber) { throw new Error("Block number not supplied to sync"); } - // Get the events + let events = []; + const latestBlockNumber = await this.realProvider.getBlockNumber(); + const filter = this.colonyNetwork.filters.ReputationMiningCycleComplete(null, null); - filter.fromBlock = blockNumber; - const events = await this.realProvider.getLogs(filter); + filter.toBlock = Math.max(blockNumber - 1, 0); + while (filter.toBlock !== latestBlockNumber) { + filter.fromBlock = filter.toBlock + 1; + filter.toBlock = Math.min(filter.fromBlock + BLOCK_PAGING_SIZE, latestBlockNumber); + const partialEvents = await this.realProvider.getLogs(filter); + events = events.concat(partialEvents); + } let localHash = await this.reputationTree.getRootHash(); let applyLogs = false; @@ -1428,6 +1437,7 @@ class ReputationMiner { // Some more cycles might have completed since we started syncing const lastEventBlock = events[events.length - 1].blockNumber filter.fromBlock = lastEventBlock; + filter.toBlock = "latest"; const sinceEvents = await this.realProvider.getLogs(filter); if (sinceEvents.length > 1){ console.log("Some more cycles have completed during the sync process. Continuing to sync...") diff --git a/test/reputation-system/client-sync-functionality.js b/test/reputation-system/client-sync-functionality.js index d9929f7bb0..4d40c87b4c 100644 --- a/test/reputation-system/client-sync-functionality.js +++ b/test/reputation-system/client-sync-functionality.js @@ -262,8 +262,10 @@ process.env.SOLIDITY_COVERAGE useJsTree: true, dbPath: fileName, }); + await reputationMiner3.initialise(colonyNetwork.address); - await reputationMiner3.sync("latest"); + const latestBlock = await currentBlock(); + await reputationMiner3.sync(parseInt(latestBlock.number, 10)); const loadedState = await reputationMiner3.getRootHash(); expect(loadedState).to.equal(currentState); From 2367cd88174ab41c673234e62da05faee40e775d Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Fri, 13 Oct 2023 10:52:04 +0100 Subject: [PATCH 04/10] Improve fallback plan when gas oracle fails --- packages/package-utils/getFeeData.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/package-utils/getFeeData.js b/packages/package-utils/getFeeData.js index b0a8e0aaf3..799f311ebb 100644 --- a/packages/package-utils/getFeeData.js +++ b/packages/package-utils/getFeeData.js @@ -24,7 +24,7 @@ const getFeeData = async function (_type, chainId, adapter, provider) { if (chainId === 100) { options.url = "https://blockscout.com/xdai/mainnet/api/v1/gas-price-oracle"; - defaultGasPrice = ethers.BigNumber.from(2000000000); + defaultGasPrice = ethers.BigNumber.from(10000000000); factor = 1; // This oracle presents the information slightly differently from ethgasstation. if (_type === "safeLow") { @@ -32,7 +32,7 @@ const getFeeData = async function (_type, chainId, adapter, provider) { } } else if (chainId === 1) { options.url = "https://ethgasstation.info/json/ethgasAPI.json"; - defaultGasPrice = ethers.BigNumber.from(2000000000); + defaultGasPrice = ethers.BigNumber.from(10000000000); factor = 10; } else { // We don't have an oracle, so just use the provided fee data @@ -75,10 +75,11 @@ const getFeeData = async function (_type, chainId, adapter, provider) { } } catch (err) { adapter.error(`Error during gas estimation: ${err}`); - feeData = { gasPrice: defaultGasPrice }; + adapter.error(`Using default fee data from node`); } } catch (err) { - adapter.error(err); + adapter.error(`Error getting fee data from provider: ${err}`); + adapter.error(`Using default static gas fee. Hopefully it's not too low...`); feeData = { gasPrice: defaultGasPrice }; } return feeData; From de4b9cdd23d77b8373a4ad61e44ee74c927b7e16 Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Fri, 13 Oct 2023 11:02:17 +0100 Subject: [PATCH 05/10] Page past mining cycles more smartly --- packages/reputation-miner/ReputationMiner.js | 62 ++++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index 99824b74ce..76ab8457d4 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -16,7 +16,7 @@ const minStake = ethers.BigNumber.from(10).pow(18).mul(2000); const DAY_IN_SECONDS = 60 * 60 * 24; -const BLOCK_PAGING_SIZE = 1000000; +const BLOCK_PAGING_SIZE = 25000; class ReputationMiner { /** @@ -1365,33 +1365,43 @@ class ReputationMiner { const latestBlockNumber = await this.realProvider.getBlockNumber(); const filter = this.colonyNetwork.filters.ReputationMiningCycleComplete(null, null); - filter.toBlock = Math.max(blockNumber - 1, 0); - while (filter.toBlock !== latestBlockNumber) { - filter.fromBlock = filter.toBlock + 1; - filter.toBlock = Math.min(filter.fromBlock + BLOCK_PAGING_SIZE, latestBlockNumber); - const partialEvents = await this.realProvider.getLogs(filter); - events = events.concat(partialEvents); - } + let syncFromIndex = -1; + let foundKnownState = false; + filter.fromBlock = latestBlockNumber + 1; + filter.toBlock = filter.fromBlock; let localHash = await this.reputationTree.getRootHash(); let applyLogs = false; - // Run through events backwards find the most recent one that we know... - let syncFromIndex = 0; - for (let i = events.length - 1 ; i >= 0 ; i -= 1){ - const event = events[i]; - const hash = event.data.slice(0, 66); - const nLeaves = ethers.BigNumber.from(`0x${event.data.slice(66, 130)}`); - // Do we have such a state? - const res = await this.queries.getReputationStateCount.get(hash, nLeaves.toString()); - if (res.n === 1){ - // We know that state! We can just sync from the next one... - syncFromIndex = i + 1; - await this.loadState(hash); - applyLogs = true; - break; + while (foundKnownState === false && filter.toBlock > blockNumber) { + filter.toBlock = filter.fromBlock - 1; + filter.fromBlock = Math.max(filter.toBlock - BLOCK_PAGING_SIZE + 1, blockNumber); + // console.log(filter); + const partialEvents = await this.realProvider.getLogs(filter); + events = events.concat(partialEvents.reverse()); + + // Run through events backwards find the most recent one that we know... + for (let i = 0 ; i < events.length ; i += 1){ + const event = events[i]; + const hash = event.data.slice(0, 66); + const nLeaves = ethers.BigNumber.from(`0x${event.data.slice(66, 130)}`); + // Do we have such a state? + const res = await this.queries.getReputationStateCount.get(hash, nLeaves.toString()); + if (res.n === 1){ + console.log("KNOWN") + // We know that state! We can just sync from the next one... + syncFromIndex = i - 1; + await this.loadState(hash); + applyLogs = true; + foundKnownState = true; + break; + } } } + if (syncFromIndex === -1 && !foundKnownState) { + syncFromIndex = events.length - 1; + } + // We're not going to apply the logs unless we're syncing from scratch (which is this if statement) // or we find a hash that we recognise as our current state, and we're going to sync from there (which // is the if statement at the end of the loop below @@ -1399,10 +1409,10 @@ class ReputationMiner { applyLogs = true; } - for (let i = syncFromIndex; i < events.length; i += 1) { - console.log(`Syncing mining cycle ${i + 1} of ${events.length}...`) + for (let i = syncFromIndex; i >= 0; i -= 1) { + console.log(`Syncing mining cycle ${syncFromIndex - i + 1} of ${syncFromIndex + 1}...`) const event = events[i]; - if (i === 0) { + if (i === events.length - 1 && !foundKnownState) { // If we are syncing from the very start of the reputation history, the block // before the very first 'ReputationMiningCycleComplete' does not have an // active reputation cycle. So we skip it if 'fromBlock' has not been judiciously @@ -1435,7 +1445,7 @@ class ReputationMiner { } // Some more cycles might have completed since we started syncing - const lastEventBlock = events[events.length - 1].blockNumber + const lastEventBlock = events[0].blockNumber filter.fromBlock = lastEventBlock; filter.toBlock = "latest"; const sinceEvents = await this.realProvider.getLogs(filter); From a2c619fc5405948492cff32ca72adf9bc2775511 Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Fri, 20 Oct 2023 14:44:56 +0100 Subject: [PATCH 06/10] First changes following review --- packages/reputation-miner/ReputationMiner.js | 7 ++++--- .../reputation-miner/ReputationMinerClient.js | 18 +++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index 76ab8457d4..9400508b97 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -1367,7 +1367,7 @@ class ReputationMiner { const filter = this.colonyNetwork.filters.ReputationMiningCycleComplete(null, null); let syncFromIndex = -1; let foundKnownState = false; - filter.fromBlock = latestBlockNumber + 1; + filter.fromBlock = latestBlockNumber + 1; // The +1 is to accommodate the first iteration of the loop filter.toBlock = filter.fromBlock; let localHash = await this.reputationTree.getRootHash(); let applyLogs = false; @@ -1375,7 +1375,7 @@ class ReputationMiner { while (foundKnownState === false && filter.toBlock > blockNumber) { filter.toBlock = filter.fromBlock - 1; filter.fromBlock = Math.max(filter.toBlock - BLOCK_PAGING_SIZE + 1, blockNumber); - // console.log(filter); + const partialEvents = await this.realProvider.getLogs(filter); events = events.concat(partialEvents.reverse()); @@ -1398,6 +1398,7 @@ class ReputationMiner { } } + // This means that we have not seen a known state, and so will have to sync from the start if (syncFromIndex === -1 && !foundKnownState) { syncFromIndex = events.length - 1; } @@ -1483,7 +1484,7 @@ class ReputationMiner { } } - async setFeeData(_feeData){ + setFeeData(_feeData){ this.feeData = _feeData; } diff --git a/packages/reputation-miner/ReputationMinerClient.js b/packages/reputation-miner/ReputationMinerClient.js index 8558c94b8a..f42248e9dc 100644 --- a/packages/reputation-miner/ReputationMinerClient.js +++ b/packages/reputation-miner/ReputationMinerClient.js @@ -443,7 +443,7 @@ class ReputationMinerClient { if (canSubmit) { this._adapter.log("⏰ Looks like it's time to submit an entry to the current cycle"); const feeData = await getFeeData("average", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); + this._miner.setFeeData(feeData); await this.submitEntry(entryIndex); this.submissionIndex += 1; this.endDoBlockChecks(); @@ -495,7 +495,7 @@ class ReputationMinerClient { } } const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); + this._miner.setFeeData(feeData); this._adapter.log("Invalidating pseudo-opponent in dispute"); await repCycle.invalidateHash(round, oppIndex, {"gasPrice": this._miner.gasPrice}); @@ -510,7 +510,7 @@ class ReputationMinerClient { const responsePossible = await repCycle.getResponsePossible(disputeStages.CONFIRM_JRH, entry.lastResponseTimestamp); if (responsePossible){ const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); + this._miner.setFeeData(feeData); this._adapter.log("Confirming JRH in dispute"); const tx = await this._miner.confirmJustificationRootHash(); await tx.wait(); @@ -525,8 +525,8 @@ class ReputationMinerClient { const responsePossible = await repCycle.getResponsePossible(disputeStages.BINARY_SEARCH_RESPONSE, entry.lastResponseTimestamp); if (responsePossible){ const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); - this._adapter.log("Responding to binary search in dispute"); + this._miner.setFeeData(feeData); + this._adapter.log("Responding to binary search in dispute"); const tx = await this._miner.respondToBinarySearchForChallenge(); await tx.wait(); } @@ -543,7 +543,7 @@ class ReputationMinerClient { const responsePossible = await repCycle.getResponsePossible(disputeStages.BINARY_SEARCH_CONFIRM, entry.lastResponseTimestamp); if (responsePossible){ const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); + this._miner.setFeeData(feeData); this._adapter.log("Confirming binary search in dispute"); const tx = await this._miner.confirmBinarySearchResult(); await tx.wait(); @@ -561,7 +561,7 @@ class ReputationMinerClient { const responsePossible = await repCycle.getResponsePossible(disputeStages.RESPOND_TO_CHALLENGE, entry.lastResponseTimestamp); if (responsePossible){ const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); + this._miner.setFeeData(feeData); this._adapter.log("Responding to challenge in dispute"); const tx = await this._miner.respondToChallenge(); await tx.wait(); @@ -579,7 +579,7 @@ class ReputationMinerClient { if (responsePossible) { // If so, invalidate them. const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); + this._miner.setFeeData(feeData); this._adapter.log("Invalidating opponent in dispute"); await repCycle.invalidateHash(round, oppIndex, {"gasPrice": this._miner.gasPrice}); this.endDoBlockChecks(); @@ -765,7 +765,7 @@ class ReputationMinerClient { const [round] = await this._miner.getMySubmissionRoundAndIndex(); if (round && round.gte(0)) { const feeData = await getFeeData("average", this.chainId, this._adapter, this._miner.realProvider); - await this._miner.setFeeData(feeData); + this._miner.setFeeData(feeData); const confirmNewHashTx = await this._miner.confirmNewHash(); From f9edd05f472c236ab96e316bf333fc2881163265 Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Mon, 23 Oct 2023 09:57:52 +0100 Subject: [PATCH 07/10] Fix package-lock.json --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c70a464d4d..6c0ebd628c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11339,7 +11339,7 @@ }, "node_modules/ethereumjs-abi": { "version": "0.6.8", - "resolved": "git+ssh://git@github.com/ethereumjs/ethereumjs-abi.git", + "resolved": "https://git@github.com/ethereumjs/ethereumjs-abi.git", "integrity": "sha512-qs8G5KwnIO/thOQjv1RvR/4oiTsy6IaCsN+ory5dbiqFXz8sd239aWJH0wmsVNPimL5X1KzQheUpi6xAo6FU4w==", "dev": true, "license": "MIT", @@ -31766,7 +31766,7 @@ }, "node_modules/truffle-hdwallet-provider/node_modules/websocket": { "version": "1.0.29", - "resolved": "git+ssh://git@github.com/web3-js/WebSocket-Node.git", + "resolved": "https://git@github.com/web3-js/WebSocket-Node.git", "integrity": "sha512-shhW6L+V8TtEtoA+mSzg67T+v2Lo5vt8eLvPKWZk7b6L4B7hduaDEbPxuz85bgl1a5CIHpNSMtob7VPBB+lAZQ==", "dev": true, "hasInstallScript": true, @@ -44957,7 +44957,7 @@ } }, "ethereumjs-abi": { - "version": "git+ssh://git@github.com/ethereumjs/ethereumjs-abi.git", + "version": "https://git@github.com/ethereumjs/ethereumjs-abi.git", "integrity": "sha512-qs8G5KwnIO/thOQjv1RvR/4oiTsy6IaCsN+ory5dbiqFXz8sd239aWJH0wmsVNPimL5X1KzQheUpi6xAo6FU4w==", "dev": true, "from": "ethereumjs-abi@^0.6.8", @@ -61966,7 +61966,7 @@ } }, "websocket": { - "version": "git+ssh://git@github.com/web3-js/WebSocket-Node.git", + "version": "https://git@github.com/web3-js/WebSocket-Node.git", "integrity": "sha512-shhW6L+V8TtEtoA+mSzg67T+v2Lo5vt8eLvPKWZk7b6L4B7hduaDEbPxuz85bgl1a5CIHpNSMtob7VPBB+lAZQ==", "dev": true, "from": "websocket@^1.0.28", From 81a0078ea5bc6b8d989acbeb32514f8c138c5f6e Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Wed, 25 Oct 2023 15:19:11 +0100 Subject: [PATCH 08/10] When gas price oracle down, still delete gasPrice from feeData --- packages/package-utils/getFeeData.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/package-utils/getFeeData.js b/packages/package-utils/getFeeData.js index 799f311ebb..354173bf43 100644 --- a/packages/package-utils/getFeeData.js +++ b/packages/package-utils/getFeeData.js @@ -48,6 +48,9 @@ const getFeeData = async function (_type, chainId, adapter, provider) { try { feeData = await provider.getFeeData(); delete feeData.lastBaseFeePerGas; + if (feeData.maxFeePerGas) { + delete feeData.gasPrice; + } // Update gas prices from whichever oracle try { const request = await axios.request(options); @@ -63,7 +66,6 @@ const getFeeData = async function (_type, chainId, adapter, provider) { // Increase the max fee per gas by the same amount (not the same ratio) feeData.maxFeePerGas = feeData.maxFeePerGas.add(newMaxPriorityFeePerGas).sub(feeData.maxPriorityFeePerGas); feeData.maxPriorityFeePerGas = newMaxPriorityFeePerGas; - delete feeData.gasPrice; return feeData; } From 5b7d30d106f39448c9b05edb05e574395bd1f351 Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Fri, 27 Oct 2023 12:20:34 +0100 Subject: [PATCH 09/10] Some abstraction in the miner --- packages/reputation-miner/ReputationMiner.js | 18 ++++++---- .../reputation-miner/ReputationMinerClient.js | 35 +++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index 9400508b97..7e1e1bd5d2 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -1382,15 +1382,11 @@ class ReputationMiner { // Run through events backwards find the most recent one that we know... for (let i = 0 ; i < events.length ; i += 1){ const event = events[i]; - const hash = event.data.slice(0, 66); - const nLeaves = ethers.BigNumber.from(`0x${event.data.slice(66, 130)}`); - // Do we have such a state? - const res = await this.queries.getReputationStateCount.get(hash, nLeaves.toString()); - if (res.n === 1){ - console.log("KNOWN") + if (await this.cycleCompleteEventIsKnownState(event)){ // We know that state! We can just sync from the next one... syncFromIndex = i - 1; - await this.loadState(hash); + const knownHash = event.data.slice(0, 66); + await this.loadState(knownHash); applyLogs = true; foundKnownState = true; break; @@ -1468,6 +1464,14 @@ class ReputationMiner { } } + async cycleCompleteEventIsKnownState(event) { + const hash = event.data.slice(0, 66); + const nLeaves = ethers.BigNumber.from(`0x${event.data.slice(66, 130)}`); + // Do we have such a state? + const res = await this.queries.getReputationStateCount.get(hash, nLeaves.toString()); + return res.n === 1; + } + async printCurrentState() { for (let i = 0; i < Object.keys(this.reputations).length; i += 1) { const key = Object.keys(this.reputations)[i]; diff --git a/packages/reputation-miner/ReputationMinerClient.js b/packages/reputation-miner/ReputationMinerClient.js index f42248e9dc..359b34b8e9 100644 --- a/packages/reputation-miner/ReputationMinerClient.js +++ b/packages/reputation-miner/ReputationMinerClient.js @@ -442,8 +442,7 @@ class ReputationMinerClient { const canSubmit = await this._miner.submissionPossible(entryIndex); if (canSubmit) { this._adapter.log("⏰ Looks like it's time to submit an entry to the current cycle"); - const feeData = await getFeeData("average", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); + await this.updateFeeData("average"); await this.submitEntry(entryIndex); this.submissionIndex += 1; this.endDoBlockChecks(); @@ -494,8 +493,7 @@ class ReputationMinerClient { return; } } - const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); + await this.updateFeeData("fast"); this._adapter.log("Invalidating pseudo-opponent in dispute"); await repCycle.invalidateHash(round, oppIndex, {"gasPrice": this._miner.gasPrice}); @@ -509,8 +507,7 @@ class ReputationMinerClient { if (submission.jrhNLeaves.eq(0)) { const responsePossible = await repCycle.getResponsePossible(disputeStages.CONFIRM_JRH, entry.lastResponseTimestamp); if (responsePossible){ - const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); + await this.updateFeeData("fast"); this._adapter.log("Confirming JRH in dispute"); const tx = await this._miner.confirmJustificationRootHash(); await tx.wait(); @@ -524,8 +521,7 @@ class ReputationMinerClient { if (oppEntry.challengeStepCompleted.gte(entry.challengeStepCompleted)) { const responsePossible = await repCycle.getResponsePossible(disputeStages.BINARY_SEARCH_RESPONSE, entry.lastResponseTimestamp); if (responsePossible){ - const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); + await this.updateFeeData("fast"); this._adapter.log("Responding to binary search in dispute"); const tx = await this._miner.respondToBinarySearchForChallenge(); await tx.wait(); @@ -542,9 +538,8 @@ class ReputationMinerClient { { const responsePossible = await repCycle.getResponsePossible(disputeStages.BINARY_SEARCH_CONFIRM, entry.lastResponseTimestamp); if (responsePossible){ - const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); - this._adapter.log("Confirming binary search in dispute"); + await this.updateFeeData("fast"); + this._adapter.log("Confirming binary search in dispute"); const tx = await this._miner.confirmBinarySearchResult(); await tx.wait(); } @@ -560,9 +555,8 @@ class ReputationMinerClient { { const responsePossible = await repCycle.getResponsePossible(disputeStages.RESPOND_TO_CHALLENGE, entry.lastResponseTimestamp); if (responsePossible){ - const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); - this._adapter.log("Responding to challenge in dispute"); + await this.updateFeeData("fast"); + this._adapter.log("Responding to challenge in dispute"); const tx = await this._miner.respondToChallenge(); await tx.wait(); } @@ -578,9 +572,8 @@ class ReputationMinerClient { ); if (responsePossible) { // If so, invalidate them. - const feeData = await getFeeData("fast", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); - this._adapter.log("Invalidating opponent in dispute"); + await this.updateFeeData("fast"); + this._adapter.log("Invalidating opponent in dispute"); await repCycle.invalidateHash(round, oppIndex, {"gasPrice": this._miner.gasPrice}); this.endDoBlockChecks(); return; @@ -764,8 +757,7 @@ class ReputationMinerClient { // Confirm hash if possible const [round] = await this._miner.getMySubmissionRoundAndIndex(); if (round && round.gte(0)) { - const feeData = await getFeeData("average", this.chainId, this._adapter, this._miner.realProvider); - this._miner.setFeeData(feeData); + await this.updateFeeData("average"); const confirmNewHashTx = await this._miner.confirmNewHash(); @@ -785,6 +777,11 @@ class ReputationMinerClient { this._miningCycleConfirmationOverdue = true; } + async updateFeeData(type) { + const feeData = await getFeeData(type, this.chainId, this._adapter, this._miner.realProvider); + this._miner.setFeeData(feeData); + } + } module.exports = ReputationMinerClient; From 100937e43418dde2b0260919c8a95a1ced6a1732 Mon Sep 17 00:00:00 2001 From: Daniel Kronovet Date: Wed, 1 Nov 2023 12:33:21 -0400 Subject: [PATCH 10/10] Refactor ReputationMiner for clarity --- packages/reputation-miner/ReputationMiner.js | 32 ++++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index 7e1e1bd5d2..b8dc3ec7ca 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -1361,34 +1361,40 @@ class ReputationMiner { if (!blockNumber) { throw new Error("Block number not supplied to sync"); } - let events = []; - const latestBlockNumber = await this.realProvider.getBlockNumber(); - const filter = this.colonyNetwork.filters.ReputationMiningCycleComplete(null, null); - let syncFromIndex = -1; - let foundKnownState = false; - filter.fromBlock = latestBlockNumber + 1; // The +1 is to accommodate the first iteration of the loop - filter.toBlock = filter.fromBlock; let localHash = await this.reputationTree.getRootHash(); + let foundKnownState = false; let applyLogs = false; + let syncFromIndex = -1; - while (foundKnownState === false && filter.toBlock > blockNumber) { + const latestBlockNumber = await this.realProvider.getBlockNumber(); + const filter = this.colonyNetwork.filters.ReputationMiningCycleComplete(null, null); + filter.fromBlock = latestBlockNumber + 1; // +1 to accommodate the first loop iteration + filter.toBlock = filter.fromBlock; + + let events = []; + while (filter.toBlock > blockNumber && !foundKnownState ) { + // Create a span of events up to BLOCK_PAGING_SIZE in length filter.toBlock = filter.fromBlock - 1; filter.fromBlock = Math.max(filter.toBlock - BLOCK_PAGING_SIZE + 1, blockNumber); + // Get new span of events, [fromBlock:oldset ... toBlock:newest] const partialEvents = await this.realProvider.getLogs(filter); + // Build a complete reversed array of events, [newest ... oldest] events = events.concat(partialEvents.reverse()); - // Run through events backwards find the most recent one that we know... - for (let i = 0 ; i < events.length ; i += 1){ + // Run through events to find the most recent one that we know... + for (let i = 0 ; i < events.length ; i += 1) { const event = events[i]; - if (await this.cycleCompleteEventIsKnownState(event)){ + if (await this.cycleCompleteEventIsKnownState(event)) { // We know that state! We can just sync from the next one... - syncFromIndex = i - 1; const knownHash = event.data.slice(0, 66); await this.loadState(knownHash); - applyLogs = true; + foundKnownState = true; + applyLogs = true; + syncFromIndex = i - 1; + break; } }