From 5548d2c69ba9aa7bf7fb626618a5960a3eb9c62c Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 29 May 2020 18:11:06 +0100 Subject: [PATCH] Try and harden a test, fixing an inconsistency --- .../ReputationMiningCycle.sol | 72 ------ .../ReputationMiningCycleCommon.sol | 3 +- docs/_Interface_IColonyNetwork.md | 20 +- docs/_Interface_ITokenLocking.md | 8 +- helpers/test-helper.js | 62 ++++- .../reputation-basic-functionality.js | 2 +- test/extensions/funding-queue.js | 2 +- .../dispute-resolution-misbehaviour.js | 236 +++++++++--------- test/reputation-system/happy-paths.js | 8 +- .../client-auto-functionality.js | 2 +- .../root-hash-submissions.js | 52 +++- 11 files changed, 248 insertions(+), 219 deletions(-) diff --git a/contracts/reputationMiningCycle/ReputationMiningCycle.sol b/contracts/reputationMiningCycle/ReputationMiningCycle.sol index 99c40d68ae..f90dc8c26c 100644 --- a/contracts/reputationMiningCycle/ReputationMiningCycle.sol +++ b/contracts/reputationMiningCycle/ReputationMiningCycle.sol @@ -505,78 +505,6 @@ contract ReputationMiningCycle is ReputationMiningCycleCommon { // Internal functions ///////////////////////// - function processBinaryChallengeSearchResponse( - uint256 round, - uint256 idx, - bytes memory jhIntermediateValue, - bytes32[2] memory lastSiblings - ) internal - { - disputeRounds[round][idx].lastResponseTimestamp = now; - disputeRounds[round][idx].challengeStepCompleted += 1; - // Save our intermediate hash - bytes32 intermediateReputationHash; - uint256 intermediateReputationNNodes; - assembly { - intermediateReputationHash := mload(add(jhIntermediateValue, 0x20)) - intermediateReputationNNodes := mload(add(jhIntermediateValue, 0x40)) - } - disputeRounds[round][idx].intermediateReputationHash = intermediateReputationHash; - disputeRounds[round][idx].intermediateReputationNNodes = intermediateReputationNNodes; - - disputeRounds[round][idx].hash1 = lastSiblings[0]; - disputeRounds[round][idx].hash2 = lastSiblings[1]; - - uint256 opponentIdx = getOpponentIdx(idx); - if (disputeRounds[round][opponentIdx].challengeStepCompleted == disputeRounds[round][idx].challengeStepCompleted ) { - // Our opponent answered this challenge already. - // Compare our intermediateReputationHash to theirs to establish how to move the bounds. - processBinaryChallengeSearchStep(round, idx); - } - } - - function processBinaryChallengeSearchStep(uint256 round, uint256 idx) internal { - uint256 opponentIdx = getOpponentIdx(idx); - uint256 searchWidth = (disputeRounds[round][idx].upperBound - disputeRounds[round][idx].lowerBound) + 1; - uint256 searchWidthNextPowerOfTwo = nextPowerOfTwoInclusive(searchWidth); - if ( - disputeRounds[round][opponentIdx].hash1 == disputeRounds[round][idx].hash1 - ) - { - disputeRounds[round][idx].lowerBound += searchWidthNextPowerOfTwo/2; - disputeRounds[round][opponentIdx].lowerBound += searchWidthNextPowerOfTwo/2; - disputeRounds[round][idx].targetHashDuringSearch = disputeRounds[round][idx].hash2; - disputeRounds[round][opponentIdx].targetHashDuringSearch = disputeRounds[round][opponentIdx].hash2; - } else { - disputeRounds[round][idx].upperBound -= (searchWidth - searchWidthNextPowerOfTwo/2); - disputeRounds[round][opponentIdx].upperBound -= (searchWidth - searchWidthNextPowerOfTwo/2); - disputeRounds[round][idx].targetHashDuringSearch = disputeRounds[round][idx].hash1; - disputeRounds[round][opponentIdx].targetHashDuringSearch = disputeRounds[round][opponentIdx].hash1; - } - // We need to keep the intermediate hashes so that we can figure out what type of dispute we are resolving later - // If the number of nodes in the reputation state are different, then we are disagreeing on whether this log entry - // corresponds to an existing reputation entry or not. - // If the hashes are different, then it's a calculation error. - // However, the intermediate hashes saved might not be the ones that correspond to the first disagreement, based on how exactly the last - // step of the binary challenge came to be. - - // If complete, mark that the binary search is completed (but the intermediate hashes may or may not be correct) by setting - // challengeStepCompleted to the maximum it could be for the number of nodes we had to search through, plus one to indicate - // they've submitted their jrh - Submission storage submission = reputationHashSubmissions[disputeRounds[round][idx].firstSubmitter]; - if (disputeRounds[round][idx].lowerBound == disputeRounds[round][idx].upperBound) { - if (2**(disputeRounds[round][idx].challengeStepCompleted-1) < submission.jrhNNodes) { - disputeRounds[round][idx].challengeStepCompleted += 1; - disputeRounds[round][opponentIdx].challengeStepCompleted += 1; - } - } - - // Our opponent responded to this step of the challenge before we did, so we should - // reset their 'last response' time to now, as they aren't able to respond - // to the next challenge before they know what it is! - disputeRounds[round][opponentIdx].lastResponseTimestamp = now; - } - function checkJRHProof1(bytes32 jrh, uint256 branchMask1, bytes32[] memory siblings1, uint256 reputationRootHashNNodes) internal view { // Proof 1 needs to prove that they started with the current reputation root hash bytes32 reputationRootHash = IColonyNetwork(colonyNetworkAddress).getReputationRootHash(); diff --git a/contracts/reputationMiningCycle/ReputationMiningCycleCommon.sol b/contracts/reputationMiningCycle/ReputationMiningCycleCommon.sol index 59d890ba07..c65cd4282d 100644 --- a/contracts/reputationMiningCycle/ReputationMiningCycleCommon.sol +++ b/contracts/reputationMiningCycle/ReputationMiningCycleCommon.sol @@ -52,7 +52,6 @@ contract ReputationMiningCycleCommon is ReputationMiningCycleStorage, PatriciaTr } function disputeRewardSize() internal returns (uint256) { - // TODO: Is this worth calculating once, and then saving? Seems quite likely. uint256 nLogEntries = reputationUpdateLog.length; // If there's no log, it must be one of the first two reputation cycles - no reward. @@ -132,7 +131,7 @@ contract ReputationMiningCycleCommon is ReputationMiningCycleStorage, PatriciaTr function responsePossible(disputeStages stage, uint256 since) internal view returns (bool) { uint256 delta = sub(now, since); // I don't believe this should ever be possible to underflow... - if (delta < SUBMITTER_ONLY_WINDOW) { + if (delta <= SUBMITTER_ONLY_WINDOW) { // require user made a submission if (reputationHashSubmissions[msg.sender].proposedNewRootHash == bytes32(0x00)) { return false; diff --git a/docs/_Interface_IColonyNetwork.md b/docs/_Interface_IColonyNetwork.md index 8fd63a58e7..2473f6ef86 100644 --- a/docs/_Interface_IColonyNetwork.md +++ b/docs/_Interface_IColonyNetwork.md @@ -116,19 +116,15 @@ Used by a user to claim any mining rewards due to them. This will place them in ### `createColony` -Overload of the simpler `createColony` -- creates a new colony in the network with a variety of options +Creates a new colony in the network, at version 3 -*Note: For the colony to mint tokens, token ownership must be transferred to the new colony* +*Note: This is now deprecated and will be removed in a future version* **Parameters** |Name|Type|Description| |---|---|---| -|_tokenAddress|address|Address of an ERC20 token to serve as the colony token -|_version|uint256|The version of colony to deploy (pass 0 for the current version) -|_colonyName|string|The label to register (if null, no label is registered) -|_orbitdb|string|The path of the orbitDB database associated with the user profile -|_useExtensionManager|bool|If true, give the ExtensionManager the root role in the colony +|_tokenAddress|address|Address of an ERC20 token to serve as the colony token. **Return Parameters** @@ -138,15 +134,19 @@ Overload of the simpler `createColony` -- creates a new colony in the network wi ### `createColony` -Creates a new colony in the network, at version 3 +Overload of the simpler `createColony` -- creates a new colony in the network with a variety of options -*Note: This is now deprecated and will be removed in a future version* +*Note: For the colony to mint tokens, token ownership must be transferred to the new colony* **Parameters** |Name|Type|Description| |---|---|---| -|_tokenAddress|address|Address of an ERC20 token to serve as the colony token. +|_tokenAddress|address|Address of an ERC20 token to serve as the colony token +|_version|uint256|The version of colony to deploy (pass 0 for the current version) +|_colonyName|string|The label to register (if null, no label is registered) +|_orbitdb|string|The path of the orbitDB database associated with the user profile +|_useExtensionManager|bool|If true, give the ExtensionManager the root role in the colony **Return Parameters** diff --git a/docs/_Interface_ITokenLocking.md b/docs/_Interface_ITokenLocking.md index 8264dc5bba..389547a37a 100644 --- a/docs/_Interface_ITokenLocking.md +++ b/docs/_Interface_ITokenLocking.md @@ -308,7 +308,7 @@ Increments the lock counter to `_lockId` for the `_user` if user's lock count is ### `withdraw` -Withdraw `_amount` of deposited tokens. Can only be called if user tokens are not locked. +DEPRECATED Withdraw `_amount` of deposited tokens. Can only be called if user tokens are not locked. **Parameters** @@ -317,12 +317,11 @@ Withdraw `_amount` of deposited tokens. Can only be called if user tokens are no |---|---|---| |_token|address|Address of the token to withdraw from |_amount|uint256|Amount to withdraw -|_force|bool|Pass true to forcibly unlock the token ### `withdraw` -DEPRECATED Withdraw `_amount` of deposited tokens. Can only be called if user tokens are not locked. +Withdraw `_amount` of deposited tokens. Can only be called if user tokens are not locked. **Parameters** @@ -330,4 +329,5 @@ DEPRECATED Withdraw `_amount` of deposited tokens. Can only be called if user to |Name|Type|Description| |---|---|---| |_token|address|Address of the token to withdraw from -|_amount|uint256|Amount to withdraw \ No newline at end of file +|_amount|uint256|Amount to withdraw +|_force|bool|Pass true to forcibly unlock the token \ No newline at end of file diff --git a/helpers/test-helper.js b/helpers/test-helper.js index 3de6a931af..c212d88abe 100644 --- a/helpers/test-helper.js +++ b/helpers/test-helper.js @@ -307,12 +307,50 @@ export async function forwardTimeTo(_timestamp, test) { await forwardTime(amountToForward.toNumber(), test); } -export async function mineBlock() { +export async function mineBlock(timestamp) { return new Promise((resolve, reject) => { web3.currentProvider.send( { jsonrpc: "2.0", method: "evm_mine", + params: timestamp ? [timestamp] : [], + id: new Date().getTime(), + }, + (err) => { + if (err) { + return reject(err); + } + return resolve(); + } + ); + }); +} + +export async function stopMining() { + return new Promise((resolve, reject) => { + web3.currentProvider.send( + { + jsonrpc: "2.0", + method: "miner_stop", + params: [], + id: new Date().getTime(), + }, + (err) => { + if (err) { + return reject(err); + } + return resolve(); + } + ); + }); +} + +export async function startMining() { + return new Promise((resolve, reject) => { + web3.currentProvider.send( + { + jsonrpc: "2.0", + method: "miner_start", params: [], id: new Date().getTime(), }, @@ -326,6 +364,18 @@ export async function mineBlock() { }); } +export async function makeTxAtTimestamp(f, args, timestamp, test) { + const client = await web3GetClient(); + if (client.indexOf("TestRPC") === -1) { + test.skip(); + } + await stopMining(); + const promise = f(...args); + await mineBlock(timestamp); + await startMining(); + return promise; +} + export function bnSqrt(bn, isGreater) { let a = bn.addn(1).divn(2); let b = bn; @@ -443,7 +493,7 @@ export async function getActiveRepCycle(colonyNetwork) { } export async function advanceMiningCycleNoContest({ colonyNetwork, client, minerAddress, test }) { - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, test); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, test); const repCycle = await getActiveRepCycle(colonyNetwork); if (client !== undefined) { @@ -647,7 +697,13 @@ export async function finishReputationMiningCycle(colonyNetwork, test) { if (nUniqueSubmittedHashes.gtn(0)) { const reputationMiningWindowOpenTimestamp = await repCycle.getReputationMiningWindowOpenTimestamp(); - await forwardTimeTo(reputationMiningWindowOpenTimestamp.addn(MINING_CYCLE_DURATION).addn(SUBMITTER_ONLY_WINDOW).toNumber(), test); + await forwardTimeTo( + reputationMiningWindowOpenTimestamp + .addn(MINING_CYCLE_DURATION) + .addn(SUBMITTER_ONLY_WINDOW + 1) + .toNumber(), + test + ); const nInvalidatedHashes = await repCycle.getNInvalidatedHashes(); if (nUniqueSubmittedHashes.sub(nInvalidatedHashes).eqn(1)) { await repCycle.confirmNewHash(nUniqueSubmittedHashes.eqn(1) ? 0 : 1); // Not a general solution - only works for one or two submissions. diff --git a/test/contracts-network/reputation-basic-functionality.js b/test/contracts-network/reputation-basic-functionality.js index 1db5bbb125..f4a319c576 100644 --- a/test/contracts-network/reputation-basic-functionality.js +++ b/test/contracts-network/reputation-basic-functionality.js @@ -171,7 +171,7 @@ contract("Reputation mining - basic functionality", (accounts) => { expect(nSubmissionsForHash).to.eq.BN(1); // Cleanup - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(0); }); }); diff --git a/test/extensions/funding-queue.js b/test/extensions/funding-queue.js index 76ee9a36e4..178209388b 100644 --- a/test/extensions/funding-queue.js +++ b/test/extensions/funding-queue.js @@ -132,7 +132,7 @@ contract("Funding Queues", (accounts) => { const rootHash = await reputationTree.getRootHash(); const repCycle = await getActiveRepCycle(colonyNetwork); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.submitRootHash(rootHash, 0, "0x00", 10, { from: MINER }); await repCycle.confirmNewHash(0); }); diff --git a/test/reputation-system/dispute-resolution-misbehaviour.js b/test/reputation-system/dispute-resolution-misbehaviour.js index e55560c55c..625d1d6737 100644 --- a/test/reputation-system/dispute-resolution-misbehaviour.js +++ b/test/reputation-system/dispute-resolution-misbehaviour.js @@ -20,6 +20,7 @@ import { accommodateChallengeAndInvalidateHashViaTimeout, finishReputationMiningCycle, removeSubdomainLimit, + makeTxAtTimestamp, } from "../../helpers/test-helper"; import { @@ -121,7 +122,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const addr = await colonyNetwork.getReputationMiningCycle(true); let repCycle = await IReputationMiningCycle.at(addr); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.submitRootHash("0x00", 0, "0x00", 10, { from: MINER1 }); await repCycle.confirmNewHash(0); @@ -146,7 +147,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { // Check we can't respond to challenge before we've confirmed JRH await checkErrorRevertEthers(goodClient.respondToChallenge(), "colony-reputation-mining-binary-search-result-not-confirmed"); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); @@ -157,7 +158,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { // Check we can't respond to challenge before we've started binary search await checkErrorRevertEthers(goodClient.respondToChallenge(), "colony-reputation-binary-search-incomplete"); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.respondToBinarySearchForChallenge(); await badClient.respondToBinarySearchForChallenge(); @@ -168,7 +169,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { // Check we can't respond to challenge before we've confirmed the binary search result await checkErrorRevertEthers(goodClient.respondToChallenge(), "colony-reputation-mining-binary-search-result-not-confirmed"); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); @@ -176,11 +177,11 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await checkErrorRevertEthers(goodClient.confirmBinarySearchResult(), "colony-reputation-binary-search-result-already-confirmed"); // Cleanup - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.respondToChallenge(); - await forwardTime(MINING_CYCLE_DURATION / 6, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.invalidateHash(0, 1); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(1); }); @@ -192,7 +193,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const addr = await colonyNetwork.getReputationMiningCycle(true); let repCycle = await IReputationMiningCycle.at(addr); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.submitRootHash("0x00", 0, "0x00", 10, { from: MINER1 }); await repCycle.confirmNewHash(0); @@ -203,7 +204,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { repCycle.confirmJustificationRootHash(0, 10000, ["0x00"], ["0x00"]), "colony-reputation-mining-index-beyond-round-length" ); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); @@ -219,7 +220,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { // // Check we can't respond to challenge before we've confirmed the binary search result // await checkErrorRevertEthers(goodClient.respondToChallenge(), "colony-reputation-mining-binary-search-result-not-confirmed"); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); @@ -238,7 +239,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { ); // Cleanup - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.respondToChallenge(); await forwardTime(MINING_CYCLE_DURATION / 6, this); await repCycle.invalidateHash(0, 1); @@ -253,7 +254,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const addr = await colonyNetwork.getReputationMiningCycle(true); let repCycle = await IReputationMiningCycle.at(addr); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.submitRootHash("0x00", 0, "0x00", 10, { from: MINER1 }); await repCycle.confirmNewHash(0); @@ -285,7 +286,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await checkErrorRevert(repCycle.invalidateHash(0, 3), "colony-reputation-mining-user-ineligible-to-respond"); // Cleanup - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await accommodateChallengeAndInvalidateHash(colonyNetwork, this, goodClient, badClient, { client2: { respondToChallenge: "colony-reputation-mining-increased-reputation-value-incorrect" }, }); @@ -555,33 +556,33 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await badClient.loadState(savedHash); await submitAndForwardTimeToDispute([goodClient, badClient], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); // Incomplete binary search - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.respondToBinarySearchForChallenge(); await badClient.respondToBinarySearchForChallenge(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.respondToBinarySearchForChallenge(); await goodClient.respondToBinarySearchForChallenge(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.respondToBinarySearchForChallenge(); await goodClient.respondToBinarySearchForChallenge(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.respondToBinarySearchForChallenge(); await goodClient.respondToBinarySearchForChallenge(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.respondToBinarySearchForChallenge(); await goodClient.respondToBinarySearchForChallenge(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.respondToBinarySearchForChallenge(); await goodClient.respondToBinarySearchForChallenge(); @@ -590,7 +591,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { // Check we can't respond to challenge before we've completed the binary search await checkErrorRevertEthers(goodClient.respondToChallenge(), "colony-reputation-binary-search-incomplete"); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.respondToBinarySearchForChallenge(); // Check we can't confirm even if we're done, but our opponent isn't await checkErrorRevertEthers(goodClient.confirmBinarySearchResult(), "colony-reputation-binary-search-incomplete"); @@ -600,13 +601,13 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await checkErrorRevertEthers(goodClient.respondToChallenge(), "colony-reputation-mining-binary-search-result-not-confirmed"); // Now we can confirm - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); // Check we can't continue confirming await checkErrorRevertEthers(goodClient.respondToBinarySearchForChallenge(), "colony-reputation-mining-challenge-not-active"); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.respondToChallenge(); // Check we can't respond again await checkErrorRevertEthers(goodClient.respondToChallenge(), "colony-reputation-mining-challenge-already-responded"); @@ -631,23 +632,23 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await goodClient.submitRootHash(); await badClient.addLogContentsToReputationTree(); await badClient.submitRootHash(); - const disputeRound = await repCycle.getDisputeRound(0); - const goodEntry = disputeRound[0]; + let disputeRound = await repCycle.getDisputeRound(0); + let goodEntry = disputeRound[0]; + let timestamp = parseInt(goodEntry.lastResponseTimestamp, 10); // Forward time again so clients can start responding to challenges - await forwardTimeTo(parseInt(goodEntry.lastResponseTimestamp, 10)); - await checkErrorRevertEthers(goodClient.confirmJustificationRootHash(), "colony-reputation-mining-user-ineligible-to-respond"); + await checkErrorRevertEthers( + makeTxAtTimestamp(goodClient.confirmJustificationRootHash.bind(goodClient), [], timestamp, this), + "colony-reputation-mining-user-ineligible-to-respond" + ); // Check it cannot respond at the start of the window - await forwardTime(1, this); - await checkErrorRevertEthers(goodClient.confirmJustificationRootHash(), "colony-reputation-mining-user-ineligible-to-respond"); + await checkErrorRevertEthers( + makeTxAtTimestamp(goodClient.confirmJustificationRootHash.bind(goodClient), [], timestamp + 1, this), + "colony-reputation-mining-user-ineligible-to-respond" + ); // Check a non-miner cannot respond, even the end of the window - // There is a sort of race condition now - we need to prepare everything we need for the checkErrorRevert call now, then - // forward time, then quickly check the transation will revert. Otherwise, the miner querying takes too long and the - // transaction succeeds (because time has advanced past the SUBMITTER_ONLY_WINDOW) - // I'm not super-happy about this, but until there's a way to 'freeze' time on ganache-cli I - // think this is the best we can do. const nLogEntries = await repCycle.getReputationUpdateLogLength(); const lastLogEntry = await repCycle.getReputationUpdateLogEntry(nLogEntries.subn(1)); @@ -656,68 +657,73 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const [, siblings1] = await goodClient.justificationTree.getProof(`0x${new BN("0").toString(16, 64)}`); const [, siblings2] = await goodClient.justificationTree.getProof(`0x${totalnUpdates.toString(16, 64)}`); - await forwardTime(SUBMITTER_ONLY_WINDOW - 5, this); + timestamp += SUBMITTER_ONLY_WINDOW; await checkErrorRevert( - repCycle.confirmJustificationRootHash(0, 0, siblings1, siblings2, { from: MINER3 }), + makeTxAtTimestamp(repCycle.confirmJustificationRootHash, [0, 0, siblings1, siblings2, { from: MINER3 }], timestamp, this), "colony-reputation-mining-user-ineligible-to-respond" ); - await goodClient.confirmJustificationRootHash(); - await badClient.confirmJustificationRootHash(); + await makeTxAtTimestamp(goodClient.confirmJustificationRootHash.bind(goodClient), [], timestamp, this); + await makeTxAtTimestamp(badClient.confirmJustificationRootHash.bind(badClient), [], timestamp, this); - // Why the try-catches? Because about 1% of the time, one of these calls will fail, because the caller only is allowed in the - // last second of the submitter only window, and we still want our test suite to pass... - - // NB runBinarySearch forward time itself. - try { - await runBinarySearch(goodClient, badClient); - } catch (e) { - await forwardTime(1, this); - await runBinarySearch(goodClient, badClient); + async function binarySearchAtTimestamp(clients, targetTimestamp, test) { + // Not using Promise.all(clients.map), because we can't have the start/stop mining cross-talking with each other + // and possibly causing the timestamps to go wrong. + for (let i = 0; i < clients.length; i += 1) { + await makeTxAtTimestamp(clients[i].respondToBinarySearchForChallenge.bind(clients[i]), [], targetTimestamp, test); + } } - await forwardTime(SUBMITTER_ONLY_WINDOW - 1, this); + disputeRound = await repCycle.getDisputeRound(0); + [goodEntry] = disputeRound; - try { - await goodClient.confirmBinarySearchResult(); - } catch (e) { - await forwardTime(1, this); - await goodClient.confirmBinarySearchResult(); - } + // Check can't respond at start of window for binary search + timestamp = parseInt(goodEntry.lastResponseTimestamp, 10) + 1; - try { - await badClient.confirmBinarySearchResult(); - } catch (e) { - await forwardTime(1, this); - await badClient.confirmBinarySearchResult(); - } + await checkErrorRevertEthers( + makeTxAtTimestamp(goodClient.respondToBinarySearchForChallenge.bind(goodClient), [{ from: MINER2 }], timestamp, this), + "colony-reputation-mining-user-ineligible-to-respond" + ); - await forwardTime(SUBMITTER_ONLY_WINDOW - 1, this); - try { - await goodClient.respondToChallenge(); - } catch (e) { - await forwardTime(1, this); - await goodClient.respondToChallenge(); - } + timestamp += SUBMITTER_ONLY_WINDOW - 1; + await binarySearchAtTimestamp([goodClient, badClient], timestamp, this); + timestamp += SUBMITTER_ONLY_WINDOW; + await binarySearchAtTimestamp([goodClient, badClient], timestamp, this); + timestamp += SUBMITTER_ONLY_WINDOW; + await binarySearchAtTimestamp([goodClient, badClient], timestamp, this); - await checkErrorRevert(repCycle.invalidateHash(0, 1, { from: MINER3 }), "colony-reputation-mining-user-ineligible-to-respond"); - await forwardTime(SUBMITTER_ONLY_WINDOW - 1, this); + // Can't confirm at start of window + await checkErrorRevertEthers( + makeTxAtTimestamp(goodClient.confirmBinarySearchResult.bind(goodClient), [], timestamp + 1, this), + "colony-reputation-mining-user-ineligible-to-respond" + ); - try { - await repCycle.invalidateHash(0, 1); - } catch (e) { - await forwardTime(1, this); - await repCycle.invalidateHash(0, 1); - } + timestamp += SUBMITTER_ONLY_WINDOW; + await makeTxAtTimestamp(goodClient.confirmBinarySearchResult.bind(goodClient), [], timestamp, this); + await makeTxAtTimestamp(badClient.confirmBinarySearchResult.bind(badClient), [], timestamp, this); - await forwardTime(SUBMITTER_ONLY_WINDOW - 1, this); - try { - await repCycle.confirmNewHash(1); - } catch (e) { - await forwardTime(1, this); - await repCycle.confirmNewHash(1); - } + // Can't respond at start of window + await checkErrorRevertEthers( + makeTxAtTimestamp(goodClient.respondToChallenge.bind(goodClient), [], timestamp + 1, this), + "colony-reputation-mining-user-ineligible-to-respond" + ); + + timestamp += SUBMITTER_ONLY_WINDOW; + await makeTxAtTimestamp(goodClient.respondToChallenge.bind(goodClient), [], timestamp, this); + + timestamp += SUBMITTER_ONLY_WINDOW; + + // Can't invalidate at start of window + await checkErrorRevert( + makeTxAtTimestamp(repCycle.invalidateHash, [0, 1, { from: MINER3 }], timestamp, this), + "colony-reputation-mining-user-ineligible-to-respond" + ); + + timestamp += SUBMITTER_ONLY_WINDOW; + await makeTxAtTimestamp(repCycle.invalidateHash, [0, 1], timestamp, this); + timestamp += SUBMITTER_ONLY_WINDOW; + await makeTxAtTimestamp(repCycle.confirmNewHash, [1], timestamp, this); }); }); @@ -738,7 +744,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const [, siblings1] = await goodClient.justificationTree.getProof(`0x${new BN("0").toString(16, 64)}`); const [, siblings2] = await goodClient.justificationTree.getProof(`0x${totalnUpdates.toString(16, 64)}`); const [round, index] = await goodClient.getMySubmissionRoundAndIndex(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); // Mess up proofs in the reverse of the order they're checked in. @@ -774,12 +780,12 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const repCycle = await getActiveRepCycle(colonyNetwork); await submitAndForwardTimeToDispute([goodClient, badClient], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await checkErrorRevert( repCycle.respondToBinarySearchForChallenge(0, 0, "0x00", ["0x00", "0x00", "0x00"]), "colony-reputation-mining-invalid-binary-search-response" @@ -803,7 +809,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const repCycle = await getActiveRepCycle(colonyNetwork); await submitAndForwardTimeToDispute([goodClient, badClient], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); @@ -817,7 +823,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const targetNodeKey = ReputationMinerTestWrapper.getHexString(targetNode, 64); const [, siblings] = await goodClient.justificationTree.getProof(targetNodeKey); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await checkErrorRevert( repCycle.confirmBinarySearchResult(round, index, "0x00", siblings), @@ -826,7 +832,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { // Cleanup await goodClient.confirmBinarySearchResult(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.invalidateHash(0, 1); await repCycle.confirmNewHash(1); }); @@ -866,7 +872,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await badClient2.initialise(colonyNetwork.address); await submitAndForwardTimeToDispute([goodClient, badClient], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient2.addLogContentsToReputationTree(); @@ -874,11 +880,11 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await badClient.confirmJustificationRootHash(); await runBinarySearch(goodClient, badClient); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await checkErrorRevertEthers(badClient2.respondToChallenge(), "colony-reputation-mining-origin-reputation-nonzero"); @@ -936,23 +942,23 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await badClient2.addLogContentsToReputationTree(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); await runBinarySearch(goodClient, badClient); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await checkErrorRevertEthers(badClient2.respondToChallenge(), "colony-reputation-mining-child-reputation-nonzero"); // Cleanup await goodClient.respondToChallenge(); await forwardTime(MINING_CYCLE_DURATION, this); await repCycle.invalidateHash(0, 1); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(1); }); @@ -977,16 +983,16 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await badClient2.addLogContentsToReputationTree(); await badClient3.addLogContentsToReputationTree(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); await runBinarySearch(goodClient, badClient); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await checkErrorRevertEthers(badClient2.respondToChallenge(), "colony-reputation-mining-invalid-before-reputation-proof"); await checkErrorRevertEthers(badClient3.respondToChallenge(), "colony-reputation-mining-invalid-after-reputation-proof"); @@ -996,7 +1002,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await goodClient.respondToChallenge(); await forwardTime(MINING_CYCLE_DURATION, this); await repCycle.invalidateHash(0, 1); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(1); }); @@ -1008,14 +1014,14 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const repCycle = await getActiveRepCycle(colonyNetwork); await submitAndForwardTimeToDispute([goodClient, badClient], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); await runBinarySearch(goodClient, badClient); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); @@ -1024,7 +1030,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const userAddress = ethers.utils.hexZeroPad(logEntry.user, 32); const skillId = ethers.utils.hexZeroPad(ethers.utils.bigNumberify(logEntry.skillId).toHexString(), 32); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await checkErrorRevert( repCycle.respondToChallenge( @@ -1085,7 +1091,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await goodClient.respondToChallenge(); await forwardTime(MINING_CYCLE_DURATION, this); await repCycle.invalidateHash(0, 1); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(1); }); @@ -1098,11 +1104,11 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const repCycle = await getActiveRepCycle(colonyNetwork); await submitAndForwardTimeToDispute([goodClient, badClient], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.respondToBinarySearchForChallenge(); await badClient.respondToBinarySearchForChallenge(); @@ -1121,15 +1127,15 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { ); // Cleanup - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.respondToBinarySearchForChallenge(); await goodClient.respondToBinarySearchForChallenge(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.respondToBinarySearchForChallenge(); await goodClient.respondToBinarySearchForChallenge(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); @@ -1137,7 +1143,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await goodClient.respondToChallenge(); await forwardTime(MINING_CYCLE_DURATION, this); await repCycle.invalidateHash(0, 1); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(1); }); @@ -1173,17 +1179,17 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await submitAndForwardTimeToDispute([badClient, badClient2], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await badClient.confirmJustificationRootHash(); await badClient2.confirmJustificationRootHash(); await runBinarySearch(badClient, badClient2); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmBinarySearchResult(); await badClient.confirmBinarySearchResult(); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); if (args.word === "high") { await checkErrorRevertEthers(badClient2.respondToChallenge(), "colony-reputation-mining-update-number-part-of-previous-log-entry-updates"); @@ -1195,7 +1201,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await goodClient.respondToChallenge(); await forwardTime(MINING_CYCLE_DURATION, this); await repCycle.invalidateHash(0, 0); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(1); }); }); @@ -1211,7 +1217,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { const repCycle = await getActiveRepCycle(colonyNetwork); await submitAndForwardTimeToDispute([goodClient, badClient], this); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await goodClient.confirmJustificationRootHash(); await badClient.confirmJustificationRootHash(); @@ -1242,7 +1248,7 @@ contract("Reputation Mining - disputes resolution misbehaviour", (accounts) => { await checkErrorRevert(repCycle.confirmNewHash(0), "colony-reputation-mining-user-ineligible-to-respond"); // Cleanup - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.confirmNewHash(0); }); }); diff --git a/test/reputation-system/happy-paths.js b/test/reputation-system/happy-paths.js index 146141337f..66a631f861 100644 --- a/test/reputation-system/happy-paths.js +++ b/test/reputation-system/happy-paths.js @@ -379,7 +379,7 @@ contract("Reputation Mining - happy paths", (accounts) => { workerRating: 3, }); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.submitRootHash(rootHash, 2, "0x00", 10, { from: MINER1 }); await repCycle.confirmNewHash(0); @@ -409,7 +409,7 @@ contract("Reputation Mining - happy paths", (accounts) => { const rootHash = await goodClient.getRootHash(); let repCycle = await getActiveRepCycle(colonyNetwork); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); await repCycle.submitRootHash(rootHash, 2, "0x00", 10, { from: MINER1 }); await repCycle.confirmNewHash(0); @@ -821,7 +821,7 @@ contract("Reputation Mining - happy paths", (accounts) => { await goodClient.addLogContentsToReputationTree(); const newRootHash = await goodClient.getRootHash(); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); const repCycle = await getActiveRepCycle(colonyNetwork); await repCycle.submitRootHash(newRootHash, 10, "0x00", 10, { from: MINER1 }); @@ -881,7 +881,7 @@ contract("Reputation Mining - happy paths", (accounts) => { const rootHash = await goodClient.getRootHash(); - await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW, this); + await forwardTime(MINING_CYCLE_DURATION + SUBMITTER_ONLY_WINDOW + 1, this); let repCycle = await getActiveRepCycle(colonyNetwork); await repCycle.submitRootHash(rootHash, 2, "0x00", 10, { from: MINER1 }); await repCycle.confirmNewHash(0); diff --git a/test/reputation-system/reputation-mining-client/client-auto-functionality.js b/test/reputation-system/reputation-mining-client/client-auto-functionality.js index 0ac40afa81..1c479fedd6 100644 --- a/test/reputation-system/reputation-mining-client/client-auto-functionality.js +++ b/test/reputation-system/reputation-mining-client/client-auto-functionality.js @@ -493,7 +493,7 @@ process.env.SOLIDITY_COVERAGE }, 30000); }); - await forwardTime(SUBMITTER_ONLY_WINDOW, this); + await forwardTime(SUBMITTER_ONLY_WINDOW + 1, this); // Good client should realise it can confirm new hash. So we wait for that event. await newCycleStart; diff --git a/test/reputation-system/root-hash-submissions.js b/test/reputation-system/root-hash-submissions.js index 131e21d6b5..f2f262c92a 100644 --- a/test/reputation-system/root-hash-submissions.js +++ b/test/reputation-system/root-hash-submissions.js @@ -57,8 +57,9 @@ let clnyToken; let goodClient; let badClient; let badClient2; +let badClient3; -const setupNewNetworkInstance = async (MINER1, MINER2, MINER3) => { +const setupNewNetworkInstance = async (MINER1, MINER2, MINER3, MINER4) => { colonyNetwork = await setupColonyNetwork(); const tokenLockingAddress = await colonyNetwork.getTokenLocking(); tokenLocking = await ITokenLocking.at(tokenLockingAddress); @@ -67,6 +68,7 @@ const setupNewNetworkInstance = async (MINER1, MINER2, MINER3) => { await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); await giveUserCLNYTokensAndStake(colonyNetwork, MINER3, DEFAULT_STAKE); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER4, DEFAULT_STAKE); await colonyNetwork.initialiseReputationMining(); await colonyNetwork.startNextCycle(); @@ -75,6 +77,8 @@ const setupNewNetworkInstance = async (MINER1, MINER2, MINER3) => { badClient = new MaliciousReputationMinerExtraRep({ loader, minerAddress: MINER2, realProviderPort, useJsTree }, 1, 0xfffffffff); // Mess up the second calculation in a different way badClient2 = new MaliciousReputationMinerExtraRep({ loader, minerAddress: MINER3, realProviderPort, useJsTree }, 1, 0xeeeeeeeee); + // And one test needs a third bad client... + badClient3 = new MaliciousReputationMinerExtraRep({ loader, minerAddress: MINER4, realProviderPort, useJsTree }, 1, 0xddddddddd); }; contract("Reputation mining - root hash submissions", (accounts) => { @@ -82,10 +86,11 @@ contract("Reputation mining - root hash submissions", (accounts) => { const MINER2 = accounts[6]; const MINER3 = accounts[7]; const MINER4 = accounts[8]; + const MINER5 = accounts[9]; before(async () => { // Setup a new network instance as we'll be modifying the global skills tree - await setupNewNetworkInstance(MINER1, MINER2, MINER3); + await setupNewNetworkInstance(MINER1, MINER2, MINER3, MINER4); }); beforeEach(async () => { @@ -110,7 +115,7 @@ contract("Reputation mining - root hash submissions", (accounts) => { afterEach(async () => { const reputationMiningGotClean = await finishReputationMiningCycle(colonyNetwork, this); - if (!reputationMiningGotClean) await setupNewNetworkInstance(MINER1, MINER2, MINER3); + if (!reputationMiningGotClean) await setupNewNetworkInstance(MINER1, MINER2, MINER3, MINER4); }); describe("when determining submission eligibility", () => { @@ -302,16 +307,16 @@ contract("Reputation mining - root hash submissions", (accounts) => { it("should not allow someone to submit a new reputation hash if they stake after the cycle begins", async () => { await forwardTime(1, this); // The condition is `windowOpen >= stakeTimestamp` so we make sure they aren't equal. - await giveUserCLNYTokensAndStake(colonyNetwork, MINER4, DEFAULT_STAKE); // Just stake an extra token to reset the time to now + await giveUserCLNYTokensAndStake(colonyNetwork, MINER5, DEFAULT_STAKE); // Just stake an extra token to reset the time to now await forwardTime(MINING_CYCLE_DURATION, this); let repCycle = await getActiveRepCycle(colonyNetwork); - await checkErrorRevert(repCycle.submitRootHash("0x12345678", 10, "0x00", 10, { from: MINER4 }), "colony-reputation-mining-stake-too-recent"); + await checkErrorRevert(repCycle.submitRootHash("0x12345678", 10, "0x00", 10, { from: MINER5 }), "colony-reputation-mining-stake-too-recent"); await advanceMiningCycleNoContest({ colonyNetwork, test: this }); await forwardTime(MINING_CYCLE_DURATION, this); repCycle = await getActiveRepCycle(colonyNetwork); - await repCycle.submitRootHash("0x12345678", 10, "0x00", 10, { from: MINER4 }); + await repCycle.submitRootHash("0x12345678", 10, "0x00", 10, { from: MINER5 }); }); it("should not allow someone to withdraw their stake if they have submitted a hash this round", async () => { @@ -643,6 +648,9 @@ contract("Reputation mining - root hash submissions", (accounts) => { const userLockMiner3 = await tokenLocking.getUserLock(clnyToken.address, MINER3); expect(userLockMiner3.balance, "Account was not punished properly").to.eq.BN(new BN(userLockMiner3Before.balance).sub(miner3Loss)); + + // Reset badClient2 to its default behaviour. + badClient2 = new MaliciousReputationMinerExtraRep({ loader, minerAddress: MINER3, realProviderPort, useJsTree }, 1, "0xeeeeeeeee"); }); it("should reward all stakers if they submitted the agreed new hash", async () => { @@ -771,5 +779,37 @@ contract("Reputation mining - root hash submissions", (accounts) => { weight = await colonyNetwork.calculateMinerWeight(0, 100); expect(weight).to.be.zero; }); + + it("should update disputeRewardSize as multiple submissions are made", async () => { + await advanceMiningCycleNoContest({ colonyNetwork, test: this }); + // This is the only test that needs a third bad client, so initialise here rather than in beforeEach + await badClient3.resetDB(); + await badClient3.initialise(colonyNetwork.address); + + await forwardTime(MINING_CYCLE_DURATION / 2, this); + await goodClient.addLogContentsToReputationTree(); + await badClient.addLogContentsToReputationTree(); + await badClient2.addLogContentsToReputationTree(); + await badClient3.addLogContentsToReputationTree(); + + const repCycle = await getActiveRepCycle(colonyNetwork); + + await goodClient.submitRootHash(); + let reward = await repCycle.getDisputeRewardSize(); + expect(reward).to.eq.BN("0"); + + await badClient.submitRootHash(); + reward = await repCycle.getDisputeRewardSize(); + expect(reward).to.eq.BN("142857142857142857142"); + + await badClient2.submitRootHash(); + // Because of how the maths works out, the reward won't have changed. + reward = await repCycle.getDisputeRewardSize(); + expect(reward).to.eq.BN("142857142857142857142"); + + await badClient3.submitRootHash(); + reward = await repCycle.getDisputeRewardSize(); + expect(reward).to.eq.BN("146341463414634146341"); + }); }); });