diff --git a/helpers/test-helper.js b/helpers/test-helper.js index 82871feb3e..5d2cd287f0 100644 --- a/helpers/test-helper.js +++ b/helpers/test-helper.js @@ -1028,3 +1028,18 @@ export function bn2bytes32(x, size = 64) { export function rolesToBytes32(roles) { return `0x${new BN(roles.map((role) => new BN(1).shln(role)).reduce((a, b) => a.or(b), new BN(0))).toString(16, 64)}`; } + +export class TestAdapter { + constructor() { + this.outputs = []; + } + + // eslint-disable-next-line class-methods-use-this + error(line) { + console.log(line); + } + + log(line) { + this.outputs.push(line); + } +} diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index e0f67e8481..b8b52d6ba8 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -85,21 +85,17 @@ class ReputationMiner { const metaColony = new ethers.Contract(metaColonyAddress, this.colonyContractDef.abi, this.realWallet); this.clnyAddress = await metaColony.getToken(); - if (this.useJsTree) { - this.reputationTree = new PatriciaTree(); - } else { + + if (!this.useJsTree) { this.patriciaTreeContractDef = await this.loader.load({ contractName: "PatriciaTree" }, { abi: true, address: false, bytecode: true }); this.patriciaTreeNoHashContractDef = await this.loader.load( { contractName: "PatriciaTreeNoHash" }, { abi: true, address: false, bytecode: true } ); - - const contractFactory = new ethers.ContractFactory(this.patriciaTreeContractDef.abi, this.patriciaTreeContractDef.bytecode, this.ganacheWallet); - const contract = await contractFactory.deploy(); - await contract.deployed(); - this.reputationTree = new ethers.Contract(contract.address, this.patriciaTreeContractDef.abi, this.ganacheWallet); } + this.reputationTree = await this.getNewPatriciaTree(this.useJsTree ? "js" : "solidity", true); + this.nReputations = ethers.constants.Zero; this.reputations = {}; this.gasPrice = ethers.utils.hexlify(20000000000); @@ -147,7 +143,7 @@ class ReputationMiner { ); this.queries.insertReputation = this.db.prepare( - `INSERT INTO reputations (reputation_rowid, colony_rowid, skill_id, user_rowid, value) + `INSERT OR IGNORE INTO reputations (reputation_rowid, colony_rowid, skill_id, user_rowid, value) SELECT (SELECT reputation_states.rowid FROM reputation_states WHERE reputation_states.root_hash=?), (SELECT colonies.rowid FROM colonies WHERE colonies.address=?), @@ -222,6 +218,20 @@ class ReputationMiner { jtType = "noop" } + // TODO: Can this be better? + this.previousReputations = JSON.parse(JSON.stringify(this.reputations)); + + this.previousReputationTree = await this.getNewPatriciaTree(this.useJsTree ? "js" : "solidity", true); + + + // eslint-disable-next-line no-restricted-syntax + for (const key of Object.keys(this.reputations)){ + const tx = await this.previousReputationTree.insert(key, this.reputations[key]) + if (!this.useJsTree) { + await tx.wait(); + } + } + await this.instantiateJustificationTree(jtType); this.justificationHashes = {}; @@ -367,7 +377,7 @@ class ReputationMiner { // Then the user-specifc sums in the order children, parents, skill. if (amount.lt(0)) { const nUpdates = ethers.BigNumber.from(logEntry.nUpdates); - const [nParents] = await this.colonyNetwork.getSkill(logEntry.skillId); + const [nParents] = await this.colonyNetwork.getSkill(logEntry.skillId, {blockTag: blockNumber}); const nChildUpdates = nUpdates.div(2).sub(1).sub(nParents); const relativeUpdateNumber = updateNumber.sub(logEntry.nPreviousUpdates).sub(this.nReputationsBeforeLatestLog); // Child updates are two sets: colonywide sums for children - located in the first nChildUpdates, @@ -375,7 +385,7 @@ class ReputationMiner { // Get current reputation amount of the origin skill, which is positioned at the end of the current logEntry nUpdates. const originSkillUpdateNumber = updateNumber.sub(relativeUpdateNumber).add(nUpdates).sub(1); - const originSkillKey = await this.getKeyForUpdateNumber(originSkillUpdateNumber); + const originSkillKey = await this.getKeyForUpdateNumber(originSkillUpdateNumber, blockNumber); originReputationProof = await this.getReputationProofObject(originSkillKey); const originSkillKeyExists = this.reputations[originSkillKey] !== undefined; @@ -400,9 +410,9 @@ class ReputationMiner { let keyUsedInCalculations; if (relativeUpdateNumber.lt(nChildUpdates)) { const childSkillUpdateNumber = updateNumber.add(nUpdates.div(2)); - keyUsedInCalculations = await this.getKeyForUpdateNumber(childSkillUpdateNumber); + keyUsedInCalculations = await this.getKeyForUpdateNumber(childSkillUpdateNumber, blockNumber); } else { - keyUsedInCalculations = await this.getKeyForUpdateNumber(updateNumber); + keyUsedInCalculations = await this.getKeyForUpdateNumber(updateNumber, blockNumber); } childReputationProof = await this.getReputationProofObject(keyUsedInCalculations); @@ -848,45 +858,101 @@ class ReputationMiner { } /** - * Get a Merkle proof and value for `key` in a past reputation state with root hash `rootHash` + * Get a Merkle proof and value for `key` in a (possibly) past reputation state with root hash `rootHash` * @param {[type]} rootHash A previous root hash of a reputation state * @param {[type]} key A key in that root hash we wish to know the value and proof for * @return {Promise} A promise that resolves to [branchmask, siblings, value] for the supplied key in the supplied root hash */ async getHistoricalProofAndValue(rootHash, key) { - const tree = new PatriciaTree(); - // Load all reputations from that state. + const currentRootHash = await this.reputationTree.getRootHash(); - let res = await this.queries.getAllReputationsInHash.all(rootHash); - if (res.length === 0) { - return new Error("No such reputation state"); + if (currentRootHash === rootHash) { + if (!this.reputations[key]){ + return new Error("Requested reputation does not exist") + } + try { + const [branchMask, siblings] = await this.reputationTree.getProof(key); + + const retBranchMask = ReputationMiner.getHexString(branchMask); + return [retBranchMask, siblings, this.reputations[key]]; + } catch (err) { + return err; + } } - for (let i = 0; i < res.length; i += 1) { - const row = res[i]; - const rowKey = ReputationMiner.getKey(row.colony_address, row.skill_id, row.user_address); - await tree.insert(rowKey, row.value); + + const previousRootHash = await this.previousReputationTree.getRootHash(); + if (previousRootHash === rootHash) { + if (!this.previousReputations[key]){ + return new Error("Requested reputation does not exist") + } + try { + const [branchMask, siblings] = await this.previousReputationTree.getProof(key); + if (branchMask === "0x"){ + return new Error("No such reputation") + } + const retBranchMask = ReputationMiner.getHexString(branchMask); + return [retBranchMask, siblings, this.previousReputations[key]]; + } catch (err) { + return err; + } + } + + // Not in the accepted or next tree, so let's look at the DB + + const allReputations = await this.queries.getAllReputationsInHash.all(rootHash); + if (allReputations.length === 0) { + return new Error("No such reputation state"); } const keyElements = ReputationMiner.breakKeyInToElements(key); const [colonyAddress, , userAddress] = keyElements; const skillId = parseInt(keyElements[1], 16); - res = await this.queries.getReputationValue.all(rootHash, userAddress, skillId, colonyAddress); + const reputationValue = await this.queries.getReputationValue.all(rootHash, userAddress, skillId, colonyAddress); - if (res.length === 0) { + if (reputationValue.length === 0) { return new Error("No such reputation"); } - if (res.length > 1) { + if (reputationValue.length > 1) { return new Error("Multiple such reputations found. Something is wrong!"); } + const tree = new PatriciaTree(); + // Load all reputations from that state. + + for (let i = 0; i < allReputations.length; i += 1) { + const row = allReputations[i]; + const rowKey = ReputationMiner.getKey(row.colony_address, row.skill_id, row.user_address); + await tree.insert(rowKey, row.value); + } + const [branchMask, siblings] = await tree.getProof(key); const retBranchMask = ReputationMiner.getHexString(branchMask); - return [retBranchMask, siblings, res[0].value]; + return [retBranchMask, siblings, reputationValue[0].value]; } async getHistoricalValue(rootHash, key) { + const currentRootHash = await this.reputationTree.getRootHash(); + + if (currentRootHash === rootHash) { + if (this.reputations[key]){ + return this.reputations[key] + } + // Otherwise, not in requested state + return new Error("Requested reputation does not exist") + } + + const previousRootHash = await this.previousReputationTree.getRootHash(); + + if (previousRootHash === rootHash) { + if (this.previousReputations[key]){ + return this.previousReputations[key] + } + // Otherwise, not in requested state + return new Error("Requested reputation does not exist") + } + let res = await this.queries.getAllReputationsInHash.all(rootHash); if (res.length === 0) { return new Error("No such reputation state"); @@ -1335,6 +1401,15 @@ class ReputationMiner { } } + // Some more cycles might have completed since we started syncing + const lastEventBlock = events[events.length - 1].blockNumber + filter.fromBlock = lastEventBlock; + const sinceEvents = await this.realProvider.getLogs(filter); + if (sinceEvents.length > 1){ + console.log("Some more cycles have completed during the sync process. Continuing to sync...") + await this.sync(lastEventBlock, saveHistoricalStates); + } + // Check final state const currentHash = await this.colonyNetwork.getReputationRootHash(); // TODO: Once getReputationRootHashNLeaves exists, use it @@ -1379,23 +1454,18 @@ class ReputationMiner { } async saveCurrentState() { - const currentRootHash = await this.getRootHash(); - let res = await this.queries.saveHashAndLeaves.run(currentRootHash, this.nReputations.toString()); + this.queries.saveHashAndLeaves.run(currentRootHash, this.nReputations.toString()); for (let i = 0; i < Object.keys(this.reputations).length; i += 1) { const key = Object.keys(this.reputations)[i]; const value = this.reputations[key]; const keyElements = ReputationMiner.breakKeyInToElements(key); const [colonyAddress, , userAddress] = keyElements; const skillId = parseInt(keyElements[1], 16); - await this.queries.saveColony.run(colonyAddress); - await this.queries.saveUser.run(userAddress); - await this.queries.saveSkill.run(skillId); - - res = this.queries.getReputationCount.get(currentRootHash, colonyAddress, skillId, userAddress); - if (res.n === 0) { - this.queries.insertReputation.run(currentRootHash, colonyAddress, skillId, userAddress, value); - } + this.queries.saveColony.run(colonyAddress); + this.queries.saveUser.run(userAddress); + this.queries.saveSkill.run(skillId); + this.queries.insertReputation.run(currentRootHash, colonyAddress, skillId, userAddress, value); } } @@ -1403,17 +1473,7 @@ class ReputationMiner { this.nReputations = ethers.constants.Zero; this.reputations = {}; - if (this.useJsTree) { - this.reputationTree = new PatriciaTree(); - } else { - this.patriciaTreeContractDef = await this.loader.load({ contractName: "PatriciaTree" }, { abi: true, address: false, bytecode: true }); - - const contractFactory = new ethers.ContractFactory(this.patriciaTreeContractDef.abi, this.patriciaTreeContractDef.bytecode, this.ganacheWallet); - const contract = await contractFactory.deploy(); - await contract.deployed(); - - this.reputationTree = new ethers.Contract(contract.address, this.patriciaTreeContractDef.abi, this.ganacheWallet); - } + this.reputationTree = await this.getNewPatriciaTree(this.useJsTree ? "js" : "solidity", true); const res = await this.queries.getAllReputationsInHash.all(reputationRootHash); this.nReputations = ethers.BigNumber.from(res.length); @@ -1432,6 +1492,27 @@ class ReputationMiner { } } + async loadStateToPrevious(reputationRootHash) { + this.previousReputations = {}; + + this.previousReputationTree = await this.getNewPatriciaTree(this.useJsTree ? "js" : "solidity", true); + + const res = await this.queries.getAllReputationsInHash.all(reputationRootHash); + for (let i = 0; i < res.length; i += 1) { + const row = res[i]; + const key = ReputationMiner.getKey(row.colony_address, row.skill_id, row.user_address); + const tx = await this.previousReputationTree.insert(key, row.value, { gasLimit: 4000000 }); + if (!this.useJsTree) { + await tx.wait(); + } + this.previousReputations[key] = row.value; + } + const currentStateHash = await this.previousReputationTree.getRootHash(); + if (currentStateHash !== reputationRootHash) { + console.log("WARNING: The supplied state failed to be recreated successfully. Are you sure it was saved?"); + } + } + async loadJustificationTree(justificationRootHash) { this.justificationHashes = {}; let jtType; @@ -1474,29 +1555,47 @@ class ReputationMiner { } async instantiateJustificationTree(type = "js") { + this.justificationTree = await this.getNewPatriciaTree(type, false); + } + + // Type can be js, solidity or noop, as appropriate. 'hash' is whether the tree (in the first two instances) + // hashes the contents or not before adding to the tree. + async getNewPatriciaTree(type = "js", hash = true){ if (type === "js") { - this.justificationTree = new PatriciaTreeNoHash(); - } else if (type === "solidity") { - const contractFactory = new ethers.ContractFactory( - this.patriciaTreeNoHashContractDef.abi, - this.patriciaTreeNoHashContractDef.bytecode, - this.ganacheWallet - ); + if (hash){ + return new PatriciaTree() + } + return new PatriciaTreeNoHash(); + } + if (type === "solidity") { + let abi; + let bytecode; + if (hash) { + abi = this.patriciaTreeContractDef.abi + bytecode = this.patriciaTreeContractDef.bytecode + } else { + abi = this.patriciaTreeNoHashContractDef.abi + bytecode = this.patriciaTreeNoHashContractDef.bytecode + } + + const contractFactory = new ethers.ContractFactory(abi, bytecode, this.ganacheWallet); const contract = await contractFactory.deploy(); await contract.deployed(); - this.justificationTree = new ethers.Contract(contract.address, this.patriciaTreeNoHashContractDef.abi, this.ganacheWallet); - } else if (type === "noop") { - this.justificationTree = { + return new ethers.Contract(contract.address, abi, this.ganacheWallet); + } + + if (type === "noop") { + return { insert: () => {return { wait: () => {}}}, getRootHash: () => {}, getImpliedRoot: () => {}, getProof: () => {}, } - } else { - console.log(`UNKNOWN TYPE for justification tree instantiation: ${type}`) } + console.log(`UNKNOWN TYPE for patricia tree instantiation: ${type}`) + return new PatriciaTree(); } async saveJustificationTree(){ @@ -1530,7 +1629,8 @@ class ReputationMiner { colony_rowid INTEGER NOT NULL, skill_id INTEGER NOT NULL, user_rowid INTEGER NOT NULL, - value text NOT NULL + value text NOT NULL, + PRIMARY KEY("reputation_rowid","colony_rowid","skill_id","user_rowid") )` ).run(); @@ -1552,6 +1652,25 @@ class ReputationMiner { await this.db.prepare('CREATE INDEX IF NOT EXISTS reputation_skill_id ON reputations (skill_id)').run(); await this.db.prepare('CREATE INDEX IF NOT EXISTS colonies_address ON colonies (address)').run(); + // We added a composite key to reputations - do we need to port it over? + const res = await this.db.prepare("SELECT COUNT(pk) AS c FROM PRAGMA_TABLE_INFO('reputations') WHERE pk <> 0").all(); + if (res[0].c === 0){ + await this.db.prepare( + `CREATE TABLE reputations2 ( + reputation_rowid text NOT NULL, + colony_rowid INTEGER NOT NULL, + skill_id INTEGER NOT NULL, + user_rowid INTEGER NOT NULL, + value text NOT NULL, + PRIMARY KEY("reputation_rowid","colony_rowid","skill_id","user_rowid") + )` + ).run(); + + await this.db.prepare(`INSERT INTO reputations2 SELECT * FROM reputations`).run() + await this.db.prepare(`DROP TABLE reputations`).run() + await this.db.prepare(`ALTER TABLE reputations2 RENAME TO reputations`).run() + console.log("Composite primary key added to reputations table") + } this.prepareQueries() } diff --git a/packages/reputation-miner/ReputationMinerClient.js b/packages/reputation-miner/ReputationMinerClient.js index 3e0e7b14aa..315bf68c43 100644 --- a/packages/reputation-miner/ReputationMinerClient.js +++ b/packages/reputation-miner/ReputationMinerClient.js @@ -188,16 +188,6 @@ class ReputationMinerClient { } const key = ReputationMiner.getKey(req.params.colonyAddress, req.params.skillId, req.params.userAddress); - const currentHash = await this._miner.getRootHash(); - if (currentHash === req.params.rootHash) { - if (this._miner.reputations[key]) { - const proof = await this._miner.getReputationProofObject(key); - delete proof.NLeaves; - proof.reputationAmount = ethers.BigNumber.from(`0x${proof.value.slice(2, 66)}`).toString(); - return res.status(200).send(proof); - } - return res.status(400).send({ message: "Requested reputation does not exist" }); - } try { const historicalProof = await this._miner.getHistoricalProofAndValue(req.params.rootHash, key); @@ -209,6 +199,7 @@ class ReputationMinerClient { proof.reputationAmount = ethers.BigNumber.from(`0x${proof.value.slice(2, 66)}`).toString(); return res.status(200).send(proof); } catch (err) { + console.log(err) return res.status(500).send({ message: "An error occurred querying the reputation" }); } }); @@ -240,18 +231,27 @@ class ReputationMinerClient { const lastStateHash = this._miner.justificationHashes[lastLeaf].jhLeafValue.slice(0, 66) if (firstStateHash === latestConfirmedReputationHash){ - await this._miner.loadState(lastStateHash); - const currentStateHash = await this._miner.reputationTree.getRootHash(); - if (currentStateHash === lastStateHash){ - const submittedState = await repCycle.getReputationHashSubmission(this._miner.minerAddress); - if (submittedState.proposedNewRootHash === ethers.utils.hexZeroPad(0, 32)) { - resumedSuccessfully = true; - this._adapter.log("Successfully resumed pre-submission"); - } else { - const jrh = await this._miner.justificationTree.getRootHash(); - if (submittedState.proposedNewRootHash === currentStateHash && submittedState.jrh === jrh){ + // We need to be able to load that state (but no Justification Tree) + + await this._miner.loadStateToPrevious(firstStateHash) + const previousStateHash = await this._miner.previousReputationTree.getRootHash(); + if (previousStateHash === firstStateHash){ + + // Then, if successful, we need to load the last state hash, including the justification tree + await this._miner.loadState(lastStateHash); + const currentStateHash = await this._miner.reputationTree.getRootHash(); + if (currentStateHash === lastStateHash){ + // Loading the state was successful... + const submittedState = await repCycle.getReputationHashSubmission(this._miner.minerAddress); + if (submittedState.proposedNewRootHash === ethers.utils.hexZeroPad(0, 32)) { resumedSuccessfully = true; - this._adapter.log("Successfully resumed mid-submission"); + this._adapter.log("Successfully resumed pre-submission"); + } else { + const jrh = await this._miner.justificationTree.getRootHash(); + if (submittedState.proposedNewRootHash === currentStateHash && submittedState.jrh === jrh){ + resumedSuccessfully = true; + this._adapter.log("Successfully resumed mid-submission"); + } } } } diff --git a/test/reputation-system/client-core-functionality.js b/test/reputation-system/client-core-functionality.js index 2a5b1b7d26..ea6439cea7 100644 --- a/test/reputation-system/client-core-functionality.js +++ b/test/reputation-system/client-core-functionality.js @@ -8,7 +8,7 @@ import bnChai from "bn-chai"; import TruffleLoader from "../../packages/reputation-miner/TruffleLoader"; import { DEFAULT_STAKE, INITIAL_FUNDING } from "../../helpers/constants"; -import { currentBlock, makeReputationKey, advanceMiningCycleNoContest, getActiveRepCycle } from "../../helpers/test-helper"; +import { currentBlock, makeReputationKey, advanceMiningCycleNoContest, getActiveRepCycle, TestAdapter } from "../../helpers/test-helper"; import { fundColonyWithTokens, setupColonyNetwork, @@ -73,7 +73,9 @@ process.env.SOLIDITY_COVERAGE await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); await reputationMiner.saveCurrentState(); - client = new ReputationMinerClient({ loader, realProviderPort, minerAddress: MINER1, useJsTree: true, auto: false }); + const adapter = new TestAdapter(); + + client = new ReputationMinerClient({ loader, realProviderPort, minerAddress: MINER1, useJsTree: true, auto: false, adapter }); await client.initialise(colonyNetwork.address, 1); }); @@ -105,19 +107,36 @@ process.env.SOLIDITY_COVERAGE expect(value).to.equal(oracleProofObject.value); }); - it("should correctly respond to a request for a reputation state in a previous state", async () => { + it("should correctly respond to a request for a reputation state in previous states", async () => { + const startingBlock = await currentBlock(); + const startingBlockNumber = startingBlock.number; + await fundColonyWithTokens(metaColony, clnyToken, INITIAL_FUNDING.muln(100)); + await setupFinalizedTask({ colonyNetwork, colony: metaColony, token: clnyToken, worker: MINER1, manager: accounts[6] }); + await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + const rootHash = await reputationMiner.getRootHash(); const key = makeReputationKey(metaColony.address, new BN(2), MINER1); const [branchMask, siblings] = await reputationMiner.getProof(key); const value = reputationMiner.reputations[key]; await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + const rootHash2 = await reputationMiner.getRootHash(); + const [branchMask2, siblings2] = await reputationMiner.getProof(key); + const value2 = reputationMiner.reputations[key]; - const url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/2/${MINER1}`; - const res = await request(url); + await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + const rootHash3 = await reputationMiner.getRootHash(); + const [branchMask3, siblings3] = await reputationMiner.getProof(key); + const value3 = reputationMiner.reputations[key]; + + await client._miner.sync(startingBlockNumber, true); // eslint-disable-line no-underscore-dangle + + let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/2/${MINER1}`; + let res = await request(url); expect(res.statusCode).to.equal(200); - const oracleProofObject = JSON.parse(res.body); + let oracleProofObject = JSON.parse(res.body); expect(branchMask).to.equal(oracleProofObject.branchMask); expect(siblings.length).to.equal(oracleProofObject.siblings.length); @@ -127,25 +146,97 @@ process.env.SOLIDITY_COVERAGE expect(key).to.equal(oracleProofObject.key); expect(value).to.equal(oracleProofObject.value); + + await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + + // Different URL so we don't hit the cache. + url = `http://127.0.0.1:3000/${rootHash2}/${metaColony.address}/2/${MINER1}`; + res = await request(url); + expect(res.statusCode).to.equal(200); + + oracleProofObject = JSON.parse(res.body); + expect(branchMask2).to.equal(oracleProofObject.branchMask); + expect(siblings2.length).to.equal(oracleProofObject.siblings.length); + + for (let i = 0; i < oracleProofObject.siblings.length; i += 1) { + expect(siblings2[i]).to.equal(oracleProofObject.siblings[i]); + } + + expect(key).to.equal(oracleProofObject.key); + expect(value2).to.equal(oracleProofObject.value); + + // Different URL so we don't hit the cache. + url = `http://127.0.0.1:3000/${rootHash3}/${metaColony.address}/2/${MINER1}`; + res = await request(url); + expect(res.statusCode).to.equal(200); + + oracleProofObject = JSON.parse(res.body); + expect(branchMask3).to.equal(oracleProofObject.branchMask); + expect(siblings3.length).to.equal(oracleProofObject.siblings.length); + + for (let i = 0; i < oracleProofObject.siblings.length; i += 1) { + expect(siblings3[i]).to.equal(oracleProofObject.siblings[i]); + } + + expect(key).to.equal(oracleProofObject.key); + expect(value3).to.equal(oracleProofObject.value); }); - it("should correctly respond to a request for a reputation state in a previous state with no proof", async () => { + it("should correctly respond to a request for a reputation state in previous states with no proof", async () => { + const startingBlock = await currentBlock(); + const startingBlockNumber = startingBlock.number; + await fundColonyWithTokens(metaColony, clnyToken, INITIAL_FUNDING.muln(100)); + await setupFinalizedTask({ colonyNetwork, colony: metaColony, token: clnyToken, worker: MINER1, manager: accounts[6] }); + await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + const rootHash = await reputationMiner.getRootHash(); const key = makeReputationKey(metaColony.address, new BN(2), MINER1); const value = reputationMiner.reputations[key]; await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + const rootHash2 = await reputationMiner.getRootHash(); + const value2 = reputationMiner.reputations[key]; - const url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/2/${MINER1}/noProof`; - const res = await request(url); + await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); + const rootHash3 = await reputationMiner.getRootHash(); + const value3 = reputationMiner.reputations[key]; + + await client._miner.sync(startingBlockNumber, true); // eslint-disable-line no-underscore-dangle + + let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/2/${MINER1}/noProof`; + let res = await request(url); expect(res.statusCode).to.equal(200); - const oracleProofObject = JSON.parse(res.body); + let oracleProofObject = JSON.parse(res.body); expect(undefined).to.equal(oracleProofObject.branchMask); expect(undefined).to.equal(oracleProofObject.siblings); expect(key).to.equal(oracleProofObject.key); expect(value).to.equal(oracleProofObject.value); + + // Different URL so we don't hit the cache. + url = `http://127.0.0.1:3000/${rootHash2}/${metaColony.address}/2/${MINER1}/noProof`; + res = await request(url); + expect(res.statusCode).to.equal(200); + + oracleProofObject = JSON.parse(res.body); + expect(undefined).to.equal(oracleProofObject.branchMask); + expect(undefined).to.equal(oracleProofObject.siblings); + + expect(key).to.equal(oracleProofObject.key); + expect(value2).to.equal(oracleProofObject.value); + + url = `http://127.0.0.1:3000/${rootHash3}/${metaColony.address}/2/${MINER1}/noProof`; + res = await request(url); + expect(res.statusCode).to.equal(200); + + oracleProofObject = JSON.parse(res.body); + expect(undefined).to.equal(oracleProofObject.branchMask); + expect(undefined).to.equal(oracleProofObject.siblings); + + expect(key).to.equal(oracleProofObject.key); + expect(value3).to.equal(oracleProofObject.value); }); it("should correctly respond to a request for a valid key in a reputation state that never existed", async () => { diff --git a/test/reputation-system/client-sync-functionality.js b/test/reputation-system/client-sync-functionality.js index aa7f45ae63..41e02d77b8 100644 --- a/test/reputation-system/client-sync-functionality.js +++ b/test/reputation-system/client-sync-functionality.js @@ -5,7 +5,7 @@ import chai from "chai"; import bnChai from "bn-chai"; import TruffleLoader from "../../packages/reputation-miner/TruffleLoader"; -import { DEFAULT_STAKE, INITIAL_FUNDING } from "../../helpers/constants"; +import { DEFAULT_STAKE, INITIAL_FUNDING, UINT256_MAX } from "../../helpers/constants"; import { forwardTime, currentBlock, advanceMiningCycleNoContest, getActiveRepCycle } from "../../helpers/test-helper"; import { giveUserCLNYTokensAndStake, setupFinalizedTask, fundColonyWithTokens } from "../../helpers/test-data-generator"; import ReputationMinerTestWrapper from "../../packages/reputation-miner/test/ReputationMinerTestWrapper"; @@ -133,6 +133,7 @@ process.env.SOLIDITY_COVERAGE for (let i = 0; i < 5; i += 1) { await setupFinalizedTask({ colonyNetwork, colony: metaColony }); } + await metaColony.emitDomainReputationPenalty(1, UINT256_MAX, 1, accounts[2], -100, { from: accounts[0] }); await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner1, test: this }); 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 f05139c13d..54b4bf4763 100644 --- a/test/reputation-system/reputation-mining-client/client-auto-functionality.js +++ b/test/reputation-system/reputation-mining-client/client-auto-functionality.js @@ -18,6 +18,7 @@ import { finishReputationMiningCycle, currentBlock, getWaitForNSubmissionsPromise, + TestAdapter, } from "../../../helpers/test-helper"; import { setupColonyNetwork, @@ -518,21 +519,6 @@ process.env.SOLIDITY_COVERAGE const repCycleEthers = await reputationMinerClient._miner.getActiveRepCycle(); - class TestAdapter { - constructor() { - this.outputs = []; - } - - // eslint-disable-next-line class-methods-use-this - error(line) { - console.log(line); - } - - log(line) { - this.outputs.push(line); - } - } - // start up another one - does it quick-load pre submission? let adapter = new TestAdapter();