Skip to content

Reputation miner updates #1013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ truffle-security-output.json
.coverage_contracts
etherrouter-address.json
ganache-chain-db
.DS_Store
*.sqlite*
65 changes: 48 additions & 17 deletions packages/reputation-miner/ReputationMiner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}

/**
Expand Down Expand Up @@ -757,9 +759,9 @@ class ReputationMiner {

async getEntryIndex(startIndex = 1) {
// Get how much we've staked, and thefore how many entries we have
const [stakeAmount] = await this.colonyNetwork.getMiningStake(this.minerAddress);
const minerStake = await this.getMiningStake();

for (let i = ethers.BigNumber.from(startIndex); i.lte(stakeAmount.div(minStake)); i = i.add(1)) {
for (let i = ethers.BigNumber.from(startIndex); i.lte(minerStake.amount.div(minStake)); i = i.add(1)) {
const submissionPossible = await this.submissionPossible(i);
if (submissionPossible) {
return i;
Expand Down Expand Up @@ -792,13 +794,13 @@ class ReputationMiner {
}

// Check the proposed entry is eligible (emulates entryQualifies modifier behaviour)
const [stakeAmount, stakeTimestamp] = await this.colonyNetwork.getMiningStake(this.minerAddress);
const minerStake = await this.getMiningStake();

if (ethers.BigNumber.from(entryIndex).gt(stakeAmount.div(minStake))) {
if (ethers.BigNumber.from(entryIndex).gt(minerStake.amount.div(minStake))) {
return false;
}

if(reputationMiningWindowOpenTimestamp.lt(stakeTimestamp)) {
if(reputationMiningWindowOpenTimestamp.lt(minerStake.timestamp)) {
return false;
}

Expand All @@ -822,6 +824,22 @@ class ReputationMiner {
return true;
}

/**
* Get the stake for the miner
* @return {Promise} Resolves to the mining stake, {amount, timestamp}
*/
getMiningStake() {
return this.colonyNetwork.getMiningStake(this.minerAddress);
}

/**
* Get the minimum stake for reputation mining
* @return {integer} The minimum stake
*/
getMinStake() {
return minStake;
}

/**
* Get the mining cycle duration.
* @return {integer} Mining cycle duration
Expand Down Expand Up @@ -1331,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)}`);
Expand All @@ -1353,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(`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
Expand Down Expand Up @@ -1417,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");
}
Expand Down Expand Up @@ -1488,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.`);
}
}

Expand Down
22 changes: 18 additions & 4 deletions packages/reputation-miner/ReputationMinerClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,18 @@ 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);

const minerStake = await this._miner.getMiningStake();
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
Expand Down Expand Up @@ -264,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);
}

Expand Down Expand Up @@ -456,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);
Expand Down Expand Up @@ -726,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);
Expand All @@ -741,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');
Expand Down
11 changes: 7 additions & 4 deletions packages/reputation-miner/bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,6 +34,7 @@ const {
oraclePort,
processingDelay,
adapterLabel,
startingHash,
} = argv;

class RetryProvider extends ethers.providers.StaticJsonRpcProvider {
Expand All @@ -53,16 +55,16 @@ 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) {
return backoff(() => super.perform(method, params), {retry: RetryProvider.attemptCheck, startingDelay: 1000});
}
}

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();
}

Expand Down Expand Up @@ -126,4 +128,5 @@ const client = new ReputationMinerClient({
oraclePort,
processingDelay
});
client.initialise(colonyNetworkAddress, syncFrom);

client.initialise(colonyNetworkAddress, syncFrom, startingHash);
14 changes: 10 additions & 4 deletions truffle.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,23 @@ module.exports = {
gasPrice: 0,
network_id: "1",
},
mainnet: {
provider: () => {
return new HDWalletProvider("private-key", "https://mainnet.infura.io/v3/infura-key");
},
network_id: "1",
},
goerli: {
provider: () => {
return new HDWalletProvider("replace-with-private-key-when-using", "https://goerli.infura.io/v3/e21146aa267845a2b7b4da025178196d");
return new HDWalletProvider("private-key", "https://goerli.infura.io/v3/infura-key");
},
network_id: "5",
},
mainnet: {
xdai: {
provider: () => {
return new HDWalletProvider("replace-with-private-key-when-using", "https://mainnet.infura.io/v3/e21146aa267845a2b7b4da025178196d");
return new HDWalletProvider("private-key", "https://xdai-archive.blockscout.com/");
},
network_id: "1",
network_id: "100",
},
storageSmoke: {
provider: () => {
Expand Down