diff --git a/contracts/ConstantsHolder.sol b/contracts/ConstantsHolder.sol index f65f316f..c842b0df 100644 --- a/contracts/ConstantsHolder.sol +++ b/contracts/ConstantsHolder.sol @@ -81,9 +81,9 @@ contract ConstantsHolder is Permissions, IConstantsHolder { uint256 public constant ALRIGHT_DELTA = 134161; uint256 public constant BROADCAST_DELTA = 177490; uint256 public constant COMPLAINT_BAD_DATA_DELTA = 80995; - uint256 public constant PRE_RESPONSE_DELTA = 100061; - uint256 public constant COMPLAINT_DELTA = 106611; - uint256 public constant RESPONSE_DELTA = 48132; + uint256 public constant PRE_RESPONSE_DELTA = 114511; + uint256 public constant COMPLAINT_DELTA = 203288; + uint256 public constant RESPONSE_DELTA = 55002; // MSR - Minimum staking requirement uint256 public msr; diff --git a/contracts/NodeRotation.sol b/contracts/NodeRotation.sol index 517bbc55..acd21fbf 100644 --- a/contracts/NodeRotation.sol +++ b/contracts/NodeRotation.sol @@ -198,7 +198,7 @@ contract NodeRotation is Permissions, INodeRotation { ) public override - allowTwo("SkaleDKG", "SkaleManager") + allowThree("SkaleDKG", "SkaleManager", "Schains") returns (uint256 newNode) { ISchainsInternal schainsInternal = ISchainsInternal(contractManager.getContract("SchainsInternal")); @@ -297,6 +297,7 @@ contract NodeRotation is Permissions, INodeRotation { } leavingHistory[nodeIndex].push(LeavingHistory({schainHash: schainHash, finishedRotation: finishTimestamp})); require(_rotations[schainHash].newNodeIndexes.add(newNodeIndex), "New node was already added"); + _rotations[schainHash].nodeIndex = nodeIndex; _rotations[schainHash].newNodeIndex = newNodeIndex; _rotations[schainHash].rotationCounter++; _rotations[schainHash].previousNodes[newNodeIndex] = nodeIndex; diff --git a/contracts/Nodes.sol b/contracts/Nodes.sol index 65e7160b..7b899c7b 100644 --- a/contracts/Nodes.sol +++ b/contracts/Nodes.sol @@ -732,6 +732,26 @@ contract Nodes is Permissions, INodes { return nodeExtras[nodeIndex].lastChangeIpTime; } + function isNodeVisible(uint256 nodeIndex) + external + view + override + checkNodeExists(nodeIndex) + returns (bool visible) + { + return !_invisible[nodeIndex]; + } + + function getFreeSpace(uint256 nodeIndex) + external + view + override + checkNodeExists(nodeIndex) + returns (uint8 freeSpace) + { + return spaceOfNodes[nodeIndex].freeSpace; + } + /** * @dev Returns the Validator ID for a given node. */ diff --git a/contracts/Schains.sol b/contracts/Schains.sol index edc173c2..04f796b0 100644 --- a/contracts/Schains.sol +++ b/contracts/Schains.sol @@ -200,7 +200,13 @@ contract Schains is Permissions, ISchains { ISchainsInternal schainsInternal = ISchainsInternal( contractManager.getContract("SchainsInternal")); require(schainsInternal.isAnyFreeNode(schainHash), "No free Nodes for new group formation"); - uint256 newNodeIndex = nodeRotation.selectNodeToGroup(schainHash); + uint256 newNodeIndex = nodeRotation.rotateNode( + skaleDKG.pendingToBeReplaced(schainHash), + schainHash, + false, + true + ); + skaleDKG.resetPendingToBeReplaced(schainHash); skaleDKG.openChannel(schainHash); emit NodeAdded(schainHash, newNodeIndex); } diff --git a/contracts/SchainsInternal.sol b/contracts/SchainsInternal.sol index 32e5c1ac..bb34cae0 100644 --- a/contracts/SchainsInternal.sol +++ b/contracts/SchainsInternal.sol @@ -24,13 +24,13 @@ pragma solidity 0.8.17; import { EnumerableSetUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol"; -import { ISkaleDKG } from "@skalenetwork/skale-manager-interfaces/ISkaleDKG.sol"; import { INodes } from "@skalenetwork/skale-manager-interfaces/INodes.sol"; +import { ISkaleDKG } from "@skalenetwork/skale-manager-interfaces/ISkaleDKG.sol"; -import { IPruningSchainsInternal } from "./interfaces/IPruningSchainInternal.sol"; -import { Permissions } from "./Permissions.sol"; import { ConstantsHolder } from "./ConstantsHolder.sol"; +import { IPruningSchainsInternal } from "./interfaces/IPruningSchainInternal.sol"; import { IRandom, Random } from "./utils/Random.sol"; +import { Permissions } from "./Permissions.sol"; /** @@ -182,29 +182,6 @@ contract SchainsInternal is Permissions, IPruningSchainsInternal { return _generateGroup(schainHash, numberOfNodes); } - /** - * @dev Allows Schains contract to change the Schain lifetime through - * an additional SKL token deposit. - * - * Requirements: - * - * - Message sender is Schains smart contract - * - Schain must exist - */ - function changeLifetime( - bytes32 schainHash, - uint256 lifetime, - uint256 deposit - ) - external - override - allow("Schains") - schainExists(schainHash) - { - schains[schainHash].deposit = schains[schainHash].deposit + deposit; - schains[schainHash].lifetime = schains[schainHash].lifetime + lifetime; - } - /** * @dev Allows Schains contract to remove an schain from the network. * Generally schains are not removed from the system; instead they are @@ -755,9 +732,18 @@ contract SchainsInternal is Permissions, IPruningSchainsInternal { * - Schain must exist */ function isAnyFreeNode(bytes32 schainHash) external view override schainExists(schainHash) returns (bool free) { + // TODO: + // Move this function to Nodes? INodes nodes = INodes(contractManager.getContract("Nodes")); uint8 space = schains[schainHash].partOfNode; - return nodes.countNodesWithFreeSpace(space) > 0; + uint256 numberOfCandidates = nodes.countNodesWithFreeSpace(space); + for (uint256 i = 0; i < _schainToExceptionNodes[schainHash].length; i++) { + uint256 nodeIndex = _schainToExceptionNodes[schainHash][i]; + if (space <= nodes.getFreeSpace(nodeIndex) && nodes.isNodeVisible(nodeIndex)) { + --numberOfCandidates; + } + } + return numberOfCandidates > 0; } /** diff --git a/contracts/SkaleDKG.sol b/contracts/SkaleDKG.sol index 64487423..38f28b1d 100644 --- a/contracts/SkaleDKG.sol +++ b/contracts/SkaleDKG.sol @@ -71,6 +71,8 @@ contract SkaleDKG is Permissions, ISkaleDKG { mapping(bytes32 => uint256) private _badNodes; + mapping(bytes32 => uint256) public override pendingToBeReplaced; + modifier correctGroup(bytes32 schainHash) { require(channels[schainHash].active, "Group is not created"); _; @@ -320,6 +322,10 @@ contract SkaleDKG is Permissions, ISkaleDKG { _badNodes[schainHash] = nodeIndex; } + function resetPendingToBeReplaced(bytes32 schainHash) external override allow("Schains") { + delete pendingToBeReplaced[schainHash]; + } + function finalizeSlashing(bytes32 schainHash, uint256 badNode) external override allow("SkaleDKG") { INodeRotation nodeRotation = INodeRotation(contractManager.getContract("NodeRotation")); ISchainsInternal schainsInternal = ISchainsInternal( @@ -328,7 +334,6 @@ contract SkaleDKG is Permissions, ISkaleDKG { emit BadGuy(badNode); emit FailedDKG(schainHash); - schainsInternal.makeSchainNodesInvisible(schainHash); if (schainsInternal.isAnyFreeNode(schainHash)) { uint256 newNode = nodeRotation.rotateNode( badNode, @@ -338,14 +343,10 @@ contract SkaleDKG is Permissions, ISkaleDKG { ); emit NewGuy(newNode); } else { + pendingToBeReplaced[schainHash] = badNode; _openChannel(schainHash); - schainsInternal.removeNodeFromSchain( - badNode, - schainHash - ); channels[schainHash].active = false; } - schainsInternal.makeSchainNodesVisible(schainHash); IPunisher(contractManager.getPunisher()).slash( INodes(contractManager.getContract("Nodes")).getValidatorId(badNode), ISlashingTable(contractManager.getContract("SlashingTable")).getPenalty("FailedDKG") @@ -595,7 +596,7 @@ contract SkaleDKG is Permissions, ISkaleDKG { ); } else if (context.dkgFunction == DkgFunction.Response){ wallets.refundGasBySchain( - schainHash, payable(msg.sender), gasTotal - gasleft() - context.delta, context.isDebt + schainHash, payable(msg.sender), gasTotal - gasleft() + context.delta, context.isDebt ); } else { wallets.refundGasBySchain( diff --git a/dictionaries/libraries.txt b/dictionaries/libraries.txt index 840b6da0..f4d45d39 100644 --- a/dictionaries/libraries.txt +++ b/dictionaries/libraries.txt @@ -21,6 +21,7 @@ mulmod muln nbconvert nbformat +nomicfoundation nomiclabs pygments pyplot diff --git a/hardhat.config.ts b/hardhat.config.ts index 5a24e312..00043614 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,6 +1,6 @@ import { task, HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-chai-matchers"; import "@nomiclabs/hardhat-etherscan"; -import "@nomiclabs/hardhat-waffle"; import "@openzeppelin/hardhat-upgrades"; import '@typechain/hardhat' import "solidity-coverage"; diff --git a/package.json b/package.json index 359d3eaf..9bdb34c9 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^4.9.3", "@openzeppelin/hardhat-upgrades": "^1.14.0", - "@skalenetwork/skale-manager-interfaces": "3.0.0", + "@skalenetwork/skale-manager-interfaces": "3.0.0-develop.1", "@skalenetwork/upgrade-tools": "^2.0.1", "@typechain/hardhat": "^7.0.0", "dotenv": "^16.3.1", @@ -53,8 +53,8 @@ "hardhat": "2.11.0 - 2.16.1" }, "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "<2.0.0", "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@nomiclabs/hardhat-waffle": "^2.0.2", "@typechain/ethers-v5": "^11.1.1", "@types/chai": "^4.3.6", "@types/chai-almost": "^1.0.1", @@ -64,6 +64,7 @@ "@types/mocha": "^9.1.1", "@types/node": "^20.8.7", "@types/sinon-chai": "^3.2.9", + "@types/underscore": "^1.11.15", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "bignumber.js": "^9.1.2", diff --git a/test/SchainsInternal.ts b/test/SchainsInternal.ts index ba8b20c7..12e85dbf 100644 --- a/test/SchainsInternal.ts +++ b/test/SchainsInternal.ts @@ -143,13 +143,6 @@ describe("SchainsInternal", () => { totalResources.should.be.equal(64); }); - it("should change schain lifetime", async () => { - await schainsInternal.changeLifetime(schainNameHash, 7, 8); - const schain = await schainsInternal.schains(schainNameHash); - schain.lifetime.should.be.equal(12); - schain.deposit.should.be.equal(13); - }); - describe("on registered schain", () => { const nodeIndex = 0; const numberOfNewSchains = 5 diff --git a/test/SkaleDKG.ts b/test/SkaleDKG.ts index 6924264c..d186d0e4 100644 --- a/test/SkaleDKG.ts +++ b/test/SkaleDKG.ts @@ -1409,7 +1409,7 @@ describe("SkaleDKG", () => { badEncryptedSecretKeyContributions[indexes[0]] ) ); - + const response = await skaleDKG.connect(validators[0].nodeAddress).response( stringKeccak256(schainName), 0, @@ -1498,7 +1498,7 @@ describe("SkaleDKG", () => { ), "Broadcast 1" ); - + await reimbursed( await skaleDKG.connect(validators[1].nodeAddress).broadcast( stringKeccak256(schainName), @@ -1509,7 +1509,7 @@ describe("SkaleDKG", () => { ), "Broadcast 2" ); - + const complaintBadData = await skaleDKG.connect(validators[1].nodeAddress).complaintBadData( stringKeccak256(schainName), 1, @@ -1533,7 +1533,7 @@ describe("SkaleDKG", () => { ), "Pre response" ); - + const responseTx = await skaleDKG.connect(validators[0].nodeAddress).response( stringKeccak256(schainName), 0, @@ -1928,7 +1928,7 @@ describe("SkaleDKG", () => { ), "Alright" ); - + alrightPoss = await skaleDKG.connect(validators[index].nodeAddress).isAlrightPossible( stringKeccak256("New16NodeSchain"), i @@ -2381,7 +2381,7 @@ describe("SkaleDKG", () => { accusedNode ) ); - + const complaint = await skaleDKG.connect(validators[0].nodeAddress).complaint( stringKeccak256("New16NodeSchain"), 8, @@ -2454,8 +2454,8 @@ describe("SkaleDKG", () => { rotation.rotationCounter ); } - const accusedNode = "15"; - const complaintNode = "7"; + const accusedNode = 15; + const complaintNode = 7; await skipTime(1800); await reimbursed( await skaleDKG.connect(validators[0].nodeAddress).complaint( @@ -2465,7 +2465,8 @@ describe("SkaleDKG", () => { ) ); const space = await nodes.spaceOfNodes(accusedNode); - space.freeSpace.should.be.equal(128); + // The node is still a part of schain because can't be replaced + space.freeSpace.should.be.equal(0); const complaint = await skaleDKG.connect(validators[0].nodeAddress).complaint( stringKeccak256("New16NodeSchain"), @@ -2486,11 +2487,18 @@ describe("SkaleDKG", () => { domainName: "some.domain.name" } ); + const createdNode = 16; await schains.restartSchainCreation("New16NodeSchain"); + rotation = await nodeRotation.getRotation(stringKeccak256("New16NodeSchain")); + + expect(rotation.rotationCounter).to.be.equal(1); + expect(rotation.nodeIndex).to.be.equal(accusedNode); + expect(rotation.newNodeIndex).to.be.equal(createdNode); + for (let i = 0; i < 17; i++) { - if (i.toString() === accusedNode) { + if (i === accusedNode) { continue; } let index = 0; @@ -2507,7 +2515,7 @@ describe("SkaleDKG", () => { } let comPubKey; for (let i = 0; i < 17; i++) { - if (i.toString() === accusedNode) { + if (i === accusedNode) { continue; } comPubKey = await keyStorage.getCommonPublicKey(stringKeccak256("New16NodeSchain")); @@ -2605,7 +2613,7 @@ describe("SkaleDKG", () => { accusedNode ) ); - + const complaint = await skaleDKG.connect(validators[0].nodeAddress).complaint( stringKeccak256("New16NodeSchain"), 8, diff --git a/yarn.lock b/yarn.lock index 33763ce6..39b3979d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1287,6 +1287,17 @@ mcl-wasm "^0.7.1" rustbn.js "~0.2.0" +"@nomicfoundation/hardhat-chai-matchers@<2.0.0": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.6.tgz#72a2e312e1504ee5dd73fe302932736432ba96bc" + integrity sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@types/chai-as-promised" "^7.1.3" + chai-as-promised "^7.1.1" + deep-eql "^4.0.1" + ordinal "^1.0.3" + "@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz#4c858096b1c17fe58a474fe81b46815f93645c15" @@ -1374,14 +1385,6 @@ table "^6.8.0" undici "^5.14.0" -"@nomiclabs/hardhat-waffle@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.2.tgz#6030aa6fd9ea05327bf79d1107356af906d8b1e4" - integrity sha512-dnhry6Bj15O8L3pBksTuXfr4RAUIf+BxRxWJXiu+ioSawcQaOcNF4gfMxn6ik0auk3zrsAJLA6m9vqe87d4xvg== - dependencies: - "@types/sinon-chai" "^3.2.3" - "@types/web3" "1.0.19" - "@oclif/command@^1.8.0": version "1.8.16" resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.16.tgz#bea46f81b2061b47e1cda318a0b923e62ca4cc0c" @@ -1752,10 +1755,10 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@skalenetwork/skale-manager-interfaces@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@skalenetwork/skale-manager-interfaces/-/skale-manager-interfaces-3.0.0.tgz#6ea1a0eae4a1684f6985a96057151f89adb77bf4" - integrity sha512-SyVA23+Jec72GxdqgiFwwEfF/PZrD2bFfI4rlpld1U+MURMFKm99eALRaKAIyqU3X3uH4ujNALjnJvCBE3DC8A== +"@skalenetwork/skale-manager-interfaces@3.0.0-develop.1": + version "3.0.0-develop.1" + resolved "https://registry.yarnpkg.com/@skalenetwork/skale-manager-interfaces/-/skale-manager-interfaces-3.0.0-develop.1.tgz#4efb397526039973d20d3b0bebdd4f746dddc987" + integrity sha512-0+vsOEwr7sGd+t3glh42cYRCOeTOqo/QqKB1AjnO4NSeQxm7HN8zfiBnLR2t3d2mlTv3ZpS/BSWN2OKpgZkbEQ== "@skalenetwork/upgrade-tools@^2.0.1": version "2.0.1" @@ -1854,6 +1857,13 @@ dependencies: "@types/chai" "*" +"@types/chai-as-promised@^7.1.3": + version "7.1.8" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9" + integrity sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw== + dependencies: + "@types/chai" "*" + "@types/chai-as-promised@^7.1.6": version "7.1.6" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.6.tgz#3b08cbe1e7206567a480dc6538bade374b19e4e1" @@ -1979,7 +1989,7 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.1.tgz#0480eeb7221eb9bc398ad7432c9d7e14b1a5a367" integrity sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg== -"@types/sinon-chai@^3.2.3", "@types/sinon-chai@^3.2.9": +"@types/sinon-chai@^3.2.9": version "3.2.9" resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.9.tgz#71feb938574bbadcb176c68e5ff1a6014c5e69d4" integrity sha512-/19t63pFYU0ikrdbXKBWj9PCdnKyTd0Qkz0X91Ta081cYsq90OxYdcWwK/dwEoDa6dtXgj2HJfmzgq+QZTHdmQ== @@ -1994,18 +2004,10 @@ dependencies: "@sinonjs/fake-timers" "^7.1.0" -"@types/underscore@*": - version "1.11.4" - resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.11.4.tgz#62e393f8bc4bd8a06154d110c7d042a93751def3" - integrity sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg== - -"@types/web3@1.0.19": - version "1.0.19" - resolved "https://registry.yarnpkg.com/@types/web3/-/web3-1.0.19.tgz#46b85d91d398ded9ab7c85a5dd57cb33ac558924" - integrity sha512-fhZ9DyvDYDwHZUp5/STa9XW2re0E8GxoioYJ4pEUZ13YHpApSagixj7IAdoYH5uAK+UalGq6Ml8LYzmgRA/q+A== - dependencies: - "@types/bn.js" "*" - "@types/underscore" "*" +"@types/underscore@^1.11.15": + version "1.11.15" + resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.11.15.tgz#29c776daecf6f1935da9adda17509686bf979947" + integrity sha512-HP38xE+GuWGlbSRq9WrZkousaQ7dragtZCruBVMi0oX1migFZavZ3OROKHSkNp/9ouq82zrWtZpg18jFnVN96g== "@typescript-eslint/eslint-plugin@^5.62.0": version "5.62.0" @@ -4325,7 +4327,7 @@ deep-eql@^2.0.2: dependencies: type-detect "^3.0.0" -deep-eql@^4.1.2: +deep-eql@^4.0.1, deep-eql@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== @@ -8577,6 +8579,11 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" +ordinal@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ordinal/-/ordinal-1.0.3.tgz#1a3c7726a61728112f50944ad7c35c06ae3a0d4d" + integrity sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ== + os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"