From f7dcf0ec5e03edf64b8703dce3044d41814fc143 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Thu, 9 May 2024 11:47:05 -0500 Subject: [PATCH] rollup: use time rather than count for interval; add resetBatchWorker method --- README.md | 3 +- .../src/event-handlers/rollup/batch-worker.js | 5 +++ backend/src/event-handlers/rollup/config.js | 4 +-- .../rollup/matrix-pools/evaluate.js | 9 +++--- ethereum/.env.example | 3 +- ethereum/contracts/Rollup.sol | 31 +++++++++++++++++-- ethereum/scripts/deploy.js | 5 ++- ethereum/scripts/util/deploy-dao-contract.js | 4 +-- 8 files changed, 50 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 2c5fdb8..b0ae5c0 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ Our work contract normally triggeres a validation pool when the customer submits The `Rollup` contract itself uses the `Availability` contract to assign a batch worker. This worker is responsible for making sure off-chain pools are conducted. -When ready, the worker submits the current batch on-chain by creating a batch post and a validation pool targeting the batch post. +When ready, the worker submits the current batch on-chain by calling `DAO.addPost()` to create a batch post and then calling `Rollup.submitBatch()`, which initiates a validation pool targeting the batch post. + ```mermaid sequenceDiagram participant client as Staking client diff --git a/backend/src/event-handlers/rollup/batch-worker.js b/backend/src/event-handlers/rollup/batch-worker.js index 39cb269..ffa3607 100644 --- a/backend/src/event-handlers/rollup/batch-worker.js +++ b/backend/src/event-handlers/rollup/batch-worker.js @@ -1,6 +1,7 @@ const { rollup, wallet } = require('../../util/contracts'); let batchWorker; +let batchStart; const getCurrentBatchWorker = () => batchWorker; @@ -11,6 +12,7 @@ const initializeBatchWorker = async () => { rollup.on('BatchWorkerAssigned', async (batchWorker_) => { batchWorker = batchWorker_; + batchStart = new Date(); console.log('Batch worker assigned:', batchWorker); if (batchWorker === await wallet.getAddress()) { console.log('This instance is the new batch worker'); @@ -22,8 +24,11 @@ const setBatchWorker = (batchWorker_) => { batchWorker = batchWorker_; }; +const getBatchAge = (new Date() - batchStart) / 1000; + module.exports = { getCurrentBatchWorker, initializeBatchWorker, setBatchWorker, + getBatchAge, }; diff --git a/backend/src/event-handlers/rollup/config.js b/backend/src/event-handlers/rollup/config.js index fe1e207..103e995 100644 --- a/backend/src/event-handlers/rollup/config.js +++ b/backend/src/event-handlers/rollup/config.js @@ -1,9 +1,9 @@ const { - ROLLUP_BATCH_SIZE, ROLLUP_AVAILABILITY_STAKE_DURATION, + ROLLUP_INTERVAL, } = process.env; module.exports = { - rollupBatchSize: ROLLUP_BATCH_SIZE || 10, + rollupInterval: ROLLUP_INTERVAL, availabilityStakeDuration: ROLLUP_AVAILABILITY_STAKE_DURATION || 600, }; diff --git a/backend/src/event-handlers/rollup/matrix-pools/evaluate.js b/backend/src/event-handlers/rollup/matrix-pools/evaluate.js index 8b85635..07317bc 100644 --- a/backend/src/event-handlers/rollup/matrix-pools/evaluate.js +++ b/backend/src/event-handlers/rollup/matrix-pools/evaluate.js @@ -2,9 +2,9 @@ const { sendMatrixEvent } = require('../../../matrix-bot'); const { wallet } = require('../../../util/contracts'); const { matrixPools } = require('../../../util/db'); const { addBatchItem, getBatchItems } = require('../batch-items'); -const { getCurrentBatchWorker } = require('../batch-worker'); +const { getCurrentBatchWorker, getBatchAge } = require('../batch-worker'); const computeMatrixPoolResult = require('./compute-result'); -const { rollupBatchSize } = require('../config'); +const { rollupInterval } = require('../config'); const submitRollup = require('../submit-rollup'); const { stakeRollupAvailability } = require('../utils'); @@ -28,9 +28,10 @@ const evaluateMatrixPoolOutcome = async (postId) => { } else if (batchWorker === await wallet.getAddress()) { // If we are the batch worker, we should wait an appropriate amout of time / // number of matrix pools before submitting a batch. + const batchAge = getBatchAge(); const batchItems = getBatchItems(); - if (batchItems.length === rollupBatchSize) { - console.log(`Batch size = ${batchItems.length}. Submitting batch.`); + if (batchAge > rollupInterval && batchItems.length) { + console.log(`Batch age = ${batchAge}, size = ${batchItems.length}. Submitting batch.`); submitBatch = true; } } diff --git a/ethereum/.env.example b/ethereum/.env.example index 1fe8104..3f43649 100644 --- a/ethereum/.env.example +++ b/ethereum/.env.example @@ -6,4 +6,5 @@ MAINNET_PRIVATE_KEY= SEED_PHRASE= ETHERSCAN_API_KEY= WORK1_PRICE="0.001" -ONBOARDING_PRICE="0.001" \ No newline at end of file +ONBOARDING_PRICE="0.001" +ROLLUP_INTERVAL=120 \ No newline at end of file diff --git a/ethereum/contracts/Rollup.sol b/ethereum/contracts/Rollup.sol index d9e3752..f94c566 100644 --- a/ethereum/contracts/Rollup.sol +++ b/ethereum/contracts/Rollup.sol @@ -5,8 +5,6 @@ import "./core/DAO.sol"; import "./Availability.sol"; contract Rollup is Availability { - constructor(DAO dao) Availability(dao) {} - struct BatchItem { address sender; address worker; @@ -19,10 +17,18 @@ contract Rollup is Availability { uint public itemCount; address public batchWorker; uint batchWorkerStakeIndex; + uint public immutable batchInterval; + uint public batchStart; + uint lastWorkerReset; + uint constant minResetInterval = 120; event BatchItemAdded(string postId, address sender, uint fee); event BatchWorkerAssigned(address batchWorker); + constructor(DAO dao, uint batchInterval_) Availability(dao) { + batchInterval = batchInterval_; + } + /// Instead of initiating a validation pool, call this method to include /// the stakes and fee in the next batch validation pool function addItem( @@ -106,9 +112,28 @@ contract Rollup is Availability { // Reset item count so we can start the next batch itemCount = 0; } - // Select the next worker + // Select the next batch worker batchWorkerStakeIndex = assignWork(); batchWorker = stakes[batchWorkerStakeIndex].worker; + batchStart = block.timestamp; emit BatchWorkerAssigned(batchWorker); } + + /// If the batch worker fails to submit the batch, a new batch worker may be selected + function resetBatchWorker() public { + require( + block.timestamp - batchStart > batchInterval, + "Current batch interval has not yet elapsed" + ); + require(itemCount > 0, "Current batch is empty"); + require( + lastWorkerReset == 0 || + block.timestamp - lastWorkerReset >= minResetInterval, + "Mininum reset interval has not elapsed since last batch worker reset" + ); + // TODO: Should the current batch worker's availability stakes be forfeit? + // Select a new batch worker + batchWorkerStakeIndex = assignWork(); + batchWorker = stakes[batchWorkerStakeIndex].worker; + } } diff --git a/ethereum/scripts/deploy.js b/ethereum/scripts/deploy.js index cf8ea90..c14b6b4 100644 --- a/ethereum/scripts/deploy.js +++ b/ethereum/scripts/deploy.js @@ -1,11 +1,14 @@ +require('dotenv').config(); const deployDAOContract = require('./util/deploy-dao-contract'); const deployWorkContract = require('./util/deploy-work-contract'); const deployRollableWorkContract = require('./util/deploy-rollable-work-contract'); const deployCoreContracts = require('./util/deploy-core-contracts'); +const { ROLLUP_INTERVAL } = process.env; + async function main() { await deployCoreContracts(); - await deployDAOContract('Rollup'); + await deployDAOContract('Rollup', [ROLLUP_INTERVAL]); await deployDAOContract('Proposals'); await deployWorkContract('Work1'); await deployWorkContract('Onboarding'); diff --git a/ethereum/scripts/util/deploy-dao-contract.js b/ethereum/scripts/util/deploy-dao-contract.js index 1cdbea8..0fd6076 100644 --- a/ethereum/scripts/util/deploy-dao-contract.js +++ b/ethereum/scripts/util/deploy-dao-contract.js @@ -6,8 +6,8 @@ require('dotenv').config(); const network = process.env.HARDHAT_NETWORK; -const deployDAOContract = async (name) => { - await deployContract(name, [contractAddresses[network].DAO]); +const deployDAOContract = async (name, args = []) => { + await deployContract(name, [contractAddresses[network].DAO, ...args]); }; module.exports = deployDAOContract;