Commit aaee365f authored by Éloïs's avatar Éloïs

[feat] oxyde-pow: refactor duniter prover to use rust cluster

parent 07a4a5b5
......@@ -268,7 +268,6 @@ module.exports = {
X_PERCENT: 0.9,
PERCENTROT: 2 / 3,
BLOCKSROT: 20,
POWDELAY: 0,
AVGGENTIME: 16 * 60,
DTDIFFEVAL: 10,
MEDIANTIMEBLOCKS: 20,
......
......@@ -129,8 +129,9 @@ export class ConfDTO
public upInterval: number,
public cpu: number,
public nbCores: number,
public ecoPow: boolean,
public prefix: number,
public powSecurityRetryDelay: number,
public powMaxDuration: number,
public powMaxHandicap: number,
public c: number,
public dt: number,
......@@ -149,7 +150,6 @@ export class ConfDTO
public sigStock: number,
public xpercent: number,
public percentRot: number,
public powDelay: number,
public avgGenTime: number,
public medianTimeBlocks: number,
public httplogs: boolean,
......@@ -215,6 +215,7 @@ export class ConfDTO
3600 * 1000,
constants.PROOF_OF_WORK.DEFAULT.CPU,
1,
false,
constants.PROOF_OF_WORK.DEFAULT.PREFIX,
0,
0,
......@@ -235,7 +236,6 @@ export class ConfDTO
constants.CONTRACT.DEFAULT.SIGSTOCK,
constants.CONTRACT.DEFAULT.X_PERCENT,
constants.CONTRACT.DEFAULT.PERCENTROT,
constants.CONTRACT.DEFAULT.POWDELAY,
constants.CONTRACT.DEFAULT.AVGGENTIME,
constants.CONTRACT.DEFAULT.MEDIANTIMEBLOCKS,
false,
......@@ -271,7 +271,7 @@ export class ConfDTO
}
static defaultConf() {
/*return new ConfDTO("", "", [], [], 0, 3600 * 1000, constants.PROOF_OF_WORK.DEFAULT.CPU, 1, constants.PROOF_OF_WORK.DEFAULT.PREFIX, 0, 0, constants.CONTRACT.DEFAULT.C, constants.CONTRACT.DEFAULT.DT, constants.CONTRACT.DEFAULT.DT_REEVAL, 0, constants.CONTRACT.DEFAULT.UD0, 0, 0, constants.CONTRACT.DEFAULT.STEPMAX, constants.CONTRACT.DEFAULT.SIGPERIOD, 0, constants.CONTRACT.DEFAULT.SIGVALIDITY, constants.CONTRACT.DEFAULT.MSVALIDITY, constants.CONTRACT.DEFAULT.SIGQTY, constants.CONTRACT.DEFAULT.SIGSTOCK, constants.CONTRACT.DEFAULT.X_PERCENT, constants.CONTRACT.DEFAULT.PERCENTROT, constants.CONTRACT.DEFAULT.POWDELAY, constants.CONTRACT.DEFAULT.AVGGENTIME, constants.CONTRACT.DEFAULT.MEDIANTIMEBLOCKS, false, 3000, false, constants.BRANCHES.DEFAULT_WINDOW_SIZE, constants.CONTRACT.DEFAULT.IDTYWINDOW, constants.CONTRACT.DEFAULT.MSWINDOW, constants.CONTRACT.DEFAULT.SIGWINDOW, 0, { pub:'', sec:'' }, null, "", "", 0, "", "", "", "", 0, "", "", null, false, "", true, true)*/
/*return new ConfDTO("", "", [], [], 0, 3600 * 1000, constants.PROOF_OF_WORK.DEFAULT.CPU, 1, constants.PROOF_OF_WORK.DEFAULT.PREFIX, 0, 0, constants.CONTRACT.DEFAULT.C, constants.CONTRACT.DEFAULT.DT, constants.CONTRACT.DEFAULT.DT_REEVAL, 0, constants.CONTRACT.DEFAULT.UD0, 0, 0, constants.CONTRACT.DEFAULT.STEPMAX, constants.CONTRACT.DEFAULT.SIGPERIOD, 0, constants.CONTRACT.DEFAULT.SIGVALIDITY, constants.CONTRACT.DEFAULT.MSVALIDITY, constants.CONTRACT.DEFAULT.SIGQTY, constants.CONTRACT.DEFAULT.SIGSTOCK, constants.CONTRACT.DEFAULT.X_PERCENT, constants.CONTRACT.DEFAULT.PERCENTROT, constants.CONTRACT.DEFAULT.AVGGENTIME, constants.CONTRACT.DEFAULT.MEDIANTIMEBLOCKS, false, 3000, false, constants.BRANCHES.DEFAULT_WINDOW_SIZE, constants.CONTRACT.DEFAULT.IDTYWINDOW, constants.CONTRACT.DEFAULT.MSWINDOW, constants.CONTRACT.DEFAULT.SIGWINDOW, 0, { pub:'', sec:'' }, null, "", "", 0, "", "", "", "", 0, "", "", null, false, "", true, true)*/
return {
currency: null,
endpoints: [],
......@@ -289,7 +289,6 @@ export class ConfDTO
sigQty: constants.CONTRACT.DEFAULT.SIGQTY,
xpercent: constants.CONTRACT.DEFAULT.X_PERCENT,
percentRot: constants.CONTRACT.DEFAULT.PERCENTROT,
powDelay: constants.CONTRACT.DEFAULT.POWDELAY,
avgGenTime: constants.CONTRACT.DEFAULT.AVGGENTIME,
dtDiffEval: constants.CONTRACT.DEFAULT.DTDIFFEVAL,
medianTimeBlocks: constants.CONTRACT.DEFAULT.MEDIANTIMEBLOCKS,
......
......@@ -263,7 +263,6 @@ export class TransactionDTO implements Cloneable {
if (dubp_version >= 12) {
sigResult.sigs[i].ok = verify(raw, sig, pub);
} else {
// TODO ESZ list all invalid transactions
sigResult.sigs[i].ok = verify(raw, sig, pub);
}
matching = sigResult.sigs[i].ok;
......
......@@ -167,9 +167,9 @@ const tasks: any = {
async.waterfall(
[
function (next: any) {
simpleInteger(
"Start computation of a new block if none received since (seconds)",
"powDelay",
simpleFloat(
"Proportion of the cpu dedicated to the pow (between 0 and 1)",
"cpu",
conf,
next
);
......
......@@ -44,14 +44,17 @@ export const ProverDependency = {
} else if (conf.nbCores <= 0) {
conf.nbCores = 1;
}
if (conf.ecoPow === null || conf.ecoPow === undefined) {
conf.ecoPow = ProverConstants.DEFAULT_ECO_MODE;
}
if (conf.prefix === null || conf.prefix === undefined) {
conf.prefix = ProverConstants.DEFAULT_PEER_ID;
}
conf.powSecurityRetryDelay = ProverConstants.POW_SECURITY_RETRY_DELAY;
conf.powMaxDuration = ProverConstants.POW_MAX_DURATION;
conf.powMaxHandicap = ProverConstants.POW_MAXIMUM_ACCEPTABLE_HANDICAP;
},
beforeSave: async (conf: ConfDTO) => {
delete conf.powSecurityRetryDelay;
delete conf.powMaxDuration;
delete conf.powMaxHandicap;
},
},
......@@ -88,14 +91,9 @@ export const ProverDependency = {
prover: (server: Server) => new Prover(server),
blockGenerator: (server: Server, prover: any) =>
new BlockGeneratorWhichProves(server, prover),
generateTheNextBlock: async (server: Server, manualValues: any) => {
const prover = new BlockProver(server);
const generator = new BlockGeneratorWhichProves(server, prover);
return generator.nextBlock(manualValues);
},
generateAndProveTheNext: async (
server: Server,
block: any,
block: BlockDTO | null,
trial: any,
manualValues: any
) => {
......@@ -322,7 +320,7 @@ function generateAndSend(
function proveAndSend(
program: any,
server: Server,
block: any,
block: BlockDTO,
issuer: any,
difficulty: any,
done: any
......
......@@ -33,6 +33,7 @@ import { DataErrors } from "../../../lib/common-libs/errors";
import { Underscore } from "../../../lib/common-libs/underscore";
import { DBCert } from "../../../lib/dal/sqliteDAL/CertDAL";
import { Map } from "../../../lib/common-libs/crypto/map";
import { BlockProver } from "./blockProver";
const inquirer = require("inquirer");
......@@ -82,7 +83,10 @@ export class BlockGenerator {
return this.server.dal;
}
nextBlock(manualValues: any = {}, simulationValues: any = {}) {
nextBlock(
manualValues: any = {},
simulationValues: any = {}
): Promise<BlockDTO> {
return this.generateNextBlock(
new NextBlockGenerator(this.mainContext, this.server, this.logger),
manualValues,
......@@ -670,7 +674,11 @@ export class BlockGenerator {
block.number = current ? current.number + 1 : 0;
// Compute the new MedianTime
if (block.number == 0) {
block.medianTime = moment.utc().unix() - this.conf.rootoffset;
if (manualValues && manualValues.time) {
block.medianTime = manualValues.time;
} else {
block.medianTime = moment.utc().unix() - this.conf.rootoffset;
}
} else {
block.medianTime = vHEAD.medianTime;
}
......@@ -888,6 +896,12 @@ export class BlockGenerator {
block.issuersCount = vHEAD.issuersCount;
block.issuersFrame = vHEAD.issuersFrame;
block.issuersFrameVar = vHEAD.issuersFrameVar;
// Block time
if (manualValues && manualValues.time && block.number > 0) {
block.time = manualValues.time;
} else {
block.time = this.generateBlockTime(block);
}
// Manual values before hashing
if (manualValues) {
Underscore.extend(
......@@ -896,31 +910,49 @@ export class BlockGenerator {
);
}
// InnerHash
block.time = block.medianTime;
block.inner_hash = hashf(rawer.getBlockInnerPart(block)).toUpperCase();
return block;
}
private generateBlockTime(block: BlockDTO) {
if (block.number === 0) {
return block.medianTime;
} else {
const now = moment.utc().unix();
const maxAcceleration = LOCAL_RULES_HELPERS.maxAcceleration(this.conf);
const timeoffset =
block.number >= this.conf.medianTimeBlocks
? 0
: this.conf.rootoffset || 0;
const medianTime = block.medianTime;
const upperBound = Math.min(
medianTime + maxAcceleration,
now - timeoffset
);
return Math.max(medianTime, upperBound);
}
}
}
export class BlockGeneratorWhichProves extends BlockGenerator {
constructor(server: Server, private prover: any) {
prover: BlockProver;
constructor(server: Server, private prover_: BlockProver | null) {
super(server);
this.prover = prover_ || new BlockProver(server);
}
async makeNextBlock(
block: DBBlock | null,
block: BlockDTO | null,
trial?: number | null,
manualValues: any = null
) {
): Promise<BlockDTO> {
const unsignedBlock = block || (await this.nextBlock(manualValues));
const trialLevel =
trial ||
(await this.mainContext.getIssuerPersonalizedDifficulty(this.selfPubkey));
return this.prover.prove(
unsignedBlock,
trialLevel,
(manualValues && manualValues.time) || null
);
return this.prover.prove(unsignedBlock, trialLevel);
}
}
......@@ -1060,6 +1092,9 @@ class NextBlockGenerator implements BlockGeneratorInterface {
* @constructor
*/
class ManualRootGenerator implements BlockGeneratorInterface {
conf() {
return null;
}
findNewCertsFromWoT() {
return Promise.resolve({});
}
......
......@@ -11,155 +11,97 @@
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
import { ProverConstants } from "./constants";
import { Server } from "../../../../server";
import { PowEngine } from "./engine";
import { DBBlock } from "../../../lib/db/DBBlock";
import { CommonConstants } from "../../../lib/common-libs/constants";
import { BlockDTO } from "../../../lib/dto/BlockDTO";
import { ConfDTO, Keypair } from "../../../lib/dto/ConfDTO";
import { ValidProof } from "../../../../neon/lib";
import { hashf } from "../../../lib/common-libs";
import { Master as PowCluster } from "./powCluster";
const os = require("os");
const querablep = require("querablep");
const POW_FOUND = true;
const POW_NOT_FOUND_YET = false;
export class WorkerFarm {
private theEngine: PowEngine;
private onAlmostPoW: any = null;
private powCluster: PowCluster;
private powPromise: any = null;
private stopPromise: any = null;
private checkPoWandNotify: any = null;
constructor(private server: Server, private logger: any) {
this.theEngine = new PowEngine(server.conf, server.logger, server.dal);
// An utility method to filter the pow notifications
this.checkPoWandNotify = (hash: string, block: DBBlock, found: boolean) => {
const matches = hash.match(/^(0{2,})[^0]/);
if (matches && this.onAlmostPoW) {
this.onAlmostPoW(hash, matches, block, found);
}
};
// Keep track of PoW advancement
this.theEngine.setOnInfoMessage((message: any) => {
if (message.error) {
this.logger.error(
"Error in engine#%s:",
this.theEngine.id,
message.error
);
} else if (message.pow) {
// A message about the PoW
const msg = message.pow;
this.checkPoWandNotify(msg.pow, msg.block, POW_NOT_FOUND_YET);
}
});
this.powCluster = new PowCluster(server.conf.nbCores, server.logger);
}
get nbWorkers() {
return this.theEngine.getNbWorkers();
return this.powCluster.nbWorkers;
}
changeCPU(cpu: any) {
return this.theEngine.setConf({ cpu });
changeCPU(cpu: number) {
return this.powCluster.setConf({ cpu });
}
changePoWPrefix(prefix: any) {
return this.theEngine.setConf({ prefix });
changePoWPrefix(prefix: number) {
return this.powCluster.setConf({ prefix });
}
isComputing() {
return this.powPromise !== null && !this.powPromise.isResolved();
}
isStopping() {
return this.stopPromise !== null && !this.stopPromise.isResolved();
}
/**
* Eventually stops the engine PoW if one was computing
*/
stopPoW() {
this.stopPromise = querablep(this.theEngine.cancel());
return this.stopPromise;
stopPoW(notify: boolean): void {
this.powCluster.cancel(notify);
}
shutDownEngine() {
return this.theEngine.shutDown();
return this.powCluster.shutDown();
}
/**
* Starts a new computation of PoW
* @param stuff The necessary data for computing the PoW
* @param proofAsk The necessary data for computing the PoW
*/
async askNewProof(stuff: ProofAsk) {
async askNewProof(proofAsk: ProofAsk): Promise<ValidProof | null> {
// Starts the PoW
this.powPromise = querablep(this.theEngine.prove(stuff));
const res = await this.powPromise;
if (res) {
this.checkPoWandNotify(res.pow.pow, res.pow.block, POW_FOUND);
}
return res && res.pow;
}
setOnAlmostPoW(onPoW: any) {
this.onAlmostPoW = onPoW;
this.powPromise = querablep(this.powCluster.prove(proofAsk));
return await this.powPromise;
}
}
export class BlockProver {
logger: any;
waitResolve: any;
workerFarmPromise: any;
workerFarm: WorkerFarm;
constructor(private server: Server) {
this.logger = server.logger;
const debug = process.execArgv.toString().indexOf("--debug") !== -1;
if (debug) {
//Set an unused port number.
process.execArgv = [];
}
this.workerFarm = new WorkerFarm(this.server, this.logger);
}
get conf(): ConfDTO {
return this.server.conf;
}
get pair(): Keypair | null {
get pair(): Keypair {
return this.conf.pair;
}
getWorker(): Promise<WorkerFarm> {
if (!this.workerFarmPromise) {
this.workerFarmPromise = (async () => {
return new WorkerFarm(this.server, this.logger);
})();
}
return this.workerFarmPromise;
}
async cancel() {
async cancel(notify: boolean) {
// If no farm was instanciated, there is nothing to do yet
if (this.workerFarmPromise) {
let farm = await this.getWorker();
await farm.stopPoW();
if (this.waitResolve) {
this.waitResolve();
this.waitResolve = null;
}
this.workerFarm.stopPoW(notify);
if (this.waitResolve) {
this.waitResolve();
this.waitResolve = null;
}
}
prove(block: any, difficulty: any, forcedTime: any = null) {
prove(block: BlockDTO, difficulty: number) {
if (this.waitResolve) {
this.waitResolve();
this.waitResolve = null;
}
const ecoMode = this.conf.ecoPow && difficulty > block.powMin;
const remainder = difficulty % 16;
const nbZeros = (difficulty - remainder) / 16;
const highMark = CommonConstants.PROOF_OF_WORK.UPPER_BOUND[remainder];
......@@ -167,64 +109,41 @@ export class BlockProver {
block.version + 1 === CommonConstants.DUBP_NEXT_VERSION;
return (async () => {
let powFarm = await this.getWorker();
if (block.number == 0) {
// On initial block, difficulty is the one given manually
block.powMin = difficulty;
}
// Start
powFarm.setOnAlmostPoW(
(pow: any, matches: any, aBlock: any, found: boolean) => {
this.powEvent(found, pow);
if (
matches &&
matches[1].length >= ProverConstants.MINIMAL_ZEROS_TO_SHOW_IN_LOGS
) {
this.logger.info(
"Matched %s zeros %s with Nonce = %s for block#%s by %s",
matches[1].length,
pow,
aBlock.nonce,
aBlock.number,
aBlock.issuer.slice(0, 6)
);
}
}
);
block.nonce = 0;
this.logger.info(
"Generating proof-of-work with %s leading zeros followed by [0-" +
highMark +
"]... (CPU usage set to %s%) for block#%s",
"]... (CPU usage set to %s%, use %s cores) for block#%s",
nbZeros,
(this.conf.cpu * 100).toFixed(0),
ecoMode ? 1 : this.workerFarm.nbWorkers,
block.number,
block.issuer.slice(0, 6)
);
const start = Date.now();
let result = await powFarm.askNewProof({
let proofOrNull = await this.workerFarm.askNewProof({
maxDuration: this.conf.powMaxDuration,
newPoW: {
conf: {
powNoSecurity: this.conf.powNoSecurity,
cpu: this.conf.cpu,
prefix: this.conf.prefix ? String(this.conf.prefix) : "",
avgGenTime: this.conf.avgGenTime,
cpu: this.conf.cpu,
medianTimeBlocks: this.conf.medianTimeBlocks,
nbCores: ecoMode ? 1 : undefined,
powNoSecurity: this.conf.powNoSecurity,
prefix: this.conf.prefix ? String(this.conf.prefix) : "",
},
block: block,
zeros: nbZeros,
highMark: highMark,
forcedTime: forcedTime,
difficulty,
pair: this.pair,
},
specialNonce: notifyVersionJumpReady
? 999 * (ProverConstants.NONCE_RANGE / 1000)
: 0,
specialNonce: notifyVersionJumpReady ? 999 : 0,
});
if (!result) {
if (proofOrNull === null) {
this.logger.info(
"GIVEN proof-of-work for block#%s with %s leading zeros followed by [0-" +
highMark +
......@@ -235,18 +154,27 @@ export class BlockProver {
);
throw "Proof-of-work computation canceled because block received";
} else {
const proof = result.block;
const testsCount = result.testsCount * powFarm.nbWorkers;
let proof: ValidProof = proofOrNull;
// Apply proof to block
block.hash = proof.hash;
block.nonce = proof.nonce;
block.signature = proof.sig;
if (!block.inner_hash || block.inner_hash === "") {
block.inner_hash = hashf(block.getRawInnerPart()).toUpperCase();
}
const testsCount = proofOrNull.itersCount * this.workerFarm.nbWorkers;
const duration = Date.now() - start;
const testsPerSecond = testsCount / (duration / 1000);
this.logger.info(
"Done: #%s, %s in %ss (~%s tests, ~%s tests/s, using %s cores, CPU %s%)",
block.number,
proof.hash,
block.hash,
(duration / 1000).toFixed(2),
testsCount,
testsPerSecond.toFixed(2),
powFarm.nbWorkers,
ecoMode ? 1 : this.workerFarm.nbWorkers,
Math.floor(100 * this.conf.cpu)
);
this.logger.info(
......@@ -255,31 +183,26 @@ export class BlockProver {
"]!",
nbZeros
);
return BlockDTO.fromJSONObject(proof);
return BlockDTO.fromJSONObject(block);
}
})();
}
async changeCPU(cpu: number) {
this.conf.cpu = cpu;
const farm = await this.getWorker();
return farm.changeCPU(cpu);
return this.workerFarm.changeCPU(cpu);
}
async changePoWPrefix(prefix: any) {
async changePoWPrefix(prefix: number) {
this.conf.prefix = prefix;
const farm = await this.getWorker();
return farm.changePoWPrefix(prefix);
}
private powEvent(found: boolean, hash: string) {
this.server && this.server.push({ pow: { found, hash } });
return this.workerFarm.changePoWPrefix(prefix);
}
}
export interface ProofAsk {
initialTestsPerRound?: number;
maxDuration?: number;
maxDuration: number;
specialNonce?: number;
newPoW: {
conf: {
......@@ -290,11 +213,8 @@ export interface ProofAsk {
avgGenTime: number;
medianTimeBlocks: number;
};
block: any;
zeros: number;
highMark: string;
forcedTime?: number;
pair: Keypair | null;
turnDuration?: number;
block: BlockDTO;
difficulty: number;
pair: Keypair;
};
}
......@@ -18,6 +18,7 @@ export const ProverConstants = {
POW_MINIMAL_TO_SHOW: 2,
DEFAULT_CPU: 0.6,
DEFAULT_ECO_MODE: true,
DEFAULT_PEER_ID: 1,
MIN_PEER_ID: 1,
MAX_PEER_ID: 899, // Due to MAX_SAFE_INTEGER = 9007199254740991 (16 digits, and we use 11 digits for the nonce + 2 digits for core number => 3 digits for the peer, must be below 900)
......@@ -27,6 +28,6 @@ export const ProverConstants = {
POW_MAXIMUM_ACCEPTABLE_HANDICAP: 64,
POW_NB_PAUSES_PER_ROUND: 10,