142 lines
4.9 KiB
Solidity
142 lines
4.9 KiB
Solidity
// SPDX-License-Identifier: Unlicense
|
|
pragma solidity ^0.8.24;
|
|
|
|
import "./core/DAO.sol";
|
|
import "./Availability.sol";
|
|
|
|
contract Rollup is Availability {
|
|
struct BatchItem {
|
|
address sender;
|
|
address worker;
|
|
uint stakeAmount;
|
|
uint fee;
|
|
string postId;
|
|
}
|
|
|
|
mapping(uint => BatchItem) public items;
|
|
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(
|
|
address author,
|
|
uint stakeAmount,
|
|
string calldata postId
|
|
) public payable {
|
|
BatchItem storage item = items[itemCount++];
|
|
item.sender = msg.sender;
|
|
item.worker = author;
|
|
item.stakeAmount = stakeAmount;
|
|
item.fee = msg.value;
|
|
item.postId = postId;
|
|
emit BatchItemAdded(postId, item.sender, item.fee);
|
|
}
|
|
|
|
/// To be called by the currently assigned batch worker,
|
|
/// If no batch worker has been assigned this may be called by anybody,
|
|
/// but it will only succeed if it is able to assign a new worker.
|
|
function submitBatch(
|
|
string calldata batchPostId,
|
|
string[] calldata batchItems,
|
|
uint poolDuration
|
|
) public returns (uint poolIndex) {
|
|
if (batchWorker != address(0)) {
|
|
require(
|
|
msg.sender == batchWorker,
|
|
"Batch result must be submitted by current batch worker"
|
|
);
|
|
}
|
|
require(batchItems.length <= itemCount, "Batch size too large");
|
|
// Make sure all batch items match
|
|
for (uint i = 0; i < batchItems.length; i++) {
|
|
require(
|
|
keccak256(bytes(batchItems[i])) ==
|
|
keccak256(bytes(items[i].postId)),
|
|
"Batch item mismatch"
|
|
);
|
|
}
|
|
// initiate a validation pool for this batch
|
|
uint fee;
|
|
for (uint i = 0; i < batchItems.length; i++) {
|
|
fee += items[i].fee;
|
|
}
|
|
poolIndex = dao.initiateValidationPool{value: fee}(
|
|
batchPostId,
|
|
poolDuration,
|
|
[uint256(1), uint256(3)],
|
|
[uint256(1), uint256(2)],
|
|
100,
|
|
true,
|
|
false,
|
|
""
|
|
);
|
|
// Include all the availability stakes from the batched work
|
|
for (uint i = 0; i < batchItems.length; i++) {
|
|
dao.delegatedStakeOnValidationPool(
|
|
poolIndex,
|
|
items[i].worker,
|
|
items[i].stakeAmount,
|
|
true
|
|
);
|
|
}
|
|
// Include availability stakes from the batch worker
|
|
if (batchWorker != address(0)) {
|
|
dao.delegatedStakeOnValidationPool(
|
|
poolIndex,
|
|
batchWorker,
|
|
stakes[batchWorkerStakeIndex].amount,
|
|
true
|
|
);
|
|
}
|
|
if (batchItems.length < itemCount) {
|
|
// Some items were added after this batch was computed.
|
|
// Keep them in the queue to be included in the next batch.
|
|
for (uint i = 0; i < itemCount - batchItems.length; i++) {
|
|
items[i] = items[batchItems.length + i];
|
|
}
|
|
itemCount = itemCount - batchItems.length;
|
|
} else {
|
|
// Reset item count so we can start the next batch
|
|
itemCount = 0;
|
|
}
|
|
// 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 {
|
|
// TODO: Grace period after the current batch is due and before the worker can be replaced
|
|
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: Submit a validation pool targeting a null post, and send the worker's availability stake
|
|
// This gives the DAO an opportunity to police the failed work
|
|
// Select a new batch worker
|
|
batchWorkerStakeIndex = assignWork();
|
|
batchWorker = stakes[batchWorkerStakeIndex].worker;
|
|
}
|
|
}
|