From 0bc04b83d21a6b13e7ccaa2d38a890b3e2fd00b7 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Mon, 13 May 2024 16:02:40 +0700 Subject: [PATCH 01/13] Draft of validating --- .vscode/settings.json | 3 + contracts/WorkerHub.sol | 186 +++++++++++++++++++++++- contracts/interfaces/IWorkerHub.sol | 15 ++ contracts/storages/WorkerHubStorage.sol | 12 +- 4 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c1b0617 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.enableFiletypes": ["solidity"] +} diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index ec0b644..f45088c 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -19,11 +19,16 @@ ReentrancyGuardUpgradeable { using Random for Random.Randomizer; using Set for Set.AddressSet; using Set for Set.Uint256Set; + using DoubleEndedQueue for Bytes32Deque; string constant private VERSION = "v0.0.1"; uint256 constant private PERCENTAGE_DENOMINATOR = 100_00; uint256 constant private BLOCK_PER_YEAR = 365 days / 2; // 2s per block + modifier onlyMiner { + require(msg.sender == ) + } + receive() external payable {} function initialize( @@ -405,6 +410,7 @@ ReentrancyGuardUpgradeable { function joinForValidating() external whenNotPaused { _updateEpoch(); + Worker storage validator = miners[msg.sender]; if (validator.tier == 0) revert NotRegistered(); if (block.timestamp < validator.activeTime) revert ValidatorInDeactivationTime(); @@ -548,7 +554,7 @@ ReentrancyGuardUpgradeable { } - function submitSolution(uint256 _assigmentId, bytes calldata _data) public virtual whenNotPaused { + function submitSolution(uint256 _assignmentId, bytes calldata _data) public virtual whenNotPaused { _updateEpoch(); address _msgSender = msg.sender; @@ -560,6 +566,10 @@ ReentrancyGuardUpgradeable { Assignment memory clonedAssignments = assignments[_assigmentId]; + + Assignment memory clonedAssignments = assignments[_assignmentId]; + + // check msgSender is miner if (_msgSender != clonedAssignments.worker) revert Unauthorized(); if (clonedAssignments.output.length != 0) revert AlreadySubmitted(); @@ -582,9 +592,9 @@ ReentrancyGuardUpgradeable { Inference storage inference = inferences[clonedAssignments.inferenceId]; - assignments[_assigmentId].output = _data; //Record the solution + assignments[_assignmentId].output = _data; //Record the solution inference.status = InferenceStatus.Solved; - inference.assignments.push(_assigmentId); + inference.assignments.push(_assignmentId); if (inference.assignments.length == 1) { uint256 fee = clonedInference.value * feePercentage / PERCENTAGE_DENOMINATOR; @@ -596,16 +606,184 @@ ReentrancyGuardUpgradeable { emit InferenceStatusUpdate(clonedAssignments.inferenceId, InferenceStatus.Solved); } - emit SolutionSubmission(_msgSender, _assigmentId); + emit SolutionSubmission(_msgSender, _assignmentId); } function _handleDisputeSuccess(uint256 _inferId) internal { // TODO } + //Check whether a worker is available (It had been joined) + function _checkAvailableWorker() internal { + if (!validatorAddresses.hasValue(msg.sender)) { + if (!minerAddresses.hasValue(msg.sender)) revert ("Invalid miner"); + if (!minerAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); + } + if (!validatorAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); + } function disputeInfer(uint256 _assignmentId) public virtual { + _updateEpoch(); + _checkAvailableWorker(); // TODO + address _msgSender = msg.sender; + + // check msgSender is validator + // if (!validatorAddresses.hasValue(msg.sender)) revert("Invalid validator"); + // if (!validatorAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); + + Assignment memory clonedAssignment = assignments[_assignmentId]; + Inference memory clonedInference = inferences[clonedAssignment.inferenceId]; + + //check this assignment has been disputed + if (clonedAssignment.output.length == 0) revert("Assignment is not submitted"); + if (clonedInference.status != InferenceStatus.Solved) revert InvalidInferenceStatus(); + + // check disputing time expire + uint40 validatingExpiredAt = uint40(clonedInference.expiredAt + validatingTimeLimit); + uint40 disputingExpiredAt = uint40(clonedInference.expiredAt + validatingTimeLimit); + + if (block.timestamp < clonedInference.expiredAt) revert ("The validating time has not yet arrived"); + if (validatingExpiredAt < block.timestamp) revert ("Exceeding the validating period"); + + if(disputedAssignmentIds.hasValue(_assignmentId)) revert ("Assignment already disputed"); + + // validatorDisputed[_msgSender][_assignmentId] = true; + disputedAssignmentIds.insert(_assignmentId); + disputedAssignmentsOf[msg.sender].insert(_assignmentId) + + DisputedAssignment storage disAssignment = disputedAssignments[_assignmentId]; + disAssignment.inferenceId = clonedAssignment.inferenceId; + disAssignment.creator = msg.sender; + disAssignment.totalValidator = validators.length; + disAssignment.validatingExpireAt = validatingExpiredAt; + disAssignment.disputingExpireAt = disputingExpiredAt; + + //inference + Inference storage inference = inferences[clonedAssignment.inferenceId]; + inference.disputingAddress = msg.sender; + inference.status = InferenceStatus.Disputing; + + //disputing queue + DisputingQueueElement memory disputingEl = DisputingQueueElement(_assignmentId, disputingExpiredAt); + bytes32 encodedEl = bytes32(abi.encode(pair)); + disputingQueue.pushBack(encodedEl); + } + + function upvoteDispute(uint256 _assignmentId) public virtual { + _updateEpoch(); + + // check msgSender is available for validating (it had been joined) + if (!validatorAddresses.hasValue(msg.sender)) revert("Invalid validator"); + if (!validatorAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); + + Assignment memory clonedAssignment = assignments[_assignmentId]; + Inference memory clonedInference = inferences[clonedAssignment.inferenceId]; + DisputedAssignment memory clonedDisputedAssignment = disputedAssignments[_assignmentId]; + + //check this assignment has been disputed + if (clonedAssignment.output.length == 0) revert("Assignment is not submitted"); + if (clonedInference.status != InferenceStatus.Disputing) revert InvalidInferenceStatus(); + + // check disputing time expire + uint40 validatingExpiredAt = clonedDisputedAssignment.validatingExpireAt; + uint40 disputingExpiredAt = clonedDisputedAssignment.disputingExpireAt;; + + if (block.timestamp < validatingExpiredAt) revert ("The disputing time has not yet arrived"); + if (disputingExpiredAt < block.timestamp) revert ("Exceeding the disputing period"); + + if (!disputedAssignmentIds.hasValue(_assignmentId)) revert ("Assignment is not disputed"); + if (votersOf[_assignmentId].hasValue(msg.sender)) revert("Validator already voted"); + + //Handle + clonedAssignment.disapprovalCount++; + + disputedAssignmentsOf[msg.sender].insert(_assignmentId); + votersOf[_assignmentId].insert(msg.sender); + } + + function _resolveDispute() internal { + while (true) { + bytes32 head = disputingQueue.popFront(); + DisputingQueueElement memory disputingEl = abi.decode(head, (DisputingQueueElement)); + uint256 assignmentId = disputingEl.id; + + Assignment memory assignment = assignments[assignmentId]; + + if (block.timestamp < disputingEl.expiredAt) { + disputingQueue.pushFront(head); + break; + } else { + uint8 disapprovalCount = votersOf[assignmentId].length; + DisputedAssignment storage disAssignment = disputedAssignments[assignmentId]; + + Assignment storage assignment = assignments[assignmentId]; + assignment.disapprovalCount = disapprovalCount; + Inference storage inference = inferences[assignment.inferenceId]; + + if (disapprovalCount * 3 < disAssignment.totalValidator * 2 + 3) { // total voted >= 2/3 validator + 1 + //inference + inference.status = InferenceStatus.Solved; + disAssignment.isValid = false; //Dispute is not valid, The miner is honest + + //slashing validator + _slashValidator(assignment.worker); + + } else { + inference.status = InferenceStatus.Killed; + disAssignment.isValid = true; //Dispute is valid, The miner is dishonest + + //slashing miner + _slashMiner(assignment.worker) + } + + emit ResolveDispute(assignmentId, inference.status); + } + } + } + + function _slashMiner(address _miner) internal { + Worker storage miner = miners[_miner]; + + if (!minerAddresses.hasValue(_miner)) revert ("Miner does not exist"); + + address modelAddress = miner.modelAddress; + + // Remove miner from available miner + if (minerAddressesByModel[modelAddress].hasValue(_miner)) { + minerAddressesByModel[modelAddress].erase(_miner); + minerAddresses.erase(_miner); + } + + // Set the time miner can join again + miner.activeTime = block.timestamp + slashingMinerTimeLimit; + uint256 fine = miner.stake * 5 / 100; // Fine = stake * 5% + miner.stake -= fine; + + TransferHelper.safeTransferNative(treasury, fine); + + emit SlashingMiner(_miner, miner.activeTime, fine); + } + + function _slashValidator(address _validator) internal { + Worker storage validator = validators[_validator]; + + if (!validatorAddresses.hasValue(_validator)) revert ("Validator does not exist"); + + address modelAddress = validator.modelAddress; + + if (validatorAddressesByModel[modelAddress].hasValue(_miner)) { + validatorAddressesByModel[modelAddress].erase(_validator); + validatorAddresses.erase(_validator); + } + + validator.activeTime = block.timestamp + slashingValidatorTimeLimit; + uint256 fine = validator.stake * 5 / 100; + validator.stake -= fine; + + TransferHelper.safeTransferNative(treasury, fine); + + emit ValidatorSlashed(_validator, validator.activeTime, fine); } function slashMiner(address _miner, bool _isFined) public virtual onlyOwner { diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 2e53aa8..c7374c9 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -84,6 +84,21 @@ interface IWorkerHub is IInferable { address creator; } + struct DisputedAssignment { + uint256 inferenceId; + address creator; + uint16 totalValidator; + uint16 votedValidator; + bool isValid; + uint40 validatingExpireAt; + uint40 disputingExpireAt; + } + + struct DisputingQueueElement { + uint256 id; + uint40 expiredAt; + } + struct UnstakeRequest { uint256 stake; uint40 unlockAt; diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index c53f376..48a0675 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; - +import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; import {IWorkerHub} from "../interfaces/IWorkerHub.sol"; import {Random} from "../lib/Random.sol"; @@ -30,8 +30,14 @@ abstract contract WorkerHubStorage is IWorkerHub { mapping(uint256 => Assignment) public assignments; mapping(address => Set.Uint256Set) internal assignmentsByMiner; mapping(uint256 => Set.Uint256Set) internal assignmentsByInference; - - mapping(address => mapping(uint256 => bool)) public validatorDisputed; + + //Dispute structures + Set.Uint256Set internal disputedAssignmentIds; + DoubleEndedQueue.Bytes32Deque disputingQueue; + mapping(uint256 => DisputedAssignment) internal disputedAssignments; // assignmentId => DisputedAssignment + mapping(address => Set.Uint256Set) disputedAssignmentsOf; //voter's address => disputed assignments + mapping(uint256 => Set.AddressSet) votersOf; // disputed assignment ID => voters's address + // mapping(address => mapping(uint256 => bool)) public validatorDisputed; // mapping total task completed in epoch and reward per epoch // epoch index => total reward From 394c86e88facfba3f61ca6041eb3fcea41381e79 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Wed, 15 May 2024 08:16:54 +0700 Subject: [PATCH 02/13] Pre-complete dispute | vote | slash --- contracts/WorkerHub.sol | 281 +++++++++++++++--------- contracts/interfaces/IWorkerHub.sol | 35 ++- contracts/storages/WorkerHubStorage.sol | 23 +- 3 files changed, 215 insertions(+), 124 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index f45088c..c441b1a 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import {DoubleEndedQueue} from "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; import {Random} from "./lib/Random.sol"; import {Set} from "./lib/Set.sol"; @@ -19,16 +20,12 @@ ReentrancyGuardUpgradeable { using Random for Random.Randomizer; using Set for Set.AddressSet; using Set for Set.Uint256Set; - using DoubleEndedQueue for Bytes32Deque; + using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; string constant private VERSION = "v0.0.1"; uint256 constant private PERCENTAGE_DENOMINATOR = 100_00; uint256 constant private BLOCK_PER_YEAR = 365 days / 2; // 2s per block - modifier onlyMiner { - require(msg.sender == ) - } - receive() external payable {} function initialize( @@ -564,7 +561,7 @@ ReentrancyGuardUpgradeable { address modelAddrOfMiner = miners[msg.sender].modelAddress; if (!minerAddressesByModel[modelAddrOfMiner].hasValue(msg.sender)) revert InvalidMiner(); - Assignment memory clonedAssignments = assignments[_assigmentId]; + Assignment memory clonedAssignments = assignments[_assignmentId]; Assignment memory clonedAssignments = assignments[_assignmentId]; @@ -613,177 +610,247 @@ ReentrancyGuardUpgradeable { // TODO } - //Check whether a worker is available (It had been joined) - function _checkAvailableWorker() internal { + //Check whether a worker is available (the worker had previously joined). + function _checkAvailableWorker() internal view { if (!validatorAddresses.hasValue(msg.sender)) { - if (!minerAddresses.hasValue(msg.sender)) revert ("Invalid miner"); - if (!minerAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); + if (!minerAddresses.hasValue(msg.sender)) revert InvalidMiner(); + + address modelAddrOfMiner = miners[msg.sender].modelAddress; + if (!minerAddressesByModel[modelAddrOfMiner].hasValue(msg.sender)) revert InvalidMiner(); } - if (!validatorAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); + + address modelAddrOfValidator = validators[msg.sender].modelAddress; + if (!validatorAddressesByModel[modelAddrOfValidator].hasValue(msg.sender)) revert InvalidValidator(); } - function disputeInfer(uint256 _assignmentId) public virtual { + function disputeInfer(uint256 _inferId) public virtual { _updateEpoch(); _checkAvailableWorker(); - // TODO - address _msgSender = msg.sender; - - // check msgSender is validator - // if (!validatorAddresses.hasValue(msg.sender)) revert("Invalid validator"); - // if (!validatorAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); - Assignment memory clonedAssignment = assignments[_assignmentId]; - Inference memory clonedInference = inferences[clonedAssignment.inferenceId]; + Inference memory clonedInference = inferences[_inferId]; + uint256[] memory assignmentIds = clonedInference.assignments; - //check this assignment has been disputed - if (clonedAssignment.output.length == 0) revert("Assignment is not submitted"); + // Check case: There is only one submission. TODO: handle (kelvin) + if (assignmentIds.length == 1) revert LoneSubmissionNoDispute(); + if (assignmentIds.length == 0) revert SubmissionsEmpty(); if (clonedInference.status != InferenceStatus.Solved) revert InvalidInferenceStatus(); - // check disputing time expire - uint40 validatingExpiredAt = uint40(clonedInference.expiredAt + validatingTimeLimit); - uint40 disputingExpiredAt = uint40(clonedInference.expiredAt + validatingTimeLimit); + // Verify if this inference has been disputed + if(disputedInferIds.hasValue(_inferId)) revert InferenceAlreadyDisputed(); - if (block.timestamp < clonedInference.expiredAt) revert ("The validating time has not yet arrived"); - if (validatingExpiredAt < block.timestamp) revert ("Exceeding the validating period"); + uint40 validateExpireTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit); + uint40 disputeExpiredTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit + disputingTimeLimit); - if(disputedAssignmentIds.hasValue(_assignmentId)) revert ("Assignment already disputed"); + // Verify whether the dispute is raised within the permitted time window + if (block.timestamp < clonedInference.expiredAt) revert PrematureValidate(); + if (validateExpireTimestamp < block.timestamp) revert ValidateTimeout(); - // validatorDisputed[_msgSender][_assignmentId] = true; - disputedAssignmentIds.insert(_assignmentId); - disputedAssignmentsOf[msg.sender].insert(_assignmentId) + disputedInferIds.insert(_inferId); + // disputedInfersOf[msg.sender].insert(_inferId); - DisputedAssignment storage disAssignment = disputedAssignments[_assignmentId]; - disAssignment.inferenceId = clonedAssignment.inferenceId; - disAssignment.creator = msg.sender; - disAssignment.totalValidator = validators.length; - disAssignment.validatingExpireAt = validatingExpiredAt; - disAssignment.disputingExpireAt = disputingExpiredAt; + DisputedInfer storage disputedInfer = disputedInfers[_inferId]; + disputedInfer.totalValidator = uint16(validatorAddresses.values.length); + disputedInfer.validatingExpireAt = validateExpireTimestamp; + disputedInfer.disputingExpireAt = disputeExpiredTimestamp; //inference - Inference storage inference = inferences[clonedAssignment.inferenceId]; + Inference storage inference = inferences[_inferId]; inference.disputingAddress = msg.sender; inference.status = InferenceStatus.Disputing; + emit DisputeInference(msg.sender, _inferId, uint40(block.timestamp), validateExpireTimestamp, disputeExpiredTimestamp); + //disputing queue - DisputingQueueElement memory disputingEl = DisputingQueueElement(_assignmentId, disputingExpiredAt); - bytes32 encodedEl = bytes32(abi.encode(pair)); - disputingQueue.pushBack(encodedEl); + // DisputingQueueElement memory disputingEl = DisputingQueueElement(assignmentId, disputingExpiredAt); + // bytes32 encodedEl = bytes32(abi.encode(pair)); + // disputingQueue.pushBack(encodedEl); } - function upvoteDispute(uint256 _assignmentId) public virtual { + function upvoteDispute(uint256 _inferId, Ballot[] calldata ballots) public virtual { _updateEpoch(); - // check msgSender is available for validating (it had been joined) - if (!validatorAddresses.hasValue(msg.sender)) revert("Invalid validator"); - if (!validatorAddressesByModel.hasValue(msg.sender)) revert("Invalid validator"); + if (ballots.length == 0) revert BallotEmpty(); + + // Check whether a validator is available (the validator had previously joined). + if (!validatorAddresses.hasValue(msg.sender)) revert InvalidValidator(); + address modelAddr = validators[msg.sender].modelAddress; + if (!validatorAddressesByModel[modelAddr].hasValue(msg.sender)) revert InvalidValidator(); - Assignment memory clonedAssignment = assignments[_assignmentId]; - Inference memory clonedInference = inferences[clonedAssignment.inferenceId]; - DisputedAssignment memory clonedDisputedAssignment = disputedAssignments[_assignmentId]; + Inference memory clonedInference = inferences[_inferId]; + DisputedInfer memory disputedInfer = disputedInfers[_inferId]; - //check this assignment has been disputed - if (clonedAssignment.output.length == 0) revert("Assignment is not submitted"); + if (clonedInference.assignments.length == 0) revert SubmissionsEmpty(); if (clonedInference.status != InferenceStatus.Disputing) revert InvalidInferenceStatus(); - // check disputing time expire - uint40 validatingExpiredAt = clonedDisputedAssignment.validatingExpireAt; - uint40 disputingExpiredAt = clonedDisputedAssignment.disputingExpireAt;; + // Verify if this assignment has been disputed. + if (!disputedInferIds.hasValue(_inferId)) revert InferenceNotDisputed(); - if (block.timestamp < validatingExpiredAt) revert ("The disputing time has not yet arrived"); - if (disputingExpiredAt < block.timestamp) revert ("Exceeding the disputing period"); + // Verify if the dispute period has ended + if (block.timestamp < disputedInfer.disputingExpireAt) revert PrematureDispute(); + if (disputedInfer.disputingExpireAt < block.timestamp) revert DisputeTimeout(); - if (!disputedAssignmentIds.hasValue(_assignmentId)) revert ("Assignment is not disputed"); - if (votersOf[_assignmentId].hasValue(msg.sender)) revert("Validator already voted"); + // Each person is only allowed to vote once. + if (votersOf[_inferId].hasValue(msg.sender)) revert ValidatorVoteExists(); - //Handle - clonedAssignment.disapprovalCount++; + uint256 ballotsLen = ballots.length; - disputedAssignmentsOf[msg.sender].insert(_assignmentId); - votersOf[_assignmentId].insert(msg.sender); + for (uint256 i = 0; i < ballotsLen; i++) { + if (!ballots[i].result) { + assignments[ballots[i].assignmentId].disapprovalCount++; + } + } + + // disputedInfersOf[msg.sender].insert(_inferId); + votersOf[_inferId].insert(msg.sender); + + emit DisputeUpvote(msg.sender, _inferId, uint40(block.timestamp)); + + //TODO: If the reaction time expires but the number of ballots is less than 2/3, + // should we extend the waiting time for validators or slash inactive validators and initiate a new vote? } - function _resolveDispute() internal { - while (true) { - bytes32 head = disputingQueue.popFront(); - DisputingQueueElement memory disputingEl = abi.decode(head, (DisputingQueueElement)); - uint256 assignmentId = disputingEl.id; + function resolveDispute(uint256 _inferId) public { + // Verify if this assignment has been disputed. + if (!disputedInferIds.hasValue(_inferId)) revert InferenceNotDisputed(); - Assignment memory assignment = assignments[assignmentId]; + Inference memory inference = inferences[_inferId]; + DisputedInfer memory disputedInfer = disputedInfers[_inferId]; - if (block.timestamp < disputingEl.expiredAt) { - disputingQueue.pushFront(head); - break; - } else { - uint8 disapprovalCount = votersOf[assignmentId].length; - DisputedAssignment storage disAssignment = disputedAssignments[assignmentId]; + if (block.timestamp < disputedInfer.disputingExpireAt) revert PrematureDispute(); + if (inference.status != InferenceStatus.Disputing) revert InvalidInferenceStatus(); - Assignment storage assignment = assignments[assignmentId]; - assignment.disapprovalCount = disapprovalCount; - Inference storage inference = inferences[assignment.inferenceId]; + // TODO: Handling the 'No voter' edge case + // votersOf[_inferId].values == 0 - if (disapprovalCount * 3 < disAssignment.totalValidator * 2 + 3) { // total voted >= 2/3 validator + 1 - //inference - inference.status = InferenceStatus.Solved; - disAssignment.isValid = false; //Dispute is not valid, The miner is honest + uint16 totalValidator = disputedInfer.totalValidator; - //slashing validator - _slashValidator(assignment.worker); + uint256[] memory assignmentIds = inference.assignments; + uint256 assignmentsLen = assignmentIds.length; + address[] memory fraudMiners = new address[](minerRequirement); + + uint256 counter = 0; + bool isDisputeValid = true; + + for (uint256 i = 0; i < assignmentsLen; i++){ + Assignment memory assignment = assignments[assignmentIds[i]]; + // A dispute will be invalid if the disapproval count for a submission falls outside the range of 1/3 to 2/3 of the total number of validators. + if (totalValidator <= assignment.disapprovalCount * 3 && assignment.disapprovalCount * 3 <= totalValidator * 2) { + isDisputeValid = false; + } else if ( totalValidator * 2 < assignment.disapprovalCount * 3 ) { + fraudMiners[counter++] = assignment.worker; + } + } - } else { - inference.status = InferenceStatus.Killed; - disAssignment.isValid = true; //Dispute is valid, The miner is dishonest + //deactivate and slash + _cullInactiveValidator(_inferId); - //slashing miner - _slashMiner(assignment.worker) - } + if (isDisputeValid) { + // Slash the fraud miners + uint256 fraudMinersLen = fraudMiners.length; - emit ResolveDispute(assignmentId, inference.status); + for (uint256 i = 0; i < fraudMinersLen; i++) { + if (fraudMiners[i] == address(0)) break; + _slashMiner(fraudMiners[i]); } + + emit DisputeResolving(_inferId, inference.modelAddress, isDisputeValid); + } else { + _slashValidator(inference.disputingAddress); } } - function _slashMiner(address _miner) internal { - Worker storage miner = miners[_miner]; + // Pruning when validator lazy to vote + function _cullInactiveValidator(uint256 _inferId) internal { + address modelAddr = inferences[_inferId].modelAddress; - if (!minerAddresses.hasValue(_miner)) revert ("Miner does not exist"); - address modelAddress = miner.modelAddress; + address[] memory validators = validatorAddressesByModel[modelAddr].values; + uint256 validatorsLen = validators.length; + if (validatorsLen == 0) return; - // Remove miner from available miner - if (minerAddressesByModel[modelAddress].hasValue(_miner)) { - minerAddressesByModel[modelAddress].erase(_miner); - minerAddresses.erase(_miner); + Set.AddressSet storage votersSet = votersOf[_inferId]; + uint256 votersLen = votersSet.values.length; + + if (votersLen == validatorsLen) return; + + address[] memory inactiveValidators = new address[](validatorsLen - votersLen); + uint16 counter = 0; + + for (uint256 i = 0; i < validatorsLen; i++) { + if (!votersSet.hasValue(validators[i])) { + inactiveValidators[counter++] = validators[i]; + } } - // Set the time miner can join again - miner.activeTime = block.timestamp + slashingMinerTimeLimit; - uint256 fine = miner.stake * 5 / 100; // Fine = stake * 5% - miner.stake -= fine; + uint256 len = inactiveValidators.length; - TransferHelper.safeTransferNative(treasury, fine); + for (uint256 i = 0; i < len; i++) { + _deactivateValidator(inactiveValidators[i]); + } + } + + function _deactivateValidator(address _validator) internal { + Worker storage validator = validators[_validator]; - emit SlashingMiner(_miner, miner.activeTime, fine); + if (!validatorAddresses.hasValue(_validator)) revert ("Validator does not exist"); + + address modelAddress = validator.modelAddress; + + // Double check hasValue + if (validatorAddressesByModel[modelAddress].hasValue(_validator)) { + validatorAddressesByModel[modelAddress].erase(_validator); + validatorAddresses.erase(_validator); + } + + validator.activeTime = uint40(block.timestamp + slashingValidatorTimeLimit); + + emit ValidatorDeactivated(_validator, modelAddress, validator.activeTime); } function _slashValidator(address _validator) internal { Worker storage validator = validators[_validator]; - if (!validatorAddresses.hasValue(_validator)) revert ("Validator does not exist"); + if (!validatorAddresses.hasValue(_validator)) revert InvalidValidator(); address modelAddress = validator.modelAddress; - if (validatorAddressesByModel[modelAddress].hasValue(_miner)) { + if (validatorAddressesByModel[modelAddress].hasValue(_validator)) { validatorAddressesByModel[modelAddress].erase(_validator); validatorAddresses.erase(_validator); } - validator.activeTime = block.timestamp + slashingValidatorTimeLimit; + validator.activeTime = uint40(block.timestamp + slashingValidatorTimeLimit); uint256 fine = validator.stake * 5 / 100; validator.stake -= fine; TransferHelper.safeTransferNative(treasury, fine); - emit ValidatorSlashed(_validator, validator.activeTime, fine); + emit FraudulentValidatorPenalized(_validator, modelAddress, treasury, fine); + } + + + + function _slashMiner(address _miner) internal { + Worker storage miner = miners[_miner]; + + if (!minerAddresses.hasValue(_miner)) revert InvalidMiner(); + + address modelAddress = miner.modelAddress; + + // Remove miner from available miner + if (minerAddressesByModel[modelAddress].hasValue(_miner)) { + minerAddressesByModel[modelAddress].erase(_miner); + minerAddresses.erase(_miner); + } + + // Set the time miner can join again + miner.activeTime = uint40(block.timestamp + slashingMinerTimeLimit); + uint256 fine = miner.stake * 5 / 100; // Fine = stake * 5% + miner.stake -= fine; + + TransferHelper.safeTransferNative(treasury, fine); + + emit FraudulentMinerPenalized(_miner, modelAddress, treasury, fine); } function slashMiner(address _miner, bool _isFined) public virtual onlyOwner { diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index c7374c9..195240c 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -84,16 +84,19 @@ interface IWorkerHub is IInferable { address creator; } - struct DisputedAssignment { - uint256 inferenceId; - address creator; + struct DisputedInfer { uint16 totalValidator; - uint16 votedValidator; + // uint16 votedValidator; bool isValid; uint40 validatingExpireAt; uint40 disputingExpireAt; } + struct Ballot { + uint256 assignmentId; + bool result; + } + struct DisputingQueueElement { uint256 id; uint40 expiredAt; @@ -179,10 +182,15 @@ interface IWorkerHub is IInferable { event UnstakeDelayTime(uint256 oldDelayTime, uint256 newDelayTime); event Restake(address indexed miner, uint256 restake, address indexed model); - event MinerDeactivated(address indexed miner, address indexed modelAddress, uint40 activeTime); - event FraudulentMinerPenalized(address indexed miner, address indexed modelAddress, address indexed treasury, uint256 fine); event PenaltyDurationUpdated(uint40 oldDuration, uint40 newDuration); event FinePercentageUpdated(uint16 oldPercent, uint16 newPercent); + event MinerDeactivated(address indexed miner, uint40 activeTime, uint256 fine); + event FraudulentMinerPenalized(address indexed miner, address indexed modelAddress, address indexed treasury, uint256 fine); + event ValidatorDeactivated(address indexed validator, address indexed modelAddress, uint40 activeTime); + event FraudulentValidatorPenalized(address indexed validator, address indexed modelAddress, address indexed treasury, uint256 fine); + event DisputeInference(address indexed caller, uint256 indexed inferId, uint40 now, uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp); + event DisputeUpvote(address indexed caller, uint256 indexed inferId, uint40 now); + event DisputeResolving(uint256 indexed inferId, address indexed modelAddress, bool status); error AlreadyRegistered(); error AlreadySubmitted(); @@ -209,6 +217,17 @@ interface IWorkerHub is IInferable { error InvalidValidator(); error InvalidMiner(); - error MinerInDeactivationTime(); - error ValidatorInDeactivationTime(); + error InferenceAlreadyDisputed(); + error InferenceNotDisputed(); + + error PrematureValidate(); + error ValidateTimeout(); + error PrematureDispute(); + error DisputeTimeout(); + + error ValidatorVoteExists(); + error SubmissionsEmpty(); + error LoneSubmissionNoDispute(); + error BallotEmpty(); + } diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index 48a0675..1dbab4e 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; +import {DoubleEndedQueue} from "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; import {IWorkerHub} from "../interfaces/IWorkerHub.sol"; import {Random} from "../lib/Random.sol"; @@ -30,14 +30,9 @@ abstract contract WorkerHubStorage is IWorkerHub { mapping(uint256 => Assignment) public assignments; mapping(address => Set.Uint256Set) internal assignmentsByMiner; mapping(uint256 => Set.Uint256Set) internal assignmentsByInference; - - //Dispute structures - Set.Uint256Set internal disputedAssignmentIds; - DoubleEndedQueue.Bytes32Deque disputingQueue; - mapping(uint256 => DisputedAssignment) internal disputedAssignments; // assignmentId => DisputedAssignment - mapping(address => Set.Uint256Set) disputedAssignmentsOf; //voter's address => disputed assignments - mapping(uint256 => Set.AddressSet) votersOf; // disputed assignment ID => voters's address - // mapping(address => mapping(uint256 => bool)) public validatorDisputed; + + mapping(address => mapping(uint256 => bool)) public validatorDisputed; //Do know the need + // mapping total task completed in epoch and reward per epoch // epoch index => total reward @@ -71,6 +66,16 @@ abstract contract WorkerHubStorage is IWorkerHub { // Tx Fee uint16 public finePercentage; + //Slashing + uint40 public slashingMinerTimeLimit; + uint40 public slashingValidatorTimeLimit; + + //Dispute structures + Set.Uint256Set internal disputedInferIds; + mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer + mapping(uint256 => Set.AddressSet) internal votersOf; // disputed inference ID => voters's address + // mapping(address => Set.Uint256Set) internal disputedInfersOf; //voter's address => disputed inference id + // DoubleEndedQueue.Bytes32Deque internal disputingQueue; // mapping tracking reward mapping(address => uint256) internal minerRewards; From ed8be6872687c5be0c5dbb2234d43e994bb5d393 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Wed, 15 May 2024 08:20:06 +0700 Subject: [PATCH 03/13] Clean code --- contracts/WorkerHub.sol | 7 ------- contracts/interfaces/IWorkerHub.sol | 6 ------ contracts/storages/WorkerHubStorage.sol | 2 -- 3 files changed, 15 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index c441b1a..cc878c2 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {DoubleEndedQueue} from "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; import {Random} from "./lib/Random.sol"; import {Set} from "./lib/Set.sol"; @@ -20,7 +19,6 @@ ReentrancyGuardUpgradeable { using Random for Random.Randomizer; using Set for Set.AddressSet; using Set for Set.Uint256Set; - using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; string constant private VERSION = "v0.0.1"; uint256 constant private PERCENTAGE_DENOMINATOR = 100_00; @@ -659,11 +657,6 @@ ReentrancyGuardUpgradeable { inference.status = InferenceStatus.Disputing; emit DisputeInference(msg.sender, _inferId, uint40(block.timestamp), validateExpireTimestamp, disputeExpiredTimestamp); - - //disputing queue - // DisputingQueueElement memory disputingEl = DisputingQueueElement(assignmentId, disputingExpiredAt); - // bytes32 encodedEl = bytes32(abi.encode(pair)); - // disputingQueue.pushBack(encodedEl); } function upvoteDispute(uint256 _inferId, Ballot[] calldata ballots) public virtual { diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 195240c..59d8c0d 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -86,7 +86,6 @@ interface IWorkerHub is IInferable { struct DisputedInfer { uint16 totalValidator; - // uint16 votedValidator; bool isValid; uint40 validatingExpireAt; uint40 disputingExpireAt; @@ -97,11 +96,6 @@ interface IWorkerHub is IInferable { bool result; } - struct DisputingQueueElement { - uint256 id; - uint40 expiredAt; - } - struct UnstakeRequest { uint256 stake; uint40 unlockAt; diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index 1dbab4e..9bf0b9b 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {DoubleEndedQueue} from "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; import {IWorkerHub} from "../interfaces/IWorkerHub.sol"; import {Random} from "../lib/Random.sol"; @@ -75,7 +74,6 @@ abstract contract WorkerHubStorage is IWorkerHub { mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer mapping(uint256 => Set.AddressSet) internal votersOf; // disputed inference ID => voters's address // mapping(address => Set.Uint256Set) internal disputedInfersOf; //voter's address => disputed inference id - // DoubleEndedQueue.Bytes32Deque internal disputingQueue; // mapping tracking reward mapping(address => uint256) internal minerRewards; From 61676eca871334b17ed1af75f7bb8c10de571ba6 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Wed, 15 May 2024 11:56:55 +0700 Subject: [PATCH 04/13] Update logic for transferring value to miners and validators --- contracts/WorkerHub.sol | 38 +++++++++++++++++++------ contracts/interfaces/IWorkerHub.sol | 1 + contracts/storages/WorkerHubStorage.sol | 2 ++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index cc878c2..e424db0 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -593,7 +593,7 @@ ReentrancyGuardUpgradeable { if (inference.assignments.length == 1) { uint256 fee = clonedInference.value * feePercentage / PERCENTAGE_DENOMINATOR; - uint256 value = clonedInference.value - fee; + uint256 value = clonedInference.value * minerFeePercentage / PERCENTAGE_DENOMINATOR; TransferHelper.safeTransferNative(treasury, fee); TransferHelper.safeTransferNative(_msgSender, value); @@ -608,6 +608,7 @@ ReentrancyGuardUpgradeable { // TODO } + //Check whether a worker is available (the worker had previously joined). function _checkAvailableWorker() internal view { if (!validatorAddresses.hasValue(msg.sender)) { @@ -621,10 +622,7 @@ ReentrancyGuardUpgradeable { if (!validatorAddressesByModel[modelAddrOfValidator].hasValue(msg.sender)) revert InvalidValidator(); } - function disputeInfer(uint256 _inferId) public virtual { - _updateEpoch(); - _checkAvailableWorker(); - + function _beforeDispute(uint256 _inferId) internal view returns(uint40, uint40){ Inference memory clonedInference = inferences[_inferId]; uint256[] memory assignmentIds = clonedInference.assignments; @@ -643,6 +641,30 @@ ReentrancyGuardUpgradeable { if (block.timestamp < clonedInference.expiredAt) revert PrematureValidate(); if (validateExpireTimestamp < block.timestamp) revert ValidateTimeout(); + return (validateExpireTimestamp, disputeExpiredTimestamp); + } + + function noDispute(uint256 _inferId) public { + _updateEpoch(); + _checkAvailableWorker(); + // TODO: following new logic, we must check the msg.sender has been assigned the task. + + (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); + + Inference memory clonedInference = inferences[_inferId]; + uint256 value = clonedInference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; + + TransferHelper.safeTransferNative(msg.sender, value); + + emit NoDisputeInference(msg.sender, _inferId, uint40(block.timestamp), value); + } + + function disputeInfer(uint256 _inferId) public virtual { + _updateEpoch(); + _checkAvailableWorker(); + + (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); + disputedInferIds.insert(_inferId); // disputedInfersOf[msg.sender].insert(_inferId); @@ -656,6 +678,7 @@ ReentrancyGuardUpgradeable { inference.disputingAddress = msg.sender; inference.status = InferenceStatus.Disputing; + emit InferenceStatusUpdate(_inferId, InferenceStatus.Disputing); emit DisputeInference(msg.sender, _inferId, uint40(block.timestamp), validateExpireTimestamp, disputeExpiredTimestamp); } @@ -703,6 +726,8 @@ ReentrancyGuardUpgradeable { } function resolveDispute(uint256 _inferId) public { + _updateEpoch(); + // Verify if this assignment has been disputed. if (!disputedInferIds.hasValue(_inferId)) revert InferenceNotDisputed(); @@ -756,7 +781,6 @@ ReentrancyGuardUpgradeable { function _cullInactiveValidator(uint256 _inferId) internal { address modelAddr = inferences[_inferId].modelAddress; - address[] memory validators = validatorAddressesByModel[modelAddr].values; uint256 validatorsLen = validators.length; if (validatorsLen == 0) return; @@ -821,8 +845,6 @@ ReentrancyGuardUpgradeable { emit FraudulentValidatorPenalized(_validator, modelAddress, treasury, fine); } - - function _slashMiner(address _miner) internal { Worker storage miner = miners[_miner]; diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 59d8c0d..09b9a37 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -183,6 +183,7 @@ interface IWorkerHub is IInferable { event ValidatorDeactivated(address indexed validator, address indexed modelAddress, uint40 activeTime); event FraudulentValidatorPenalized(address indexed validator, address indexed modelAddress, address indexed treasury, uint256 fine); event DisputeInference(address indexed caller, uint256 indexed inferId, uint40 now, uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp); + event NoDisputeInference(address indexed caller, uint256 indexed inferId, uint40 now, uint256 value); event DisputeUpvote(address indexed caller, uint256 indexed inferId, uint40 now); event DisputeResolving(uint256 indexed inferId, address indexed modelAddress, bool status); diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index 9bf0b9b..25c4dcd 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -65,6 +65,8 @@ abstract contract WorkerHubStorage is IWorkerHub { // Tx Fee uint16 public finePercentage; + uint16 public minerFeePercentage; + //Slashing uint40 public slashingMinerTimeLimit; uint40 public slashingValidatorTimeLimit; From 4e3def1ea7715dfb05a4a0e64970d7c37cf1df76 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Wed, 15 May 2024 11:56:55 +0700 Subject: [PATCH 05/13] Update logic for transferring value to miners and validators --- contracts/WorkerHub.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index e424db0..1de4e3d 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -775,6 +775,10 @@ ReentrancyGuardUpgradeable { } else { _slashValidator(inference.disputingAddress); } + + inferences[_inferId].status = InferenceStatus.Solved; + + emit InferenceStatusUpdate(_inferId, InferenceStatus.Solved); } // Pruning when validator lazy to vote From 6f2305ed394d753c654f08f08ababe9b34fc7ff7 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Wed, 15 May 2024 15:46:45 +0700 Subject: [PATCH 06/13] Create func deactivate inactive miners and update resolve inference --- contracts/WorkerHub.sol | 42 +++++++++++++++++++++---- contracts/interfaces/IWorkerHub.sol | 5 ++- contracts/storages/WorkerHubStorage.sol | 8 ++--- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index 1de4e3d..85fa9a7 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -771,6 +771,9 @@ ReentrancyGuardUpgradeable { _slashMiner(fraudMiners[i]); } + uint256 value = inference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; + TransferHelper.safeTransferNative(inference.disputingAddress, value); + emit DisputeResolving(_inferId, inference.modelAddress, isDisputeValid); } else { _slashValidator(inference.disputingAddress); @@ -813,7 +816,7 @@ ReentrancyGuardUpgradeable { function _deactivateValidator(address _validator) internal { Worker storage validator = validators[_validator]; - if (!validatorAddresses.hasValue(_validator)) revert ("Validator does not exist"); + if (!validatorAddresses.hasValue(_validator)) revert InvalidValidator(); address modelAddress = validator.modelAddress; @@ -823,11 +826,29 @@ ReentrancyGuardUpgradeable { validatorAddresses.erase(_validator); } - validator.activeTime = uint40(block.timestamp + slashingValidatorTimeLimit); + validator.activeTime = uint40(block.timestamp + penaltyDuration); emit ValidatorDeactivated(_validator, modelAddress, validator.activeTime); } + function _deactivateMiner(address _miner) internal { + Worker storage miner = miners[_miner]; + + if (!minerAddresses.hasValue(_miner)) revert InvalidMiner(); + + address modelAddress = miner.modelAddress; + + // Double check hasValue + if (minerAddressesByModel[modelAddress].hasValue(_miner)) { + minerAddressesByModel[modelAddress].erase(_miner); + minerAddresses.erase(_miner); + } + + miner.activeTime = uint40(block.timestamp + penaltyDuration); + + emit MinerDeactivated(_miner, modelAddress, miner.activeTime); + } + function _slashValidator(address _validator) internal { Worker storage validator = validators[_validator]; @@ -840,8 +861,8 @@ ReentrancyGuardUpgradeable { validatorAddresses.erase(_validator); } - validator.activeTime = uint40(block.timestamp + slashingValidatorTimeLimit); - uint256 fine = validator.stake * 5 / 100; + validator.activeTime = uint40(block.timestamp + penaltyDuration); + uint256 fine = validator.stake * finePercentage / PERCENTAGE_DENOMINATOR; validator.stake -= fine; TransferHelper.safeTransferNative(treasury, fine); @@ -863,8 +884,8 @@ ReentrancyGuardUpgradeable { } // Set the time miner can join again - miner.activeTime = uint40(block.timestamp + slashingMinerTimeLimit); - uint256 fine = miner.stake * 5 / 100; // Fine = stake * 5% + miner.activeTime = uint40(block.timestamp + penaltyDuration); + uint256 fine = miner.stake * finePercentage / PERCENTAGE_DENOMINATOR; // Fine = stake * 5% miner.stake -= fine; TransferHelper.safeTransferNative(treasury, fine); @@ -937,6 +958,15 @@ ReentrancyGuardUpgradeable { inference.status = InferenceStatus.Killed; TransferHelper.safeTransferNative(inference.creator, inference.value); emit InferenceStatusUpdate(_inferenceId, InferenceStatus.Killed); + + // Deactivate inactive miners. + // Deactivate all 3 miners because this inference has solving status. This mean there is no submission. + uint256[] memory assignmentIds = inference.assignments; + uint256 assignmentsLen = assignmentIds.length; + + for (uint256 i = 0; i < assignmentsLen; i++) { + _deactivateMiner(assignments[assignmentIds[i]].worker); + } } } diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 09b9a37..5682376 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -178,7 +178,7 @@ interface IWorkerHub is IInferable { event PenaltyDurationUpdated(uint40 oldDuration, uint40 newDuration); event FinePercentageUpdated(uint16 oldPercent, uint16 newPercent); - event MinerDeactivated(address indexed miner, uint40 activeTime, uint256 fine); + event MinerDeactivated(address indexed miner, address indexed modelAddress, uint40 activeTime); event FraudulentMinerPenalized(address indexed miner, address indexed modelAddress, address indexed treasury, uint256 fine); event ValidatorDeactivated(address indexed validator, address indexed modelAddress, uint40 activeTime); event FraudulentValidatorPenalized(address indexed validator, address indexed modelAddress, address indexed treasury, uint256 fine); @@ -225,4 +225,7 @@ interface IWorkerHub is IInferable { error LoneSubmissionNoDispute(); error BallotEmpty(); + error MinerInSlashingTime(); + error ValidatorInSlashingTime(); + } diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index 25c4dcd..5af6c78 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -64,12 +64,12 @@ abstract contract WorkerHubStorage is IWorkerHub { uint256 public rewardPerEpoch; // 12299.97 reward EAI for 1 worker per year // Tx Fee - uint16 public finePercentage; - uint16 public minerFeePercentage; + uint16 public minerFeePercentage; // Percentage of inference value allocated to miner + uint16 public finePercentage; //Slashing - uint40 public slashingMinerTimeLimit; - uint40 public slashingValidatorTimeLimit; + // uint40 public slashingMinerTimeLimit; + // uint40 public slashingValidatorTimeLimit; //Dispute structures Set.Uint256Set internal disputedInferIds; From b68d4a04a7d04f0979bf64cd0a4232ce4dce0e9d Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Wed, 15 May 2024 18:10:47 +0700 Subject: [PATCH 07/13] Update logic related to slashing the dishonest disputer --- contracts/WorkerHub.sol | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index 85fa9a7..cd9091b 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -644,7 +644,7 @@ ReentrancyGuardUpgradeable { return (validateExpireTimestamp, disputeExpiredTimestamp); } - function noDispute(uint256 _inferId) public { + function noDisputeInfer(uint256 _inferId) public { _updateEpoch(); _checkAvailableWorker(); // TODO: following new logic, we must check the msg.sender has been assigned the task. @@ -662,6 +662,7 @@ ReentrancyGuardUpgradeable { function disputeInfer(uint256 _inferId) public virtual { _updateEpoch(); _checkAvailableWorker(); + // TODO: following new logic, we must check the msg.sender has been assigned the task. (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); @@ -776,7 +777,13 @@ ReentrancyGuardUpgradeable { emit DisputeResolving(_inferId, inference.modelAddress, isDisputeValid); } else { - _slashValidator(inference.disputingAddress); + // disputing address can be miner or validator + address disputer = inference.disputingAddress; + if (minerAddresses.hasValue(disputer)) { + _slashMiner(disputer); + } else if (validatorAddresses.hasValue(disputer)) { + _slashValidator(disputer); + } } inferences[_inferId].status = InferenceStatus.Solved; From 04cf2333d41e2b3fc705e7e0390da36e3f4a6bfd Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Fri, 17 May 2024 15:00:41 +0700 Subject: [PATCH 08/13] Add voting status --- contracts/WorkerHub.sol | 42 +++++++++++++++++++---------- contracts/interfaces/IWorkerHub.sol | 1 + 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index cd9091b..7e9e090 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -548,7 +548,6 @@ ReentrancyGuardUpgradeable { } } - function submitSolution(uint256 _assignmentId, bytes calldata _data) public virtual whenNotPaused { _updateEpoch(); address _msgSender = msg.sender; @@ -566,12 +565,11 @@ ReentrancyGuardUpgradeable { // check msgSender is miner if (_msgSender != clonedAssignments.worker) revert Unauthorized(); - if (clonedAssignments.output.length != 0) revert AlreadySubmitted(); + if (clonedAssignments.output.length != 0) revert AlreadySubmitted(); Inference memory clonedInference = inferences[clonedAssignments.inferenceId]; - if (clonedInference.status != InferenceStatus.Solving && - clonedInference.status != InferenceStatus.Solved) + if (clonedInference.status != InferenceStatus.Solving) { revert InvalidInferenceStatus(); } @@ -588,7 +586,6 @@ ReentrancyGuardUpgradeable { Inference storage inference = inferences[clonedAssignments.inferenceId]; assignments[_assignmentId].output = _data; //Record the solution - inference.status = InferenceStatus.Solved; inference.assignments.push(_assignmentId); if (inference.assignments.length == 1) { @@ -598,7 +595,7 @@ ReentrancyGuardUpgradeable { TransferHelper.safeTransferNative(_msgSender, value); emit TransferFee(_msgSender, value, treasury, fee); - emit InferenceStatusUpdate(clonedAssignments.inferenceId, InferenceStatus.Solved); + emit InferenceStatusUpdate(clonedAssignments.inferenceId, InferenceStatus.Solving); } emit SolutionSubmission(_msgSender, _assignmentId); @@ -629,7 +626,7 @@ ReentrancyGuardUpgradeable { // Check case: There is only one submission. TODO: handle (kelvin) if (assignmentIds.length == 1) revert LoneSubmissionNoDispute(); if (assignmentIds.length == 0) revert SubmissionsEmpty(); - if (clonedInference.status != InferenceStatus.Solved) revert InvalidInferenceStatus(); + if (clonedInference.status != InferenceStatus.Solving) revert InvalidInferenceStatus(); // Verify if this inference has been disputed if(disputedInferIds.hasValue(_inferId)) revert InferenceAlreadyDisputed(); @@ -649,13 +646,17 @@ ReentrancyGuardUpgradeable { _checkAvailableWorker(); // TODO: following new logic, we must check the msg.sender has been assigned the task. + //TODO: must check this infer is still disputed , no_disputed + (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); - Inference memory clonedInference = inferences[_inferId]; - uint256 value = clonedInference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; + Inference storage inference = inferences[_inferId]; + inference.status = InferenceStatus.Solved; + uint256 value = inference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; TransferHelper.safeTransferNative(msg.sender, value); + emit InferenceStatusUpdate(_inferId, InferenceStatus.Solved); emit NoDisputeInference(msg.sender, _inferId, uint40(block.timestamp), value); } @@ -664,6 +665,7 @@ ReentrancyGuardUpgradeable { _checkAvailableWorker(); // TODO: following new logic, we must check the msg.sender has been assigned the task. + //TODO: must check this infer is still disputed , no_disputed (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); disputedInferIds.insert(_inferId); @@ -709,6 +711,11 @@ ReentrancyGuardUpgradeable { // Each person is only allowed to vote once. if (votersOf[_inferId].hasValue(msg.sender)) revert ValidatorVoteExists(); + if (votersOf[_inferId].values.length == 0) { + inferences[_inferId].status = InferenceStatus.Voting; + emit InferenceStatusUpdate(_inferId, InferenceStatus.Voting); + } + uint256 ballotsLen = ballots.length; for (uint256 i = 0; i < ballotsLen; i++) { @@ -775,6 +782,9 @@ ReentrancyGuardUpgradeable { uint256 value = inference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; TransferHelper.safeTransferNative(inference.disputingAddress, value); + inferences[_inferId].status = InferenceStatus.Killed; + + emit InferenceStatusUpdate(_inferId, InferenceStatus.Killed); emit DisputeResolving(_inferId, inference.modelAddress, isDisputeValid); } else { // disputing address can be miner or validator @@ -784,11 +794,11 @@ ReentrancyGuardUpgradeable { } else if (validatorAddresses.hasValue(disputer)) { _slashValidator(disputer); } - } - inferences[_inferId].status = InferenceStatus.Solved; - - emit InferenceStatusUpdate(_inferId, InferenceStatus.Solved); + inferences[_inferId].status = InferenceStatus.Solved; + + emit InferenceStatusUpdate(_inferId, InferenceStatus.Solved); + } } // Pruning when validator lazy to vote @@ -964,7 +974,6 @@ ReentrancyGuardUpgradeable { if (inference.status == InferenceStatus.Solving && block.timestamp > inference.expiredAt) { inference.status = InferenceStatus.Killed; TransferHelper.safeTransferNative(inference.creator, inference.value); - emit InferenceStatusUpdate(_inferenceId, InferenceStatus.Killed); // Deactivate inactive miners. // Deactivate all 3 miners because this inference has solving status. This mean there is no submission. @@ -974,6 +983,11 @@ ReentrancyGuardUpgradeable { for (uint256 i = 0; i < assignmentsLen; i++) { _deactivateMiner(assignments[assignmentIds[i]].worker); } + + //TODO: If the validator who was assigned an inference has not called dispute() or no_dispute(), he will be deactivated + + emit InferenceStatusUpdate(_inferenceId, InferenceStatus.Killed); + } } diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 5682376..1fb5e82 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -8,6 +8,7 @@ interface IWorkerHub is IInferable { Nil, Solving, Disputing, + Voting, Solved, Killed } From 745e4e5415e5d0e65a62eeaf174190bc4db1981e Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Fri, 17 May 2024 17:16:18 +0700 Subject: [PATCH 09/13] Rebase from rune-worker-hub --- contracts/WorkerHub.sol | 101 +++++++----------------- contracts/interfaces/IWorkerHub.sol | 5 +- contracts/storages/WorkerHubStorage.sol | 24 +++--- 3 files changed, 42 insertions(+), 88 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index 7e9e090..ebf938e 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -405,7 +405,6 @@ ReentrancyGuardUpgradeable { function joinForValidating() external whenNotPaused { _updateEpoch(); - Worker storage validator = miners[msg.sender]; if (validator.tier == 0) revert NotRegistered(); if (block.timestamp < validator.activeTime) revert ValidatorInDeactivationTime(); @@ -430,8 +429,11 @@ ReentrancyGuardUpgradeable { validator.stake = 0; validator.commitment = 0; - validatorAddresses.erase(msg.sender); - validatorAddressesByModel[validator.modelAddress].erase(msg.sender); + if (validatorAddresses.hasValue(msg.sender)) { + _claimReward(msg.sender, false); + validatorAddresses.erase(msg.sender); + validatorAddressesByModel[validator.modelAddress].erase(msg.sender); + } validator.modelAddress = address(0); uint currentUnstake = validatorUnstakeRequests[msg.sender].stake; @@ -550,7 +552,6 @@ ReentrancyGuardUpgradeable { function submitSolution(uint256 _assignmentId, bytes calldata _data) public virtual whenNotPaused { _updateEpoch(); - address _msgSender = msg.sender; // Check whether miner is available (the miner had previously joined). The inactive miner is not allowed to submit solution. if (!minerAddresses.hasValue(msg.sender)) revert InvalidMiner(); @@ -558,13 +559,10 @@ ReentrancyGuardUpgradeable { address modelAddrOfMiner = miners[msg.sender].modelAddress; if (!minerAddressesByModel[modelAddrOfMiner].hasValue(msg.sender)) revert InvalidMiner(); - Assignment memory clonedAssignments = assignments[_assignmentId]; - - Assignment memory clonedAssignments = assignments[_assignmentId]; // check msgSender is miner - if (_msgSender != clonedAssignments.worker) revert Unauthorized(); + if (msg.sender != clonedAssignments.worker) revert Unauthorized(); if (clonedAssignments.output.length != 0) revert AlreadySubmitted(); Inference memory clonedInference = inferences[clonedAssignments.inferenceId]; @@ -592,13 +590,13 @@ ReentrancyGuardUpgradeable { uint256 fee = clonedInference.value * feePercentage / PERCENTAGE_DENOMINATOR; uint256 value = clonedInference.value * minerFeePercentage / PERCENTAGE_DENOMINATOR; TransferHelper.safeTransferNative(treasury, fee); - TransferHelper.safeTransferNative(_msgSender, value); + TransferHelper.safeTransferNative(msg.sender, value); - emit TransferFee(_msgSender, value, treasury, fee); + emit TransferFee(msg.sender, value, treasury, fee); emit InferenceStatusUpdate(clonedAssignments.inferenceId, InferenceStatus.Solving); } - emit SolutionSubmission(_msgSender, _assignmentId); + emit SolutionSubmission(msg.sender, _assignmentId); } function _handleDisputeSuccess(uint256 _inferId) internal { @@ -776,7 +774,7 @@ ReentrancyGuardUpgradeable { for (uint256 i = 0; i < fraudMinersLen; i++) { if (fraudMiners[i] == address(0)) break; - _slashMiner(fraudMiners[i]); + _slashMiner(fraudMiners[i], true); } uint256 value = inference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; @@ -790,9 +788,9 @@ ReentrancyGuardUpgradeable { // disputing address can be miner or validator address disputer = inference.disputingAddress; if (minerAddresses.hasValue(disputer)) { - _slashMiner(disputer); + _slashMiner(disputer, true); } else if (validatorAddresses.hasValue(disputer)) { - _slashValidator(disputer); + _slashValidator(disputer, true); } inferences[_inferId].status = InferenceStatus.Solved; @@ -826,51 +824,25 @@ ReentrancyGuardUpgradeable { uint256 len = inactiveValidators.length; for (uint256 i = 0; i < len; i++) { - _deactivateValidator(inactiveValidators[i]); - } - } - - function _deactivateValidator(address _validator) internal { - Worker storage validator = validators[_validator]; - - if (!validatorAddresses.hasValue(_validator)) revert InvalidValidator(); - - address modelAddress = validator.modelAddress; - - // Double check hasValue - if (validatorAddressesByModel[modelAddress].hasValue(_validator)) { - validatorAddressesByModel[modelAddress].erase(_validator); - validatorAddresses.erase(_validator); + _slashValidator(inactiveValidators[i], false); } - - validator.activeTime = uint40(block.timestamp + penaltyDuration); - - emit ValidatorDeactivated(_validator, modelAddress, validator.activeTime); } - function _deactivateMiner(address _miner) internal { - Worker storage miner = miners[_miner]; - - if (!minerAddresses.hasValue(_miner)) revert InvalidMiner(); - - address modelAddress = miner.modelAddress; - - // Double check hasValue - if (minerAddressesByModel[modelAddress].hasValue(_miner)) { - minerAddressesByModel[modelAddress].erase(_miner); - minerAddresses.erase(_miner); - } + function slashValidator(address _validator, bool _isFined) public virtual onlyOwner { + _updateEpoch(); - miner.activeTime = uint40(block.timestamp + penaltyDuration); + if (_validator == address(0)) revert InvalidValidator(); - emit MinerDeactivated(_miner, modelAddress, miner.activeTime); + _slashMiner(_validator, _isFined); } - function _slashValidator(address _validator) internal { + function _slashValidator(address _validator, bool _isFined) internal { Worker storage validator = validators[_validator]; if (!validatorAddresses.hasValue(_validator)) revert InvalidValidator(); + _claimReward(_validator, false); + address modelAddress = validator.modelAddress; if (validatorAddressesByModel[modelAddress].hasValue(_validator)) { @@ -879,35 +851,18 @@ ReentrancyGuardUpgradeable { } validator.activeTime = uint40(block.timestamp + penaltyDuration); - uint256 fine = validator.stake * finePercentage / PERCENTAGE_DENOMINATOR; - validator.stake -= fine; - - TransferHelper.safeTransferNative(treasury, fine); - - emit FraudulentValidatorPenalized(_validator, modelAddress, treasury, fine); - } - function _slashMiner(address _miner) internal { - Worker storage miner = miners[_miner]; - - if (!minerAddresses.hasValue(_miner)) revert InvalidMiner(); + if (_isFined) { + uint256 fine = validator.stake * finePercentage / PERCENTAGE_DENOMINATOR; + validator.stake -= fine; - address modelAddress = miner.modelAddress; + TransferHelper.safeTransferNative(treasury, fine); - // Remove miner from available miner - if (minerAddressesByModel[modelAddress].hasValue(_miner)) { - minerAddressesByModel[modelAddress].erase(_miner); - minerAddresses.erase(_miner); + emit FraudulentValidatorPenalized(_validator, modelAddress, treasury, fine); + return; } - // Set the time miner can join again - miner.activeTime = uint40(block.timestamp + penaltyDuration); - uint256 fine = miner.stake * finePercentage / PERCENTAGE_DENOMINATOR; // Fine = stake * 5% - miner.stake -= fine; - - TransferHelper.safeTransferNative(treasury, fine); - - emit FraudulentMinerPenalized(_miner, modelAddress, treasury, fine); + emit ValidatorDeactivated(_validator, modelAddress, validator.activeTime); } function slashMiner(address _miner, bool _isFined) public virtual onlyOwner { @@ -981,7 +936,7 @@ ReentrancyGuardUpgradeable { uint256 assignmentsLen = assignmentIds.length; for (uint256 i = 0; i < assignmentsLen; i++) { - _deactivateMiner(assignments[assignmentIds[i]].worker); + _slashMiner(assignments[assignmentIds[i]].worker, false); } //TODO: If the validator who was assigned an inference has not called dispute() or no_dispute(), he will be deactivated diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 1fb5e82..45542ed 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -179,6 +179,7 @@ interface IWorkerHub is IInferable { event PenaltyDurationUpdated(uint40 oldDuration, uint40 newDuration); event FinePercentageUpdated(uint16 oldPercent, uint16 newPercent); + event MinerDeactivated(address indexed miner, address indexed modelAddress, uint40 activeTime); event FraudulentMinerPenalized(address indexed miner, address indexed modelAddress, address indexed treasury, uint256 fine); event ValidatorDeactivated(address indexed validator, address indexed modelAddress, uint40 activeTime); @@ -226,7 +227,7 @@ interface IWorkerHub is IInferable { error LoneSubmissionNoDispute(); error BallotEmpty(); - error MinerInSlashingTime(); - error ValidatorInSlashingTime(); + error MinerInDeactivationTime(); + error ValidatorInDeactivationTime(); } diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index 5af6c78..76f9d86 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -32,7 +32,6 @@ abstract contract WorkerHubStorage is IWorkerHub { mapping(address => mapping(uint256 => bool)) public validatorDisputed; //Do know the need - // mapping total task completed in epoch and reward per epoch // epoch index => total reward mapping(uint256 => MinerEpochState) public rewardInEpoch; @@ -64,24 +63,23 @@ abstract contract WorkerHubStorage is IWorkerHub { uint256 public rewardPerEpoch; // 12299.97 reward EAI for 1 worker per year // Tx Fee - uint16 public minerFeePercentage; // Percentage of inference value allocated to miner uint16 public finePercentage; - //Slashing - // uint40 public slashingMinerTimeLimit; - // uint40 public slashingValidatorTimeLimit; - - //Dispute structures - Set.Uint256Set internal disputedInferIds; - mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer - mapping(uint256 => Set.AddressSet) internal votersOf; // disputed inference ID => voters's address - // mapping(address => Set.Uint256Set) internal disputedInfersOf; //voter's address => disputed inference id - // mapping tracking reward mapping(address => uint256) internal minerRewards; + // tracking time miner join the network to // determine multiplier value mapping(address => Boost) internal boost; - uint256[97] private __gap; + // Tx Fee + uint16 public minerFeePercentage; // Percentage of inference value allocated to miner + + //Dispute structures + Set.Uint256Set internal disputedInferIds; + mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer + mapping(uint256 => Set.AddressSet) internal votersOf; // disputed inference ID => voters's address + // mapping(address => Set.Uint256Set) internal disputedInfersOf; //voter's address => disputed inference id + + uint256[93] private __gap; } From be4400aa573e664debd1cef995b969560101b261 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Sun, 19 May 2024 18:55:24 +0700 Subject: [PATCH 10/13] Pre-complete no_dispute, dispute flow Define the validation assignment for validator Check the assigned validator's submission --- contracts/WorkerHub.sol | 94 +++++++++++++++++++------ contracts/interfaces/IWorkerHub.sol | 14 +++- contracts/storages/WorkerHubStorage.sol | 1 + 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index ebf938e..d9d5971 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -617,18 +617,50 @@ ReentrancyGuardUpgradeable { if (!validatorAddressesByModel[modelAddrOfValidator].hasValue(msg.sender)) revert InvalidValidator(); } + function _checkAvailableValidator() internal view { + if (!validatorAddresses.hasValue(msg.sender)) revert InvalidValidator(); + + address modelAddrOfValidator = validators[msg.sender].modelAddress; + if (!validatorAddressesByModel[modelAddrOfValidator].hasValue(msg.sender)) revert InvalidValidator(); + } + function _beforeDispute(uint256 _inferId) internal view returns(uint40, uint40){ Inference memory clonedInference = inferences[_inferId]; uint256[] memory assignmentIds = clonedInference.assignments; - // Check case: There is only one submission. TODO: handle (kelvin) if (assignmentIds.length == 1) revert LoneSubmissionNoDispute(); if (assignmentIds.length == 0) revert SubmissionsEmpty(); if (clonedInference.status != InferenceStatus.Solving) revert InvalidInferenceStatus(); + uint40 validateExpireTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit); + uint40 disputeExpiredTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit + disputingTimeLimit); + + // Verify whether the dispute is raised within the permitted time window + if (block.timestamp < clonedInference.expiredAt) revert PrematureValidate(); + if (validateExpireTimestamp < block.timestamp) revert ValidateTimeout(); + + return (validateExpireTimestamp, disputeExpiredTimestamp); + } + + // If the inference has only one submission, we allow it to be no_disputed + function _beforeNoDispute(uint256 _inferId) internal view returns(uint40, uint40) { + Inference memory clonedInference = inferences[_inferId]; + uint256[] memory assignmentIds = clonedInference.assignments; + + if (assignmentIds.length == 0) revert SubmissionsEmpty(); + if (clonedInference.status != InferenceStatus.Solving) revert InvalidInferenceStatus(); + // Verify if this inference has been disputed if(disputedInferIds.hasValue(_inferId)) revert InferenceAlreadyDisputed(); + //TODO check only assigned validator call this function + if (validatingAssignments[_inferId].assignedValidator != msg.sender) revert InvalidValidator(); + + // Verify the msg.sender has already been dispute/no_disputed + if (validatingAssignments[_inferId].result != ValidationResult.Nil) revert ("Do not allowed to re-submit the validation task"); + + //TODO: Check whether msg.sender is the assigned validator + uint40 validateExpireTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit); uint40 disputeExpiredTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit + disputingTimeLimit); @@ -639,32 +671,43 @@ ReentrancyGuardUpgradeable { return (validateExpireTimestamp, disputeExpiredTimestamp); } + // Only validator is allowed to call noDisputeIsnfer() function noDisputeInfer(uint256 _inferId) public { _updateEpoch(); - _checkAvailableWorker(); - // TODO: following new logic, we must check the msg.sender has been assigned the task. + _checkAvailableValidator(); + _beforeNoDispute(_inferId); - //TODO: must check this infer is still disputed , no_disputed + validatingAssignments[_inferId].result != ValidationResult.NoDispute; //Record the no_dispute request from the assigned validator - (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); - - Inference storage inference = inferences[_inferId]; - inference.status = InferenceStatus.Solved; - uint256 value = inference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; + // Inference storage inference = inferences[_inferId]; + // inference.status = InferenceStatus.Solved; + // uint256 value = inference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; - TransferHelper.safeTransferNative(msg.sender, value); + // TransferHelper.safeTransferNative(msg.sender, value); - emit InferenceStatusUpdate(_inferId, InferenceStatus.Solved); - emit NoDisputeInference(msg.sender, _inferId, uint40(block.timestamp), value); + // emit InferenceStatusUpdate(_inferId, InferenceStatus.Solved); + emit NoDisputeInference(msg.sender, _inferId); } - function disputeInfer(uint256 _inferId) public virtual { + function disputeInfer(uint256 _inferId, bool useValidatorRole) public virtual { _updateEpoch(); _checkAvailableWorker(); - // TODO: following new logic, we must check the msg.sender has been assigned the task. - //TODO: must check this infer is still disputed , no_disputed - (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); + (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); + + // TODO: if the msg.sender is the assigned validator, we have to record this request + if (validatingAssignments[_inferId].assignedValidator == msg.sender && + validatingAssignments[_inferId].result != ValidationResult.Nil) revert ("Do not allowed to re-submit the validation task"); + + if (validatingAssignments[_inferId].assignedValidator == msg.sender) { + validatingAssignments[_inferId].result = ValidationResult.Dispute; + + if (disputedInferIds.hasValue(_inferId)) { + return; + } + } else if (disputedInferIds.hasValue(_inferId)) { + revert InferenceAlreadyDisputed(); + } disputedInferIds.insert(_inferId); // disputedInfersOf[msg.sender].insert(_inferId); @@ -673,6 +716,7 @@ ReentrancyGuardUpgradeable { disputedInfer.totalValidator = uint16(validatorAddresses.values.length); disputedInfer.validatingExpireAt = validateExpireTimestamp; disputedInfer.disputingExpireAt = disputeExpiredTimestamp; + disputedInfer.isValidatorDispute = useValidatorRole; //inference Inference storage inference = inferences[_inferId]; @@ -741,7 +785,7 @@ ReentrancyGuardUpgradeable { DisputedInfer memory disputedInfer = disputedInfers[_inferId]; if (block.timestamp < disputedInfer.disputingExpireAt) revert PrematureDispute(); - if (inference.status != InferenceStatus.Disputing) revert InvalidInferenceStatus(); + if (inference.status != InferenceStatus.Disputing || inference.status != InferenceStatus.Voting) revert InvalidInferenceStatus(); // TODO: Handling the 'No voter' edge case // votersOf[_inferId].values == 0 @@ -767,6 +811,7 @@ ReentrancyGuardUpgradeable { //deactivate and slash _cullInactiveValidator(_inferId); + _checkAssignedValidatorSubmit(_inferId, isDisputeValid); if (isDisputeValid) { // Slash the fraud miners @@ -778,7 +823,8 @@ ReentrancyGuardUpgradeable { } uint256 value = inference.value * (PERCENTAGE_DENOMINATOR - feePercentage - minerFeePercentage) / PERCENTAGE_DENOMINATOR; - TransferHelper.safeTransferNative(inference.disputingAddress, value); + TransferHelper.safeTransferNative(inference.disputingAddress, value); //Transfer 30% fee to the honest disputer + TransferHelper.safeTransferNative(inference.creator, inference.value); //Refund to user inferences[_inferId].status = InferenceStatus.Killed; @@ -787,9 +833,9 @@ ReentrancyGuardUpgradeable { } else { // disputing address can be miner or validator address disputer = inference.disputingAddress; - if (minerAddresses.hasValue(disputer)) { + if (minerAddresses.hasValue(disputer) && !disputedInfer.isValidatorDispute) { _slashMiner(disputer, true); - } else if (validatorAddresses.hasValue(disputer)) { + } else if (validatorAddresses.hasValue(disputer) && disputedInfer.isValidatorDispute) { _slashValidator(disputer, true); } @@ -799,6 +845,14 @@ ReentrancyGuardUpgradeable { } } + function _checkAssignedValidatorSubmit(uint256 _inferId, bool _isDisputeValid) internal virtual { + if (validatingAssignments[_inferId].result == ValidationResult.Nil) { + _slashValidator(validatingAssignments[_inferId].assignedValidator, false); + } else if (_isDisputeValid && validatingAssignments[_inferId].result == ValidationResult.NoDispute) { + _slashValidator(validatingAssignments[_inferId].assignedValidator, true); + } + } + // Pruning when validator lazy to vote function _cullInactiveValidator(uint256 _inferId) internal { address modelAddr = inferences[_inferId].modelAddress; diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 45542ed..7792109 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -13,6 +13,12 @@ interface IWorkerHub is IInferable { Killed } + enum ValidationResult{ + Nil, + Dispute, + NoDispute + } + struct MinerEpochState { uint256 perfReward; uint256 epochReward; @@ -90,6 +96,7 @@ interface IWorkerHub is IInferable { bool isValid; uint40 validatingExpireAt; uint40 disputingExpireAt; + bool isValidatorDispute; //Validator or Miner call cal dispute() } struct Ballot { @@ -105,8 +112,11 @@ interface IWorkerHub is IInferable { struct Boost { uint40 minerTimestamp; uint40 validatorTimestamp; - uint48 reserved1; + uint4s8 reserved1; uint128 reserved2; + struct ValidatingAssignment { + address assignedValidator; + ValidationResult result; } event MiningTimeLimitUpdate(uint40 newValue); @@ -185,7 +195,7 @@ interface IWorkerHub is IInferable { event ValidatorDeactivated(address indexed validator, address indexed modelAddress, uint40 activeTime); event FraudulentValidatorPenalized(address indexed validator, address indexed modelAddress, address indexed treasury, uint256 fine); event DisputeInference(address indexed caller, uint256 indexed inferId, uint40 now, uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp); - event NoDisputeInference(address indexed caller, uint256 indexed inferId, uint40 now, uint256 value); + event NoDisputeInference(address indexed caller, uint256 indexed inferId); event DisputeUpvote(address indexed caller, uint256 indexed inferId, uint40 now); event DisputeResolving(uint256 indexed inferId, address indexed modelAddress, bool status); diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index 76f9d86..ada88d3 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -79,6 +79,7 @@ abstract contract WorkerHubStorage is IWorkerHub { Set.Uint256Set internal disputedInferIds; mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer mapping(uint256 => Set.AddressSet) internal votersOf; // disputed inference ID => voters's address + mapping(uint256 => ValidatingAssignment) internal validatingAssignments; // infer ID => the address of validator call no_dispute // mapping(address => Set.Uint256Set) internal disputedInfersOf; //voter's address => disputed inference id uint256[93] private __gap; From d03cc6d760540f42ecf6ede132f1af25fc75e39c Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Tue, 21 May 2024 11:52:51 +0700 Subject: [PATCH 11/13] Update code related to disputing --- contracts/WorkerHub.sol | 37 +++++++++++++------------ contracts/storages/WorkerHubStorage.sol | 2 +- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/contracts/WorkerHub.sol b/contracts/WorkerHub.sol index d9d5971..9e91ef4 100644 --- a/contracts/WorkerHub.sol +++ b/contracts/WorkerHub.sol @@ -624,14 +624,25 @@ ReentrancyGuardUpgradeable { if (!validatorAddressesByModel[modelAddrOfValidator].hasValue(msg.sender)) revert InvalidValidator(); } - function _beforeDispute(uint256 _inferId) internal view returns(uint40, uint40){ + // If the inference has only one submission, we allow it to be no_disputed + function _beforeNoDispute(uint256 _inferId) internal view returns(uint40, uint40) { Inference memory clonedInference = inferences[_inferId]; uint256[] memory assignmentIds = clonedInference.assignments; - if (assignmentIds.length == 1) revert LoneSubmissionNoDispute(); if (assignmentIds.length == 0) revert SubmissionsEmpty(); if (clonedInference.status != InferenceStatus.Solving) revert InvalidInferenceStatus(); + // Verify if this inference has been disputed + if(disputedInferIds.hasValue(_inferId)) revert InferenceAlreadyDisputed(); + + //TODO check only assigned validator call this function // TODO: mr. @kochou assign assignedValidator in the assign validator func. + if (validatingAssignments[_inferId].assignedValidator != msg.sender) revert InvalidValidator(); + + // Verify the msg.sender has already been dispute/no_disputed + if (validatingAssignments[_inferId].result != ValidationResult.Nil) revert ("Do not allowed to re-submit the validation task"); + + //TODO: Check whether msg.sender is the assigned validator + uint40 validateExpireTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit); uint40 disputeExpiredTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit + disputingTimeLimit); @@ -642,25 +653,14 @@ ReentrancyGuardUpgradeable { return (validateExpireTimestamp, disputeExpiredTimestamp); } - // If the inference has only one submission, we allow it to be no_disputed - function _beforeNoDispute(uint256 _inferId) internal view returns(uint40, uint40) { + function _beforeDispute(uint256 _inferId) internal view returns(uint40, uint40){ Inference memory clonedInference = inferences[_inferId]; uint256[] memory assignmentIds = clonedInference.assignments; + if (assignmentIds.length == 1) revert LoneSubmissionNoDispute(); if (assignmentIds.length == 0) revert SubmissionsEmpty(); if (clonedInference.status != InferenceStatus.Solving) revert InvalidInferenceStatus(); - // Verify if this inference has been disputed - if(disputedInferIds.hasValue(_inferId)) revert InferenceAlreadyDisputed(); - - //TODO check only assigned validator call this function - if (validatingAssignments[_inferId].assignedValidator != msg.sender) revert InvalidValidator(); - - // Verify the msg.sender has already been dispute/no_disputed - if (validatingAssignments[_inferId].result != ValidationResult.Nil) revert ("Do not allowed to re-submit the validation task"); - - //TODO: Check whether msg.sender is the assigned validator - uint40 validateExpireTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit); uint40 disputeExpiredTimestamp = uint40(clonedInference.expiredAt + validatingTimeLimit + disputingTimeLimit); @@ -677,7 +677,7 @@ ReentrancyGuardUpgradeable { _checkAvailableValidator(); _beforeNoDispute(_inferId); - validatingAssignments[_inferId].result != ValidationResult.NoDispute; //Record the no_dispute request from the assigned validator + validatingAssignments[_inferId].result = ValidationResult.NoDispute; //Record the no_dispute request from the assigned validator // Inference storage inference = inferences[_inferId]; // inference.status = InferenceStatus.Solved; @@ -692,10 +692,11 @@ ReentrancyGuardUpgradeable { function disputeInfer(uint256 _inferId, bool useValidatorRole) public virtual { _updateEpoch(); _checkAvailableWorker(); + _beforeDispute(_inferId); (uint40 validateExpireTimestamp, uint40 disputeExpiredTimestamp) = _beforeDispute(_inferId); - // TODO: if the msg.sender is the assigned validator, we have to record this request + // if the msg.sender is the assigned validator, we have to record this request if (validatingAssignments[_inferId].assignedValidator == msg.sender && validatingAssignments[_inferId].result != ValidationResult.Nil) revert ("Do not allowed to re-submit the validation task"); @@ -771,7 +772,7 @@ ReentrancyGuardUpgradeable { emit DisputeUpvote(msg.sender, _inferId, uint40(block.timestamp)); - //TODO: If the reaction time expires but the number of ballots is less than 2/3, + //TODO: (out of date) If the reaction time expires but the number of ballots is less than 2/3, // should we extend the waiting time for validators or slash inactive validators and initiate a new vote? } diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index ada88d3..62eed51 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -79,7 +79,7 @@ abstract contract WorkerHubStorage is IWorkerHub { Set.Uint256Set internal disputedInferIds; mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer mapping(uint256 => Set.AddressSet) internal votersOf; // disputed inference ID => voters's address - mapping(uint256 => ValidatingAssignment) internal validatingAssignments; // infer ID => the address of validator call no_dispute + mapping(uint256 => ValidatingAssignment) internal validatingAssignments; // infer ID => the submission record of the assigned validator // mapping(address => Set.Uint256Set) internal disputedInfersOf; //voter's address => disputed inference id uint256[93] private __gap; From 984227d8953d0de6e3892e223856465bd4e06790 Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Tue, 21 May 2024 11:52:51 +0700 Subject: [PATCH 12/13] Update code related to disputing --- contracts/storages/WorkerHubStorage.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/storages/WorkerHubStorage.sol b/contracts/storages/WorkerHubStorage.sol index 62eed51..fd98e55 100644 --- a/contracts/storages/WorkerHubStorage.sol +++ b/contracts/storages/WorkerHubStorage.sol @@ -77,10 +77,10 @@ abstract contract WorkerHubStorage is IWorkerHub { //Dispute structures Set.Uint256Set internal disputedInferIds; - mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer + mapping(uint256 => DisputedInfer) internal disputedInfers; // inferId => DisputedInfer detail mapping(uint256 => Set.AddressSet) internal votersOf; // disputed inference ID => voters's address - mapping(uint256 => ValidatingAssignment) internal validatingAssignments; // infer ID => the submission record of the assigned validator + mapping(uint256 => ValidatingAssignment) internal validatingAssignments; // infer ID => the validating task of the assigned validator // mapping(address => Set.Uint256Set) internal disputedInfersOf; //voter's address => disputed inference id - uint256[93] private __gap; + uint256[92] private __gap; } From eca44172d4b50b8a3da9f5616b9f4e3d93de310b Mon Sep 17 00:00:00 2001 From: kelvin2608 <2608@newbitcoincity.com> Date: Wed, 22 May 2024 11:22:07 +0700 Subject: [PATCH 13/13] fix bug --- contracts/interfaces/IWorkerHub.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IWorkerHub.sol b/contracts/interfaces/IWorkerHub.sol index 7792109..4f32c8e 100644 --- a/contracts/interfaces/IWorkerHub.sol +++ b/contracts/interfaces/IWorkerHub.sol @@ -112,8 +112,10 @@ interface IWorkerHub is IInferable { struct Boost { uint40 minerTimestamp; uint40 validatorTimestamp; - uint4s8 reserved1; + uint48 reserved1; uint128 reserved2; + } + struct ValidatingAssignment { address assignedValidator; ValidationResult result;