Commit f71880a9 authored by Cédric Moreau's avatar Cédric Moreau

[fix] #1037 Migrate module "duniter-prover"

parent 9598ed09
......@@ -23,5 +23,7 @@ app/modules/daemon.js
app/modules/export-bc.js
app/modules/check-config.js
app/modules/config.js
app/modules/prover/*.js
app/modules/prover/lib/*.js
test/*.js
test/**/*.js
\ No newline at end of file
......@@ -51,6 +51,8 @@ app/lib/rules/*.js*
app/lib/logger*js*
app/service/*.js*
app/lib/wot.js*
app/modules/prover/*.js*
app/modules/prover/lib/*.js*
app/modules/router*.js*
app/modules/wizard.js*
app/modules/revert.js*
......
......@@ -60,7 +60,7 @@ export class BlockchainContext {
* Gets a copy of vHEAD, extended with some extra properties.
* @param props The extra properties to add.
*/
async getvHeadCopy(props: any): Promise<any> {
async getvHeadCopy(props: any = {}): Promise<any> {
if (!this.vHEAD) {
await this.refreshHead();
}
......
......@@ -10,7 +10,13 @@ export class ConfDTO {
public currency: string,
public endpoints: string[],
public rmEndpoints: string[],
public rootoffset: number,
public upInterval: number,
public cpu: number,
public nbCores: number,
public prefix: number,
public powSecurityRetryDelay: number,
public powMaxHandicap: number,
public c: number,
public dt: number,
public dtReeval: number,
......@@ -46,6 +52,6 @@ export class ConfDTO {
) {}
static mock() {
return new ConfDTO("", "", [], [], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, null, 0, "", "", "")
return new ConfDTO("", "", [], [], 0, 0, 0.6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, null, 0, "", "", "")
}
}
\ No newline at end of file
"use strict";
import {BlockDTO} from "../dto/BlockDTO"
import {ConfDTO} from "../dto/ConfDTO"
import {FileDAL} from "../dal/fileDAL"
import {DBBlock} from "../db/DBBlock"
import {DBIdentity} from "../dal/sqliteDAL/IdentityDAL"
import {TransactionDTO} from "../dto/TransactionDTO"
import * as local_rules from "./local_rules"
const co = require('co');
const _ = require('underscore');
const common = require('duniter-common');
const indexer = require('../indexer').Indexer
......@@ -158,9 +155,13 @@ export const GLOBAL_RULES_HELPERS = {
// Functions used in an external context too
checkMembershipBlock: (ms:any, current:DBBlock, conf:ConfDTO, dal:FileDAL) => checkMSTarget(ms, current ? { number: current.number + 1} : { number: 0 }, conf, dal),
checkCertificationIsValid: (cert:any, current:DBBlock, findIdtyFunc:any, conf:ConfDTO, dal:FileDAL) => checkCertificationIsValid(current ? current : { number: 0 }, cert, findIdtyFunc, conf, dal),
checkCertificationIsValid: (cert:any, current:DBBlock, findIdtyFunc:any, conf:ConfDTO, dal:FileDAL) => {
return checkCertificationIsValid(current ? current : { number: 0, currency: '' }, cert, findIdtyFunc, conf, dal)
},
checkCertificationIsValidForBlock: (cert:any, block:BlockDTO, idty:DBIdentity, conf:ConfDTO, dal:FileDAL) => checkCertificationIsValid(block, cert, () => idty, conf, dal),
checkCertificationIsValidForBlock: (cert:any, block:{ number:number, currency:string }, findIdtyFunc:(b:{ number:number, currency:string }, pubkey:string, dal:FileDAL) => Promise<any>, conf:ConfDTO, dal:FileDAL) => {
return checkCertificationIsValid(block, cert, findIdtyFunc, conf, dal)
},
isOver3Hops: async (member:any, newLinks:any, newcomers:string[], current:DBBlock, conf:ConfDTO, dal:FileDAL) => {
if (!current) {
......@@ -177,7 +178,7 @@ export const GLOBAL_RULES_HELPERS = {
checkExistsPubkey: (pub:string, dal:FileDAL) => dal.getWrittenIdtyByPubkey(pub),
checkSingleTransaction: (tx:TransactionDTO, block:{ medianTime: number }, conf:ConfDTO, dal:FileDAL, alsoCheckPendingTransactions:boolean) => GLOBAL_RULES_FUNCTIONS.checkSourcesAvailability({
checkSingleTransaction: (tx:TransactionDTO, block:{ medianTime: number }, conf:ConfDTO, dal:FileDAL, alsoCheckPendingTransactions:boolean = false) => GLOBAL_RULES_FUNCTIONS.checkSourcesAvailability({
transactions: [tx],
medianTime: block.medianTime
}, conf, dal, alsoCheckPendingTransactions),
......@@ -228,7 +229,7 @@ async function checkMSTarget (ms:any, block:any, conf:ConfDTO, dal:FileDAL) {
}
}
async function checkCertificationIsValid (block:any, cert:any, findIdtyFunc:any, conf:ConfDTO, dal:FileDAL) {
async function checkCertificationIsValid (block:{ number:number, currency:string }, cert:any, findIdtyFunc:(b:{ number:number, currency:string }, pubkey:string, dal:FileDAL) => Promise<any>, conf:ConfDTO, dal:FileDAL) {
if (block.number == 0 && cert.block_number != 0) {
throw Error('Number must be 0 for root block\'s certifications');
} else {
......@@ -250,10 +251,7 @@ async function checkCertificationIsValid (block:any, cert:any, findIdtyFunc:any,
throw Error('Certifier must be a member')
}
}
// TODO: weird call, we cannot just do "await findIdtyFunc(...)". There is a bug somewhere.
let idty = await co(function*() {
return yield findIdtyFunc(block, cert.to, dal)
})
let idty = await findIdtyFunc(block, cert.to, dal)
let current = block.number == 0 ? null : await dal.getCurrentBlockOrNull();
if (!idty) {
throw Error('Identity does not exist for certified');
......
const constants = require('../lib/constants');
const wizard = require('../lib/wizard');
const logger = require('../lib/logger').NewLogger('wizard');
module.exports = {
duniter: {
......@@ -11,6 +10,7 @@ module.exports = {
onConfiguredExecute: async (server:any) => {
await server.checkConfig()
const logger = require('../lib/logger').NewLogger('wizard')
logger.warn('Configuration seems correct.');
}
}]
......
This diff is collapsed.
import {ConfDTO} from "../../lib/dto/ConfDTO"
import {BlockGenerator, BlockGeneratorWhichProves} from "./lib/blockGenerator"
import {Constants} from "./lib/constants"
import {BlockProver} from "./lib/blockProver"
import {Prover} from "./lib/prover"
const async = require('async');
const contacter = require('duniter-crawler').duniter.methods.contacter;
const common = require('duniter-common');
const Peer = common.document.Peer
export const ProverDependency = {
duniter: {
/*********** Permanent prover **************/
config: {
onLoading: async (conf:ConfDTO) => {
if (conf.cpu === null || conf.cpu === undefined) {
conf.cpu = Constants.DEFAULT_CPU;
}
conf.powSecurityRetryDelay = Constants.POW_SECURITY_RETRY_DELAY;
conf.powMaxHandicap = Constants.POW_MAXIMUM_ACCEPTABLE_HANDICAP;
},
beforeSave: async (conf:ConfDTO) => {
delete conf.powSecurityRetryDelay;
delete conf.powMaxHandicap;
}
},
service: {
output: (server:any) => {
const generator = new BlockGenerator(server);
server.generatorGetJoinData = generator.getSinglePreJoinData.bind(generator)
server.generatorComputeNewCerts = generator.computeNewCerts.bind(generator)
server.generatorNewCertsToLinks = generator.newCertsToLinks.bind(generator)
return new Prover(server)
}
},
methods: {
hookServer: (server:any) => {
const generator = new BlockGenerator(server);
server.generatorGetJoinData = generator.getSinglePreJoinData.bind(generator)
server.generatorComputeNewCerts = generator.computeNewCerts.bind(generator)
server.generatorNewCertsToLinks = generator.newCertsToLinks.bind(generator)
},
prover: (server:any, conf:ConfDTO, logger:any) => new Prover(server),
blockGenerator: (server:any, prover:any) => new BlockGeneratorWhichProves(server, prover),
generateTheNextBlock: async (server:any, manualValues:any) => {
const prover = new BlockProver(server);
const generator = new BlockGeneratorWhichProves(server, prover);
return generator.nextBlock(manualValues);
},
generateAndProveTheNext: async (server:any, block:any, trial:any, manualValues:any) => {
const prover = new BlockProver(server);
const generator = new BlockGeneratorWhichProves(server, prover);
let res = await generator.makeNextBlock(block, trial, manualValues);
return res
}
},
/*********** CLI gen-next + gen-root **************/
cliOptions: [
{value: '--show', desc: 'With gen-next or gen-root commands, displays the generated block.'},
{value: '--check', desc: 'With gen-next: just check validity of generated block.'},
{value: '--at <medianTime>', desc: 'With gen-next --show --check: allows to try in a future time.', parser: parseInt }
],
cli: [{
name: 'gen-next [host] [port] [difficulty]',
desc: 'Tries to generate the next block of the blockchain.',
onDatabaseExecute: async (server:any, conf:ConfDTO, program:any, params:any) => {
const host = params[0];
const port = params[1];
const difficulty = params[2];
const generator = new BlockGeneratorWhichProves(server, null);
return generateAndSend(program, host, port, difficulty, server, () => generator.nextBlock);
}
}, {
name: 'gen-root [host] [port] [difficulty]',
desc: 'Tries to generate the next block of the blockchain.',
preventIfRunning: true,
onDatabaseExecute: async (server:any, conf:ConfDTO, program:any, params:any) => {
const host = params[0];
const port = params[1];
const difficulty = params[2];
const generator = new BlockGeneratorWhichProves(server, null);
let toDelete, catched = true;
do {
try {
await generateAndSend(program, host, port, difficulty, server, () => generator.nextBlock);
catched = false;
} catch (e) {
toDelete = await server.dal.idtyDAL.query('SELECT * FROM idty i WHERE 5 > (SELECT count(*) from cert c where c.`to` = i.pubkey)');
console.log('Deleting', toDelete.map((i:any) => i.pubkey));
await server.dal.idtyDAL.exec('DELETE FROM idty WHERE pubkey IN (' + toDelete.map((i:any) => "'" + i.pubkey + "'").join(',') + ')');
await server.dal.idtyDAL.exec('DELETE FROM cert WHERE `to` IN (' + toDelete.map((i:any) => "'" + i.pubkey + "'").join(',') + ')');
await server.dal.idtyDAL.exec('DELETE FROM cert WHERE `from` IN (' + toDelete.map((i:any) => "'" + i.pubkey + "'").join(',') + ')');
}
} while (catched && toDelete.length);
console.log('Done');
}
}, {
name: 'gen-root-choose [host] [port] [difficulty]',
desc: 'Tries to generate root block, with choice of root members.',
preventIfRunning: true,
onDatabaseExecute: async (server:any, conf:ConfDTO, program:any, params:any) => {
const host = params[0];
const port = params[1];
const difficulty = params[2];
if (!host) {
throw 'Host is required.';
}
if (!port) {
throw 'Port is required.';
}
if (!difficulty) {
throw 'Difficulty is required.';
}
const generator = new BlockGenerator(server);
return generateAndSend(program, host, port, difficulty, server, () => generator.manualRoot);
}
}]
}
}
function generateAndSend(program:any, host:string, port:string, difficulty:string, server:any, getGenerationMethod:any) {
const logger = server.logger;
return new Promise((resolve, reject) => {
async.waterfall([
function (next:any) {
const method = getGenerationMethod(server);
(async() => {
const simulationValues:any = {}
if (program.show && program.check) {
if (program.at && !isNaN(program.at)) {
simulationValues.medianTime = program.at
}
}
const block = await method(null, simulationValues);
next(null, block);
})()
},
function (block:any, next:any) {
if (program.check) {
block.time = block.medianTime;
program.show && console.log(block.getRawSigned());
(async() => {
try {
const parsed = common.parsers.parseBlock.syncWrite(block.getRawSigned());
await server.BlockchainService.checkBlock(parsed, false);
logger.info('Acceptable block');
next();
} catch (e) {
next(e);
}
})()
}
else {
logger.debug('Block to be sent: %s', block.getRawInnerPart());
async.waterfall([
function (subNext:any) {
proveAndSend(program, server, block, server.conf.pair.pub, parseInt(difficulty), host, parseInt(port), subNext);
}
], next);
}
}
], (err:any, data:any) => {
err && reject(err);
!err && resolve(data);
});
});
}
function proveAndSend(program:any, server:any, block:any, issuer:any, difficulty:any, host:any, port:any, done:any) {
const logger = server.logger;
async.waterfall([
function (next:any) {
block.issuer = issuer;
program.show && console.log(block.getRawSigned());
(async () => {
try {
const prover = new BlockProver(server);
const proven = await prover.prove(block, difficulty);
const peer = Peer.fromJSON({
endpoints: [['BASIC_MERKLED_API', host, port].join(' ')]
});
program.show && console.log(proven.getRawSigned());
logger.info('Posted block ' + proven.getRawSigned());
const p = Peer.fromJSON(peer);
const contact = contacter(p.getHostPreferDNS(), p.getPort());
await contact.postBlock(proven.getRawSigned());
} catch(e) {
next(e);
}
})()
}
], done);
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import {Constants} from "./constants"
import {ConfDTO, Keypair} from "../../../lib/dto/ConfDTO"
import {PowEngine} from "./engine"
import {DBBlock} from "../../../lib/db/DBBlock"
const querablep = require('querablep');
const common = require('duniter-common');
const Block = common.document.Block
const POW_FOUND = true;
const POW_NOT_FOUND_YET = false;
export class WorkerFarm {
private theEngine:PowEngine
private onAlmostPoW:any = null
private powPromise:any = null
private stopPromise:any = null
private checkPoWandNotify:any = null
constructor(private server:any, private logger:any) {
this.theEngine = new PowEngine(server.conf, server.logger)
// 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)
}
})
}
changeCPU(cpu:any) {
return this.theEngine.setConf({ cpu })
}
changePoWPrefix(prefix:any) {
return this.theEngine.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;
}
/**
* Starts a new computation of PoW
* @param stuff The necessary data for computing the PoW
*/
async askNewProof(stuff:any) {
// 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
}
}
export class BlockProver {
conf:ConfDTO
pair:Keypair|null
logger:any
waitResolve:any
workerFarmPromise:any
constructor(private server:any) {
this.conf = server.conf
this.pair = this.conf.pair
this.logger = server.logger
const debug = process.execArgv.toString().indexOf('--debug') !== -1;
if(debug) {
//Set an unused port number.
process.execArgv = [];
}
}
getWorker() {
if (!this.workerFarmPromise) {
this.workerFarmPromise = (async () => {
return new WorkerFarm(this.server, this.logger)
})()
}
return this.workerFarmPromise
}
async cancel() {
// If no farm was instanciated, there is nothing to do yet
if (this.workerFarmPromise) {
let farm = await this.getWorker();
if (farm.isComputing() && !farm.isStopping()) {
await farm.stopPoW()
}
if (this.waitResolve) {
this.waitResolve();
this.waitResolve = null;
}
}
}
prove(block:any, difficulty:any, forcedTime:any = null) {
if (this.waitResolve) {
this.waitResolve();
this.waitResolve = null;
}
const remainder = difficulty % 16;
const nbZeros = (difficulty - remainder) / 16;
const highMark = common.constants.PROOF_OF_WORK.UPPER_BOUND[remainder];
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 >= Constants.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', nbZeros, (this.conf.cpu * 100).toFixed(0), block.number, block.issuer.slice(0,6));
const start = Date.now();
let result = await powFarm.askNewProof({
newPoW: { conf: this.conf, block: block, zeros: nbZeros, highMark: highMark, forcedTime: forcedTime, pair: this.pair }
});
if (!result) {
this.logger.info('GIVEN proof-of-work for block#%s with %s leading zeros followed by [0-' + highMark + ']! stop PoW for %s', block.number, nbZeros, this.pair && this.pair.pub.slice(0,6));
throw 'Proof-of-work computation canceled because block received';
} else {
const proof = result.block;
const testsCount = result.testsCount;
const duration = (Date.now() - start);
const testsPerSecond = (testsCount / (duration / 1000)).toFixed(2);
this.logger.info('Done: #%s, %s in %ss (%s tests, ~%s tests/s)', block.number, proof.hash, (duration / 1000).toFixed(2), testsCount, testsPerSecond);
this.logger.info('FOUND proof-of-work with %s leading zeros followed by [0-' + highMark + ']!', nbZeros);
return Block.fromJSON(proof);
}
})()
};
async changeCPU(cpu:number) {
this.conf.cpu = cpu;
const farm = await this.getWorker()
return farm.changeCPU(cpu)
}
async changePoWPrefix(prefix:any) {
const farm = await this.getWorker()
return farm.changePoWPrefix(prefix)
}
private powEvent(found:boolean, hash:string) {
this.server && this.server.push({ pow: { found, hash } });
}
}
"use strict";
module.exports = {
PULLING_MAX_DURATION: 10 * 1000, // 10 seconds
CORES_MAXIMUM_USE_IN_PARALLEL: 8,
MINIMAL_ZEROS_TO_SHOW_IN_LOGS: 3,
POW_MINIMAL_TO_SHOW: 2,
DEFAULT_CPU: 0.6,
NONCE_RANGE: 1000 * 1000 * 1000 * 100,
POW_MAXIMUM_ACCEPTABLE_HANDICAP: 64,
// When to trigger the PoW process again if no PoW is triggered for a while. In milliseconds.
POW_SECURITY_RETRY_DELAY: 10 * 60 * 1000
Object.defineProperty(exports, "__esModule", { value: true });
exports.Constants = {
PULLING_MAX_DURATION: 10 * 1000,
CORES_MAXIMUM_USE_IN_PARALLEL: 8,
MINIMAL_ZEROS_TO_SHOW_IN_LOGS: 3,
POW_MINIMAL_TO_SHOW: 2,
DEFAULT_CPU: 0.6,
NONCE_RANGE: 1000 * 1000 * 1000 * 100,
POW_MAXIMUM_ACCEPTABLE_HANDICAP: 64,
// When to trigger the PoW process again if no PoW is triggered for a while. In milliseconds.
POW_SECURITY_RETRY_DELAY: 10 * 60 * 1000
};
//# sourceMappingURL=constants.js.map
\ No newline at end of file
export const Constants = {
PULLING_MAX_DURATION: 10 * 1000, // 10 seconds
CORES_MAXIMUM_USE_IN_PARALLEL: 8,
MINIMAL_ZEROS_TO_SHOW_IN_LOGS: 3,
POW_MINIMAL_TO_SHOW: 2,
DEFAULT_CPU: 0.6,
NONCE_RANGE: 1000 * 1000 * 1000 * 100,
POW_MAXIMUM_ACCEPTABLE_HANDICAP: 64,
// When to trigger the PoW process again if no PoW is triggered for a while. In milliseconds.
POW_SECURITY_RETRY_DELAY: 10 * 60 * 1000
}
"use strict";
const os = require('os')
const co = require('co')
const querablep = require('querablep')
const powCluster = require('./powCluster')
const constants = require('./constants')
module.exports = function (conf, logger) {
return new PowEngine(conf, logger);
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }