From 304920c7ec5d2a7144f9fa0c6ba7cdc0ec8e32e3 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 5 May 2025 17:55:01 -0300 Subject: [PATCH 1/7] fix: move heartbeat to the end of the loop --- src/bots/kleros-liquid.js | 134 ++++++++++++++++-------------- src/xdai-bots/x-kleros-liquid.js | 136 +++++++++++++++++-------------- 2 files changed, 144 insertions(+), 126 deletions(-) diff --git a/src/bots/kleros-liquid.js b/src/bots/kleros-liquid.js index c48e9e9..550df75 100644 --- a/src/bots/kleros-liquid.js +++ b/src/bots/kleros-liquid.js @@ -1,29 +1,21 @@ -const delay = require('delay') -const https = require('https') -const _klerosLiquid = require('../contracts/kleros-liquid.json') +const delay = require("delay"); +const https = require("https"); +const _klerosLiquid = require("../contracts/kleros-liquid.json"); -const DELAYED_STAKES_ITERATIONS = 15 +const DELAYED_STAKES_ITERATIONS = 15; module.exports = async (web3, batchedSend) => { // Instantiate the Kleros Liquid contract. const klerosLiquid = new web3.eth.Contract( _klerosLiquid.abi, process.env.KLEROS_LIQUID_CONTRACT_ADDRESS - ) - const PhaseEnum = Object.freeze({"staking": 0, "generating": 1, "drawing": 2}) + ); + const PhaseEnum = Object.freeze({ staking: 0, generating: 1, drawing: 2 }); // Keep track of executed disputes so we don't waste resources on them. - const executedDisputeIDs = {} + const executedDisputeIDs = {}; while (true) { - if (process.env.HEARTBEAT_URL) { - https - .get(process.env.HEARTBEAT_URL, () => {}) - .on("error", (e) => { - console.error("Failed to send heartbeat: %s", e); - }); - } - // Try to execute delayed set stakes if there are any. We check because this transaction still succeeds when there are not any and we don't want to waste gas in those cases. if ( (await klerosLiquid.methods.lastDelayedSetStake().call()) >= @@ -32,75 +24,75 @@ module.exports = async (web3, batchedSend) => { batchedSend({ args: [DELAYED_STAKES_ITERATIONS], method: klerosLiquid.methods.executeDelayedSetStakes, - to: klerosLiquid.options.address - }) + to: klerosLiquid.options.address, + }); // Loop over all disputes. try { - let disputeID = 0 + let disputeID = 0; while (true) { if (!executedDisputeIDs[disputeID]) { - const dispute = await klerosLiquid.methods.disputes(disputeID).call() + const dispute = await klerosLiquid.methods.disputes(disputeID).call(); const dispute2 = await klerosLiquid.methods .getDispute(disputeID) - .call() + .call(); const voteCounters = await Promise.all( // eslint-disable-next-line no-loop-func dispute2.votesLengths.map(async (numberOfVotes, i) => { - let voteCounter + let voteCounter; try { voteCounter = await klerosLiquid.methods .getVoteCounter(disputeID, i) - .call() + .call(); } catch (_) { // Look it up manually if numberOfChoices is too high for loop - let tied = true - let winningChoice = '0' - const _voteCounters = {} + let tied = true; + let winningChoice = "0"; + const _voteCounters = {}; for (let j = 0; j < numberOfVotes; j++) { const vote = await klerosLiquid.methods.getVote( disputeID, i, j - ) + ); if (vote.voted) { // increment vote count _voteCounters[vote.choice] = _voteCounters[vote.choice] ? _voteCounters[vote.choice] + 1 - : 1 + : 1; if (vote.choice === winningChoice) { - if (tied) tied = false // broke tie + if (tied) tied = false; // broke tie } else { const _winningChoiceVotes = - _voteCounters[winningChoice] || 0 + _voteCounters[winningChoice] || 0; if (_voteCounters[vote.choice] > _winningChoiceVotes) { - winningChoice = vote.choice - tied = false + winningChoice = vote.choice; + tied = false; } else if ( _voteCounters[vote.choice] === _winningChoiceVotes ) - tied = true + tied = true; } } } voteCounter = { tied, - winningChoice - } + winningChoice, + }; } - return voteCounter + return voteCounter; }) - ) + ); const notTieAndNoOneCoherent = voteCounters.map( - v => + (v) => !voteCounters[voteCounters.length - 1].tied && v.counts[voteCounters[voteCounters.length - 1].winningChoice] === - '0' - ) + "0" + ); if ( !dispute.ruled || dispute2.votesLengths.some( @@ -118,7 +110,7 @@ module.exports = async (web3, batchedSend) => { dispute.drawsInRound && { args: [disputeID, 15], method: klerosLiquid.methods.drawJurors, - to: klerosLiquid.options.address + to: klerosLiquid.options.address, }, ...dispute2.votesLengths.map( // eslint-disable-next-line no-loop-func @@ -127,53 +119,69 @@ module.exports = async (web3, batchedSend) => { Number(dispute2.repartitionsInEachRound[i]) && { args: [disputeID, i, 15], method: klerosLiquid.methods.execute, - to: klerosLiquid.options.address + to: klerosLiquid.options.address, } ), { args: [disputeID], method: klerosLiquid.methods.executeRuling, - to: klerosLiquid.options.address + to: klerosLiquid.options.address, }, { args: [disputeID], method: klerosLiquid.methods.passPeriod, - to: klerosLiquid.options.address - } - ].filter(t => t) - ) - else executedDisputeIDs[disputeID] = true // The dispute is finalized, cache it. + to: klerosLiquid.options.address, + }, + ].filter((t) => t) + ); + else executedDisputeIDs[disputeID] = true; // The dispute is finalized, cache it. } - disputeID++ + disputeID++; } } catch (_) {} // Reached the end of the disputes list. // Try to pass the phase. - let readyForNextPhase = false - const phase = await klerosLiquid.methods.phase().call() - const lastPhaseChange = await klerosLiquid.methods.lastPhaseChange().call() - const disputesWithoutJurors = await klerosLiquid.methods.disputesWithoutJurors().call() + let readyForNextPhase = false; + const phase = await klerosLiquid.methods.phase().call(); + const lastPhaseChange = await klerosLiquid.methods.lastPhaseChange().call(); + const disputesWithoutJurors = await klerosLiquid.methods + .disputesWithoutJurors() + .call(); if (phase == PhaseEnum.staking) { - const minStakingTime = await klerosLiquid.methods.minStakingTime().call() - if ((Date.now() - lastPhaseChange * 1000 >= minStakingTime * 1000) && disputesWithoutJurors > 0) { - readyForNextPhase = true + const minStakingTime = await klerosLiquid.methods.minStakingTime().call(); + if ( + Date.now() - lastPhaseChange * 1000 >= minStakingTime * 1000 && + disputesWithoutJurors > 0 + ) { + readyForNextPhase = true; } } else if (phase == PhaseEnum.generating) { - readyForNextPhase = true + readyForNextPhase = true; } else if (phase == PhaseEnum.drawing) { - const maxDrawingTime = await klerosLiquid.methods.maxDrawingTime().call() - if ((Date.now() - lastPhaseChange * 1000 >= maxDrawingTime * 1000) || disputesWithoutJurors == 0) { - readyForNextPhase = true + const maxDrawingTime = await klerosLiquid.methods.maxDrawingTime().call(); + if ( + Date.now() - lastPhaseChange * 1000 >= maxDrawingTime * 1000 || + disputesWithoutJurors == 0 + ) { + readyForNextPhase = true; } } if (readyForNextPhase) { batchedSend({ method: klerosLiquid.methods.passPhase, - to: klerosLiquid.options.address - }) + to: klerosLiquid.options.address, + }); + } + + if (process.env.HEARTBEAT_URL) { + https + .get(process.env.HEARTBEAT_URL, () => {}) + .on("error", (e) => { + console.error("Failed to send heartbeat: %s", e); + }); } - await delay(1000 * 60 * 10) // Every 10 minutes + await delay(1000 * 60 * 10); // Every 10 minutes } -} +}; diff --git a/src/xdai-bots/x-kleros-liquid.js b/src/xdai-bots/x-kleros-liquid.js index 303c695..2d6250c 100644 --- a/src/xdai-bots/x-kleros-liquid.js +++ b/src/xdai-bots/x-kleros-liquid.js @@ -1,35 +1,27 @@ -const delay = require('delay') -const https = require('https') -const _xKlerosLiquid = require('../contracts/x-kleros-liquid.json') -const _randomAuRa = require('../contracts/random-au-ra.json') +const delay = require("delay"); +const https = require("https"); +const _xKlerosLiquid = require("../contracts/x-kleros-liquid.json"); +const _randomAuRa = require("../contracts/random-au-ra.json"); -const DELAYED_STAKES_ITERATIONS = 15 +const DELAYED_STAKES_ITERATIONS = 15; module.exports = async (web3, batchedSend) => { // Instantiate the Kleros Liquid contract. const xKlerosLiquid = new web3.eth.Contract( _xKlerosLiquid.abi, process.env.XDAI_X_KLEROS_LIQUID_CONTRACT_ADDRESS - ) + ); const randomAuRa = new web3.eth.Contract( _randomAuRa.abi, await xKlerosLiquid.methods.RNGenerator().call() - ) + ); - const PhaseEnum = Object.freeze({ staking: 0, generating: 1, drawing: 2 }) + const PhaseEnum = Object.freeze({ staking: 0, generating: 1, drawing: 2 }); // Keep track of executed disputes so we don't waste resources on them. - const executedDisputeIDs = {} + const executedDisputeIDs = {}; while (true) { - if (process.env.HEARTBEAT_URL) { - https - .get(process.env.HEARTBEAT_URL, () => {}) - .on("error", (e) => { - console.error("Failed to send heartbeat: %s", e); - }); - } - // Try to execute delayed set stakes if there are any. We check because this transaction still succeeds when there are not any and we don't want to waste gas in those cases. if ( (await xKlerosLiquid.methods.lastDelayedSetStake().call()) >= @@ -38,76 +30,80 @@ module.exports = async (web3, batchedSend) => { batchedSend({ args: [DELAYED_STAKES_ITERATIONS], method: xKlerosLiquid.methods.executeDelayedSetStakes, - to: xKlerosLiquid.options.address - }) + to: xKlerosLiquid.options.address, + }); // Loop over all disputes. try { - const totalDisputes = Number(await xKlerosLiquid.methods.totalDisputes().call()) + const totalDisputes = Number( + await xKlerosLiquid.methods.totalDisputes().call() + ); - for(let disputeID = 0; disputeID < totalDisputes; disputeID++) { + for (let disputeID = 0; disputeID < totalDisputes; disputeID++) { if (!executedDisputeIDs[disputeID]) { - const dispute = await xKlerosLiquid.methods.disputes(disputeID).call() + const dispute = await xKlerosLiquid.methods + .disputes(disputeID) + .call(); const dispute2 = await xKlerosLiquid.methods .getDispute(disputeID) - .call() + .call(); const voteCounters = await Promise.all( // eslint-disable-next-line no-loop-func dispute2.votesLengths.map(async (numberOfVotes, i) => { - let voteCounter + let voteCounter; try { voteCounter = await xKlerosLiquid.methods .getVoteCounter(disputeID, i) - .call() + .call(); } catch (_) { // Look it up manually if numberOfChoices is too high for loop - let tied = true - let winningChoice = '0' - const _voteCounters = {} + let tied = true; + let winningChoice = "0"; + const _voteCounters = {}; for (let j = 0; j < numberOfVotes; j++) { const vote = await xKlerosLiquid.methods.getVote( disputeID, i, j - ) + ); if (vote.voted) { // increment vote count _voteCounters[vote.choice] = _voteCounters[vote.choice] ? _voteCounters[vote.choice] + 1 - : 1 + : 1; if (vote.choice === winningChoice) { - if (tied) tied = false // broke tie + if (tied) tied = false; // broke tie } else { const _winningChoiceVotes = - _voteCounters[winningChoice] || 0 + _voteCounters[winningChoice] || 0; if (_voteCounters[vote.choice] > _winningChoiceVotes) { - winningChoice = vote.choice - tied = false + winningChoice = vote.choice; + tied = false; } else if ( _voteCounters[vote.choice] === _winningChoiceVotes ) - tied = true + tied = true; } } } voteCounter = { tied, - winningChoice - } + winningChoice, + }; } - return voteCounter + return voteCounter; }) - ) + ); const notTieAndNoOneCoherent = voteCounters.map( - v => + (v) => !voteCounters[voteCounters.length - 1].tied && v.counts[voteCounters[voteCounters.length - 1].winningChoice] === - '0' - ) + "0" + ); if ( !dispute.ruled || dispute2.votesLengths.some( @@ -125,7 +121,7 @@ module.exports = async (web3, batchedSend) => { dispute.drawsInRound && { args: [disputeID, 15], method: xKlerosLiquid.methods.drawJurors, - to: xKlerosLiquid.options.address + to: xKlerosLiquid.options.address, }, ...dispute2.votesLengths.map( // eslint-disable-next-line no-loop-func @@ -134,23 +130,23 @@ module.exports = async (web3, batchedSend) => { Number(dispute2.repartitionsInEachRound[i]) && { args: [disputeID, i, 15], method: xKlerosLiquid.methods.execute, - to: xKlerosLiquid.options.address + to: xKlerosLiquid.options.address, } ), { args: [disputeID], method: xKlerosLiquid.methods.executeRuling, - to: xKlerosLiquid.options.address + to: xKlerosLiquid.options.address, }, { args: [disputeID], method: xKlerosLiquid.methods.passPeriod, - to: xKlerosLiquid.options.address - } - ].filter(t => t) - ) + to: xKlerosLiquid.options.address, + }, + ].filter((t) => t) + ); } else { - executedDisputeIDs[disputeID] = true + executedDisputeIDs[disputeID] = true; } // The dispute is finalized, cache it. } } @@ -159,42 +155,56 @@ module.exports = async (web3, batchedSend) => { } // Try to pass the phase. - let readyForNextPhase = false - const phase = await xKlerosLiquid.methods.phase().call() - const lastPhaseChange = await xKlerosLiquid.methods.lastPhaseChange().call() + let readyForNextPhase = false; + const phase = await xKlerosLiquid.methods.phase().call(); + const lastPhaseChange = await xKlerosLiquid.methods + .lastPhaseChange() + .call(); const disputesWithoutJurors = await xKlerosLiquid.methods .disputesWithoutJurors() - .call() + .call(); if (phase == PhaseEnum.staking) { - const minStakingTime = await xKlerosLiquid.methods.minStakingTime().call() + const minStakingTime = await xKlerosLiquid.methods + .minStakingTime() + .call(); if ( Date.now() - lastPhaseChange * 1000 >= minStakingTime * 1000 && disputesWithoutJurors > 0 ) { - readyForNextPhase = true + readyForNextPhase = true; } } else if (phase == PhaseEnum.generating) { const isCommitPhase = await randomAuRa.methods.isCommitPhase().call(); if (isCommitPhase) { - readyForNextPhase = true + readyForNextPhase = true; } } else if (phase == PhaseEnum.drawing) { - const maxDrawingTime = await xKlerosLiquid.methods.maxDrawingTime().call() + const maxDrawingTime = await xKlerosLiquid.methods + .maxDrawingTime() + .call(); if ( Date.now() - lastPhaseChange * 1000 >= maxDrawingTime * 1000 && disputesWithoutJurors == 0 ) { - readyForNextPhase = true + readyForNextPhase = true; } } if (readyForNextPhase) { batchedSend({ method: xKlerosLiquid.methods.passPhase, - to: xKlerosLiquid.options.address - }) + to: xKlerosLiquid.options.address, + }); + } + + if (process.env.HEARTBEAT_URL) { + https + .get(process.env.HEARTBEAT_URL, () => {}) + .on("error", (e) => { + console.error("Failed to send heartbeat: %s", e); + }); } - await delay(5 * 60 * 1000) // Every 5 minutes + await delay(5 * 60 * 1000); // Every 5 minutes } -} +}; From 7c06c20ce9c4f5ccdc0cf86501edec618d6ee629 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 5 May 2025 18:48:34 -0300 Subject: [PATCH 2/7] fix: add logs to the kleros-liquid bots --- src/bots/kleros-liquid.js | 21 +++++++++++++++------ src/xdai-bots/x-kleros-liquid.js | 25 ++++++++++++++++--------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/bots/kleros-liquid.js b/src/bots/kleros-liquid.js index 550df75..63342d0 100644 --- a/src/bots/kleros-liquid.js +++ b/src/bots/kleros-liquid.js @@ -16,17 +16,19 @@ module.exports = async (web3, batchedSend) => { const executedDisputeIDs = {}; while (true) { + console.log("Initializing klerosLiquid loop..."); // Try to execute delayed set stakes if there are any. We check because this transaction still succeeds when there are not any and we don't want to waste gas in those cases. if ( (await klerosLiquid.methods.lastDelayedSetStake().call()) >= (await klerosLiquid.methods.nextDelayedSetStake().call()) - ) + ) { + console.log("Executing delayed set stakes..."); batchedSend({ args: [DELAYED_STAKES_ITERATIONS], method: klerosLiquid.methods.executeDelayedSetStakes, to: klerosLiquid.options.address, }); - + } // Loop over all disputes. try { let disputeID = 0; @@ -100,8 +102,9 @@ module.exports = async (web3, batchedSend) => { Number(notTieAndNoOneCoherent[i] ? l : l * 2) !== Number(dispute2.repartitionsInEachRound[i]) ) - ) + ) { // The dispute is not finalized, try to call all of its callbacks. + console.log("Calling callbacks for dispute %s", disputeID); batchedSend( [ // We check if there are still pending draws because if there aren't any and the dispute is still in the evidence period, @@ -134,11 +137,16 @@ module.exports = async (web3, batchedSend) => { }, ].filter((t) => t) ); - else executedDisputeIDs[disputeID] = true; // The dispute is finalized, cache it. + } else { + executedDisputeIDs[disputeID] = true; // The dispute is finalized, cache it. + console.log("Dispute %s is finalized, caching it.", disputeID); + } } disputeID++; } - } catch (_) {} // Reached the end of the disputes list. + } catch (e) { + console.error("Failed to process disputes: ", e); + } // Reached the end of the disputes list. // Try to pass the phase. let readyForNextPhase = false; @@ -168,6 +176,7 @@ module.exports = async (web3, batchedSend) => { } if (readyForNextPhase) { + console.log("Passing phase..."); batchedSend({ method: klerosLiquid.methods.passPhase, to: klerosLiquid.options.address, @@ -181,7 +190,7 @@ module.exports = async (web3, batchedSend) => { console.error("Failed to send heartbeat: %s", e); }); } - + console.log("Waiting for 10 minutes for next loop..."); await delay(1000 * 60 * 10); // Every 10 minutes } }; diff --git a/src/xdai-bots/x-kleros-liquid.js b/src/xdai-bots/x-kleros-liquid.js index 2d6250c..d13a9d5 100644 --- a/src/xdai-bots/x-kleros-liquid.js +++ b/src/xdai-bots/x-kleros-liquid.js @@ -23,23 +23,25 @@ module.exports = async (web3, batchedSend) => { while (true) { // Try to execute delayed set stakes if there are any. We check because this transaction still succeeds when there are not any and we don't want to waste gas in those cases. + console.log("Initializing xKlerosLiquid loop..."); if ( (await xKlerosLiquid.methods.lastDelayedSetStake().call()) >= (await xKlerosLiquid.methods.nextDelayedSetStake().call()) - ) - batchedSend({ + ) { + console.log("Executing delayed set stakes..."); + await batchedSend({ args: [DELAYED_STAKES_ITERATIONS], method: xKlerosLiquid.methods.executeDelayedSetStakes, to: xKlerosLiquid.options.address, }); + } - // Loop over all disputes. try { const totalDisputes = Number( await xKlerosLiquid.methods.totalDisputes().call() ); - - for (let disputeID = 0; disputeID < totalDisputes; disputeID++) { + console.log("Looping over %s disputes...", totalDisputes); + for (let disputeID = 450; disputeID < totalDisputes; disputeID++) { if (!executedDisputeIDs[disputeID]) { const dispute = await xKlerosLiquid.methods .disputes(disputeID) @@ -47,6 +49,7 @@ module.exports = async (web3, batchedSend) => { const dispute2 = await xKlerosLiquid.methods .getDispute(disputeID) .call(); + const voteCounters = await Promise.all( // eslint-disable-next-line no-loop-func dispute2.votesLengths.map(async (numberOfVotes, i) => { @@ -113,7 +116,8 @@ module.exports = async (web3, batchedSend) => { ) ) { // The dispute is not finalized, try to call all of its callbacks. - batchedSend( + console.log("Calling callbacks for dispute %s", disputeID); + await batchedSend( [ // We check if there are still pending draws because if there aren't any and the dispute is still in the evidence period, // then the transaction would still succeed and we don't want to waste gas in those cases. @@ -148,10 +152,12 @@ module.exports = async (web3, batchedSend) => { } else { executedDisputeIDs[disputeID] = true; } // The dispute is finalized, cache it. + console.log("The dispute %s is finalized.", disputeID); } } - } catch { + } catch (e) { // do nothing... + console.error("Error while looping over disputes:", e); } // Try to pass the phase. @@ -191,7 +197,8 @@ module.exports = async (web3, batchedSend) => { } if (readyForNextPhase) { - batchedSend({ + console.log("Passing phase..."); + await batchedSend({ method: xKlerosLiquid.methods.passPhase, to: xKlerosLiquid.options.address, }); @@ -204,7 +211,7 @@ module.exports = async (web3, batchedSend) => { console.error("Failed to send heartbeat: %s", e); }); } - + console.log("xKlerosLiquid loop concluded. Awaiting 5 mins for next loop."); await delay(5 * 60 * 1000); // Every 5 minutes } }; From e852fc39db2c7caab4675b6951470ed9b455d7b7 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 5 May 2025 18:50:42 -0300 Subject: [PATCH 3/7] fix: wait a minute until restarting failed bot --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 1526132..fb05237 100644 --- a/src/index.js +++ b/src/index.js @@ -42,7 +42,7 @@ const run = async (bot, { providerUrl, batcherAddress, privateKey }) => { } catch (err) { console.error('Bot error: ', err) } - await delay(10000) // Wait 10 seconds before restarting failed bot. + await delay(60000); // Wait 60 seconds before restarting failed bot. } } From 2395f0f2cf011730a97aa5362e087d066e3877bd Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 5 May 2025 20:55:17 -0300 Subject: [PATCH 4/7] fix: await batchSend and avoid heartbeat if error --- src/bots/kleros-liquid.js | 10 ++++++---- src/xdai-bots/x-kleros-liquid.js | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/bots/kleros-liquid.js b/src/bots/kleros-liquid.js index 63342d0..91e8cc3 100644 --- a/src/bots/kleros-liquid.js +++ b/src/bots/kleros-liquid.js @@ -15,6 +15,7 @@ module.exports = async (web3, batchedSend) => { // Keep track of executed disputes so we don't waste resources on them. const executedDisputeIDs = {}; + let doHeartbeat = true; while (true) { console.log("Initializing klerosLiquid loop..."); // Try to execute delayed set stakes if there are any. We check because this transaction still succeeds when there are not any and we don't want to waste gas in those cases. @@ -23,7 +24,7 @@ module.exports = async (web3, batchedSend) => { (await klerosLiquid.methods.nextDelayedSetStake().call()) ) { console.log("Executing delayed set stakes..."); - batchedSend({ + await batchedSend({ args: [DELAYED_STAKES_ITERATIONS], method: klerosLiquid.methods.executeDelayedSetStakes, to: klerosLiquid.options.address, @@ -105,7 +106,7 @@ module.exports = async (web3, batchedSend) => { ) { // The dispute is not finalized, try to call all of its callbacks. console.log("Calling callbacks for dispute %s", disputeID); - batchedSend( + await batchedSend( [ // We check if there are still pending draws because if there aren't any and the dispute is still in the evidence period, // then the transaction would still succeed and we don't want to waste gas in those cases. @@ -146,6 +147,7 @@ module.exports = async (web3, batchedSend) => { } } catch (e) { console.error("Failed to process disputes: ", e); + doHeartbeat = false; } // Reached the end of the disputes list. // Try to pass the phase. @@ -177,13 +179,13 @@ module.exports = async (web3, batchedSend) => { if (readyForNextPhase) { console.log("Passing phase..."); - batchedSend({ + await batchedSend({ method: klerosLiquid.methods.passPhase, to: klerosLiquid.options.address, }); } - if (process.env.HEARTBEAT_URL) { + if (process.env.HEARTBEAT_URL && doHeartbeat) { https .get(process.env.HEARTBEAT_URL, () => {}) .on("error", (e) => { diff --git a/src/xdai-bots/x-kleros-liquid.js b/src/xdai-bots/x-kleros-liquid.js index d13a9d5..ffa8bea 100644 --- a/src/xdai-bots/x-kleros-liquid.js +++ b/src/xdai-bots/x-kleros-liquid.js @@ -20,7 +20,7 @@ module.exports = async (web3, batchedSend) => { // Keep track of executed disputes so we don't waste resources on them. const executedDisputeIDs = {}; - + let doHeartbeat = true; while (true) { // Try to execute delayed set stakes if there are any. We check because this transaction still succeeds when there are not any and we don't want to waste gas in those cases. console.log("Initializing xKlerosLiquid loop..."); @@ -41,7 +41,7 @@ module.exports = async (web3, batchedSend) => { await xKlerosLiquid.methods.totalDisputes().call() ); console.log("Looping over %s disputes...", totalDisputes); - for (let disputeID = 450; disputeID < totalDisputes; disputeID++) { + for (let disputeID = 0; disputeID < totalDisputes; disputeID++) { if (!executedDisputeIDs[disputeID]) { const dispute = await xKlerosLiquid.methods .disputes(disputeID) @@ -158,6 +158,7 @@ module.exports = async (web3, batchedSend) => { } catch (e) { // do nothing... console.error("Error while looping over disputes:", e); + doHeartbeat = false; } // Try to pass the phase. @@ -204,7 +205,7 @@ module.exports = async (web3, batchedSend) => { }); } - if (process.env.HEARTBEAT_URL) { + if (process.env.HEARTBEAT_URL && doHeartbeat) { https .get(process.env.HEARTBEAT_URL, () => {}) .on("error", (e) => { From 3eedb766949f82d0db1335a7044dc566d4d1fa42 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 5 May 2025 21:24:09 -0300 Subject: [PATCH 5/7] fix: gas logic The baseFee and Priority fee were not well defined. When the fee in gnosis were bellow 1gwei fails to create the tx --- src/utils/batched-send.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/utils/batched-send.js b/src/utils/batched-send.js index 12cd934..90f94e9 100644 --- a/src/utils/batched-send.js +++ b/src/utils/batched-send.js @@ -57,11 +57,14 @@ module.exports = ( ) ).then(_pendingBatches => (pendingBatches = _pendingBatches)) - const currentGasPrice = web3.utils.toBN(await web3.eth.getGasPrice()) - const maxGasPrice = !!process.env.GAS_PRICE_CEILING_WEI ? process.env.GAS_PRICE_CEILING_WEI : currentGasPrice - const gasPrice = currentGasPrice.gt(web3.utils.toBN(maxGasPrice)) - ? maxGasPrice - : currentGasPrice.toString() + const currentGasPrice = web3.utils.toBN(await web3.eth.getGasPrice()); + const maxGasPrice = !!process.env.GAS_PRICE_CEILING_WEI + ? web3.utils.toBN(process.env.GAS_PRICE_CEILING_WEI) + : currentGasPrice; + const maxPriorityFeePerGas = web3.utils.toBN(web3.utils.toWei("1", "gwei")); + const maxFeePerGas = currentGasPrice.gt(maxGasPrice) + ? maxGasPrice.add(maxPriorityFeePerGas).toString() + : currentGasPrice.add(maxPriorityFeePerGas).toString(); // Build data for the batch transaction using all the transactions in the new batch and all the transactions in previous pending batches. // We do this because if we have pending batches by the time a new batch arrives, it means that their gas prices were too low, so sending a new batch transaction with the same nonce @@ -88,7 +91,7 @@ module.exports = ( batch.targets, batch.values, batch.datas - ) + ); web3.eth .sendSignedTransaction( ( @@ -100,8 +103,8 @@ module.exports = ( batch.totalGas, to: transactionBatcher.options.address, value: batch.totalValue, - maxFeePerGas: gasPrice, - maxPriorityFeePerGas: web3.utils.toWei('1', 'gwei') + maxFeePerGas, + maxPriorityFeePerGas, }, privateKey ) From 6f3ba6e46ec8e8fb30e3f80ce1c5a5d0bd487824 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 5 May 2025 21:24:41 -0300 Subject: [PATCH 6/7] fix:logs when disputa callbacks are processed --- src/xdai-bots/x-kleros-liquid.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xdai-bots/x-kleros-liquid.js b/src/xdai-bots/x-kleros-liquid.js index ffa8bea..ed8d81e 100644 --- a/src/xdai-bots/x-kleros-liquid.js +++ b/src/xdai-bots/x-kleros-liquid.js @@ -149,10 +149,11 @@ module.exports = async (web3, batchedSend) => { }, ].filter((t) => t) ); + console.log("Callbacks for dispute %s processed.", disputeID); } else { executedDisputeIDs[disputeID] = true; + console.log("The dispute %s was already executed.", disputeID); } // The dispute is finalized, cache it. - console.log("The dispute %s is finalized.", disputeID); } } } catch (e) { From e0a08e460def7755f5763a6e2453b5eafb6dcaf9 Mon Sep 17 00:00:00 2001 From: kokialgo Date: Mon, 5 May 2025 21:58:26 -0300 Subject: [PATCH 7/7] fix: stop while if reach last dispute --- src/bots/kleros-liquid.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bots/kleros-liquid.js b/src/bots/kleros-liquid.js index 91e8cc3..374c16c 100644 --- a/src/bots/kleros-liquid.js +++ b/src/bots/kleros-liquid.js @@ -35,7 +35,13 @@ module.exports = async (web3, batchedSend) => { let disputeID = 0; while (true) { if (!executedDisputeIDs[disputeID]) { - const dispute = await klerosLiquid.methods.disputes(disputeID).call(); + let dispute; + try { + dispute = await klerosLiquid.methods.disputes(disputeID).call(); + } catch (_) { + //console.log(e); + break; + } const dispute2 = await klerosLiquid.methods .getDispute(disputeID) .call();