diff --git a/.gitignore b/.gitignore index 01705854c9..4e422704d0 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ truffle-security-output.json .coverage_contracts etherrouter-address.json ganache-chain-db +.DS_Store +*.sqlite* \ No newline at end of file diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index 21930630d6..249ba2f68c 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -62,9 +62,11 @@ class ReputationMiner { if (minerAddress) { this.realWallet = this.realProvider.getSigner(minerAddress); } else { + // TODO: Check that this wallet can stake? this.realWallet = new ethers.Wallet(privateKey, this.realProvider); - console.log("Transactions will be signed from ", this.realWallet.address); + this.minerAddress = this.realWallet.address; } + console.log(`Transactions will be signed from ${this.realWallet.address}`); } /** @@ -1347,20 +1349,28 @@ class ReputationMiner { * @param { Bool } saveHistoricalStates Whether to save historical (valid) states while syncing * @return {Promise} A promise that resolves once the state is up-to-date */ - async sync(blockNumber, saveHistoricalStates = false) { - if (!blockNumber) { - throw new Error("Block number not supplied to sync"); - } + async sync(blockNumber = 1, saveHistoricalStates = false) { + // Get the events const filter = this.colonyNetwork.filters.ReputationMiningCycleComplete(null, null); filter.fromBlock = blockNumber; const events = await this.realProvider.getLogs(filter); let localHash = await this.reputationTree.getRootHash(); + + console.log(`Beginning sync from block ${blockNumber} and hash ${localHash} with ${events.length} cycles to go`) + let applyLogs = false; + // We're going to apply logs if: + // - We're syncing from a user-provided hash (which is this conditional) + // - We find a match for an on-chain state in our db (which is the loop below) + // - We are syncing from scratch (e.g. no user-given or on-chain state is found, localHash is 0) + if (localHash !== `0x${new BN(0).toString(16, 64)}`) { + applyLogs = true; + } // Run through events backwards find the most recent one that we know... let syncFromIndex = 0; - for (let i = events.length - 1 ; i >= 0 ; i -= 1){ + for (let i = events.length - 1 ; i >= 0 ; i -= 1) { const event = events[i]; const hash = event.data.slice(0, 66); const nLeaves = ethers.BigNumber.from(`0x${event.data.slice(66, 130)}`); @@ -1369,22 +1379,24 @@ class ReputationMiner { if (res.n === 1){ // We know that state! We can just sync from the next one... syncFromIndex = i + 1; + localHash = hash; await this.loadState(hash); applyLogs = true; break; } } - // We're not going to apply the logs unless we're syncing from scratch (which is this if statement) - // or we find a hash that we recognise as our current state, and we're going to sync from there (which - // is the if statement at the end of the loop below + console.log(`Syncing forward from index ${syncFromIndex} with local hash ${localHash}`) + if (localHash === `0x${new BN(0).toString(16, 64)}`) { applyLogs = true; } for (let i = syncFromIndex; i < events.length; i += 1) { - console.log(`${new Date().toLocaleTimeString()}: Syncing mining cycle ${i + 1} of ${events.length}...`) const event = events[i]; + const time = new Date().toLocaleTimeString(); + console.log(`${time}: Syncing mining cycle ${i + 1} of ${events.length}, from block ${event.blockNumber} and localHash ${localHash}`); + if (i === 0) { // If we are syncing from the very start of the reputation history, the block // before the very first 'ReputationMiningCycleComplete' does not have an @@ -1433,7 +1445,8 @@ class ReputationMiner { localHash = await this.reputationTree.getRootHash(); const localNLeaves = await this.nReputations; if (localHash !== currentHash || !currentNLeaves.eq(localNLeaves)) { - console.log("ERROR: Sync failed and did not recover"); + console.log(`Error: Sync failed and did not recover, final hash does not match ${currentHash}.`); + console.log("If the miner has been syncing for a while, try restarting, as the mining cycle may have advanced."); } else { console.log("Sync successful, even if there were warnings above"); } @@ -1504,7 +1517,9 @@ class ReputationMiner { } const currentStateHash = await this.reputationTree.getRootHash(); if (currentStateHash !== reputationRootHash) { - console.log("WARNING: The supplied state failed to be recreated successfully. Are you sure it was saved?"); + console.log(`WARNING: The supplied state ${reputationRootHash} failed to be recreated successfully. Are you sure it was saved?`); + } else { + console.log(`Reputation state ${reputationRootHash} was loaded successfully.`); } } diff --git a/packages/reputation-miner/ReputationMinerClient.js b/packages/reputation-miner/ReputationMinerClient.js index c546a2433e..d2e6645ca3 100644 --- a/packages/reputation-miner/ReputationMinerClient.js +++ b/packages/reputation-miner/ReputationMinerClient.js @@ -211,7 +211,7 @@ class ReputationMinerClient { * @param {string} colonyNetworkAddress The address of the current `ColonyNetwork` contract * @return {Promise} */ - async initialise(colonyNetworkAddress, startingBlock) { + async initialise(colonyNetworkAddress, startingBlock, startingHash) { this.resolveBlockChecksFinished = undefined; await this._miner.initialise(colonyNetworkAddress); @@ -219,6 +219,10 @@ class ReputationMinerClient { const numEntries = minerStake.amount.div(this._miner.getMinStake()); this._adapter.log(`Miner has staked ${minerStake.amount} CLNY, allowing up to ${numEntries} entries per cycle`); + if (minerStake.amount.eq(0)) { + this._adapter.log(`Stake for mining by depositing ${minStake} in tokenLocking then calling stakeForMining on the network`); + } + let resumedSuccessfully = false; // If we have a JRH saved, and it goes from the current (on chain) state to // a state that we know, then let's assume it's correct @@ -268,9 +272,14 @@ class ReputationMinerClient { // Get latest state from database if available, otherwise sync to current state on-chain await this._miner.createDB(); + this._adapter.log(`Attempting to load latest on-chain state ${latestConfirmedReputationHash}`); await this._miner.loadState(latestConfirmedReputationHash); if (this._miner.nReputations.eq(0)) { this._adapter.log("Latest state not found - need to sync"); + if (startingHash !== undefined) { + this._adapter.log(`Loading starting hash ${startingHash}`); + await this._miner.loadState(startingHash); + } await this._miner.sync(startingBlock, true); } @@ -460,7 +469,7 @@ class ReputationMinerClient { const {entryIndex} = this.best12Submissions[this.submissionIndex]; const canSubmit = await this._miner.submissionPossible(entryIndex); if (canSubmit) { - this._adapter.log("⏰ Looks like it's time to submit an entry to the current cycle"); + this._adapter.log(`⏰ ${new Date().toLocaleTimeString()}: Looks like it's time to submit an entry to the current cycle`); this.submissionIndex += 1; await this.updateGasEstimate('average'); await this.submitEntry(entryIndex); @@ -730,7 +739,7 @@ class ReputationMinerClient { async submitEntry(entryIndex) { const rootHash = await this._miner.getRootHash(); - this._adapter.log(`#️⃣ Miner ${this._miner.minerAddress} submitting new reputation hash ${rootHash} at entry index ${entryIndex.toNumber()}`); + this._adapter.log(`#️⃣ Submitting new reputation hash ${rootHash} at entry index ${entryIndex.toNumber()}`); // Submit hash let submitRootHashTx = await this._miner.submitRootHash(entryIndex); @@ -745,8 +754,9 @@ class ReputationMinerClient { } async confirmEntry() { - this._adapter.log("⏰ Looks like it's time to confirm the new hash"); + this._adapter.log(`⏰ ${new Date().toLocaleTimeString()}: Looks like it's time to confirm the new hash`); // Confirm hash if possible + const [round] = await this._miner.getMySubmissionRoundAndIndex(); if (round && round.gte(0)) { await this.updateGasEstimate('average'); diff --git a/packages/reputation-miner/bin/index.js b/packages/reputation-miner/bin/index.js index 19a29e27e2..9635bf94cb 100644 --- a/packages/reputation-miner/bin/index.js +++ b/packages/reputation-miner/bin/index.js @@ -8,6 +8,7 @@ const { argv } = require("yargs") .option('privateKey', {string:true}) .option('colonyNetworkAddress', {string:true}) .option('minerAddress', {string:true}) + .option('startingHash', {string:true}) .option('providerAddress', {type: "array", default: []}); const ethers = require("ethers"); const backoff = require("exponential-backoff").backOff; @@ -33,6 +34,7 @@ const { oraclePort, processingDelay, adapterLabel, + startingHash, } = argv; class RetryProvider extends ethers.providers.StaticJsonRpcProvider { @@ -53,7 +55,7 @@ class RetryProvider extends ethers.providers.StaticJsonRpcProvider { return backoff(() => super.getNetwork(), {retry: RetryProvider.attemptCheck}); } - // This should return a Promise (and may throw erros) + // This should return a Promise (and may throw errors) // method is the method name (e.g. getBalance) and params is an // object with normalized values passed in, depending on the method perform(method, params) { @@ -61,8 +63,8 @@ class RetryProvider extends ethers.providers.StaticJsonRpcProvider { } } -if ((!minerAddress && !privateKey) || !colonyNetworkAddress || !syncFrom) { - console.log("❗️ You have to specify all of ( --minerAddress or --privateKey ) and --colonyNetworkAddress and --syncFrom on the command line!"); +if ((!minerAddress && !privateKey) || !colonyNetworkAddress) { + console.log("❗️ You have to specify all of ( --minerAddress or --privateKey ) and --colonyNetworkAddress on the command line!"); process.exit(); } @@ -126,4 +128,5 @@ const client = new ReputationMinerClient({ oraclePort, processingDelay }); -client.initialise(colonyNetworkAddress, syncFrom); + +client.initialise(colonyNetworkAddress, syncFrom, startingHash);