Commit 32a7b905 authored by Cédric Moreau's avatar Cédric Moreau
Browse files

[enh] #875 Migrate back 'indexer' and 'rules' into duniter core

parent 17a6ad77
......@@ -3,9 +3,9 @@ const _ = require('underscore');
const co = require('co');
const Q = require('q');
const common = require('duniter-common');
const indexer = require('duniter-common').indexer;
const indexer = require('../indexer');
const constants = require('../constants');
const rules = require('duniter-common').rules;
const rules = require('../rules')
const Identity = require('../entity/identity');
const Certification = require('../entity/certification');
const Membership = require('../entity/membership');
......
......@@ -3,7 +3,7 @@ const Q = require('q');
const co = require('co');
const _ = require('underscore');
const common = require('duniter-common');
const indexer = require('duniter-common').indexer;
const indexer = require('../indexer');
const logger = require('../logger')('filedal');
const Configuration = require('../entity/configuration');
const Merkle = require('../entity/merkle');
......
......@@ -4,7 +4,7 @@
const _ = require('underscore');
const co = require('co');
const indexer = require('duniter-common').indexer;
const indexer = require('../../indexer');
module.exports = AbstractIndex;
......
......@@ -5,7 +5,7 @@
const co = require('co');
const constants = require('./../../../constants');
const common = require('duniter-common');
const indexer = require('duniter-common').indexer;
const indexer = require('../../../indexer');
const AbstractSQLite = require('./../AbstractSQLite');
const AbstractIndex = require('./../AbstractIndex');
......
......@@ -4,7 +4,7 @@
const co = require('co');
const _ = require('underscore');
const indexer = require('duniter-common').indexer;
const indexer = require('../../../indexer');
const AbstractSQLite = require('./../AbstractSQLite');
const AbstractIndex = require('./../AbstractIndex');
......
......@@ -3,7 +3,7 @@
*/
const co = require('co');
const indexer = require('duniter-common').indexer;
const indexer = require('../../../indexer');
const AbstractSQLite = require('./../AbstractSQLite');
const AbstractIndex = require('./../AbstractIndex');
......
......@@ -5,7 +5,7 @@
const _ = require('underscore');
const co = require('co');
const common = require('duniter-common');
const indexer = require('duniter-common').indexer;
const indexer = require('../../../indexer');
const constants = require('../../../constants');
const AbstractSQLite = require('./../AbstractSQLite');
const AbstractIndex = require('./../AbstractIndex');
......
This diff is collapsed.
"use strict";
const co = require('co');
const _ = require('underscore');
const common = require('duniter-common');
const indexer = require('../indexer');
const local_rules = require('../rules/local_rules');
const constants = common.constants
const keyring = common.keyring
const rawer = common.rawer
const Identity = common.document.Identity
const Transaction = common.document.Transaction
const unlock = common.txunlock
let rules = {};
// Empty logger by default
let logger = {
debug: () => {},
warn: () => {}
}
// TODO: all the global rules should be replaced by index rule someday
rules.FUNCTIONS = {
checkIdentitiesAreWritable: (block, conf, dal) => co(function *() {
let current = yield dal.getCurrentBlockOrNull();
for (const obj of block.identities) {
let idty = Identity.fromInline(obj);
let found = yield dal.getWrittenIdtyByUID(idty.uid);
if (found) {
throw Error('Identity already used');
}
// Because the window rule does not apply on initial certifications
if (current && idty.buid != constants.SPECIAL_BLOCK) {
// From DUP 0.5: we fully check the blockstamp
const basedBlock = yield dal.getBlockByBlockstamp(idty.buid);
// Check if writable
let duration = current.medianTime - parseInt(basedBlock.medianTime);
if (duration > conf.idtyWindow) {
throw Error('Identity is too old and cannot be written');
}
}
}
return true;
}),
checkSourcesAvailability: (block, conf, dal, alsoCheckPendingTransactions) => co(function *() {
let txs = block.getTransactions();
const current = yield dal.getCurrentBlockOrNull();
for (const tx of txs) {
let unlocks = {};
let sumOfInputs = 0;
let maxOutputBase = current.unitbase;
for (const unlock of tx.unlocks) {
let sp = unlock.split(':');
let index = parseInt(sp[0]);
unlocks[index] = sp[1];
}
for (let k = 0, len2 = tx.inputs.length; k < len2; k++) {
let src = tx.inputs[k];
let dbSrc = yield dal.getSource(src.identifier, src.pos);
logger.debug('Source %s:%s:%s:%s = %s', src.amount, src.base, src.identifier, src.pos, dbSrc && dbSrc.consumed);
if (!dbSrc && alsoCheckPendingTransactions) {
// For chained transactions which are checked on sandbox submission, we accept them if there is already
// a previous transaction of the chain already recorded in the pool
dbSrc = yield co(function*() {
let hypotheticSrc = null;
let targetTX = yield dal.getTxByHash(src.identifier);
if (targetTX) {
let outputStr = targetTX.outputs[src.pos];
if (outputStr) {
hypotheticSrc = Transaction.outputStr2Obj(outputStr);
hypotheticSrc.consumed = false;
hypotheticSrc.time = 0;
}
}
return hypotheticSrc;
});
}
if (!dbSrc || dbSrc.consumed) {
logger.warn('Source ' + [src.type, src.identifier, src.pos].join(':') + ' is not available');
throw constants.ERRORS.SOURCE_ALREADY_CONSUMED;
}
sumOfInputs += dbSrc.amount * Math.pow(10, dbSrc.base);
if (block.medianTime - dbSrc.written_time < tx.locktime) {
throw constants.ERRORS.LOCKTIME_PREVENT;
}
let sigResults = local_rules.HELPERS.getSigResult(tx);
let unlocksForCondition = [];
let unlocksMetadata = {};
let unlockValues = unlocks[k];
if (dbSrc.conditions) {
if (unlockValues) {
// Evaluate unlock values
let sp = unlockValues.split(' ');
for (const func of sp) {
let param = func.match(/\((.+)\)/)[1];
if (func.match(/^SIG/)) {
let pubkey = tx.issuers[parseInt(param)];
if (!pubkey) {
logger.warn('Source ' + [src.amount, src.base, src.type, src.identifier, src.pos].join(':') + ' unlock fail (unreferenced signatory)');
throw constants.ERRORS.WRONG_UNLOCKER;
}
unlocksForCondition.push({
pubkey: pubkey,
sigOK: sigResults.sigs[pubkey] && sigResults.sigs[pubkey].matching || false
});
} else if (func.match(/^XHX/)) {
unlocksForCondition.push(param);
}
}
}
if (dbSrc.conditions.match(/CLTV/)) {
unlocksMetadata.currentTime = block.medianTime;
}
if (dbSrc.conditions.match(/CSV/)) {
unlocksMetadata.elapsedTime = block.medianTime - dbSrc.written_time;
}
try {
if (!unlock(dbSrc.conditions, unlocksForCondition, unlocksMetadata)) {
throw Error('Locked');
}
} catch (e) {
logger.warn('Source ' + [src.amount, src.base, src.type, src.identifier, src.pos].join(':') + ' unlock fail');
throw constants.ERRORS.WRONG_UNLOCKER;
}
}
}
let sumOfOutputs = tx.outputs.reduce(function(p, output) {
if (output.base > maxOutputBase) {
throw constants.ERRORS.WRONG_OUTPUT_BASE;
}
return p + output.amount * Math.pow(10, output.base);
}, 0);
if (sumOfInputs !== sumOfOutputs) {
logger.warn('Inputs/Outputs != 1 (%s/%s)', sumOfInputs, sumOfOutputs);
throw constants.ERRORS.WRONG_AMOUNTS;
}
}
return true;
})
};
rules.HELPERS = {
// Functions used in an external context too
checkMembershipBlock: (ms, current, conf, dal) => checkMSTarget(ms, current ? { number: current.number + 1} : { number: 0 }, conf, dal),
checkCertificationIsValid: (cert, current, findIdtyFunc, conf, dal) => checkCertificationIsValid(current ? current : { number: 0 }, cert, findIdtyFunc, conf, dal),
checkCertificationIsValidForBlock: (cert, block, idty, conf, dal) => checkCertificationIsValid(block, cert, () => idty, conf, dal),
isOver3Hops: (member, newLinks, newcomers, current, conf, dal) => co(function *() {
if (!current) {
return Promise.resolve(false);
}
try {
return indexer.DUP_HELPERS.checkPeopleAreNotOudistanced([member], newLinks, newcomers, conf, dal);
} catch (e) {
return true;
}
}),
checkExistsUserID: (uid, dal) => dal.getWrittenIdtyByUID(uid),
checkExistsPubkey: (pub, dal) => dal.getWrittenIdtyByPubkey(pub),
checkSingleTransaction: (tx, block, conf, dal, alsoCheckPendingTransactions) => rules.FUNCTIONS.checkSourcesAvailability({
getTransactions: () => [tx],
medianTime: block.medianTime
}, conf, dal, alsoCheckPendingTransactions),
checkTxBlockStamp: (tx, dal) => co(function *() {
const number = tx.blockstamp.split('-')[0];
const hash = tx.blockstamp.split('-')[1];
const basedBlock = yield dal.getBlockByNumberAndHashOrNull(number, hash);
if (!basedBlock) {
throw "Wrong blockstamp for transaction";
}
// Valuates the blockstampTime field
tx.blockstampTime = basedBlock.medianTime;
const current = yield dal.getCurrentBlockOrNull();
if (current && current.medianTime > basedBlock.medianTime + constants.TX_WINDOW) {
throw "Transaction has expired";
}
})
};
/*****************************
*
* UTILITY FUNCTIONS
*
*****************************/
function checkMSTarget (ms, block, conf, dal) {
return co(function *() {
if (block.number == 0 && ms.number != 0) {
throw Error('Number must be 0 for root block\'s memberships');
}
else if (block.number == 0 && ms.fpr != 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855') {
throw Error('Hash must be E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 for root block\'s memberships');
}
else if (block.number == 0) {
return null; // Valid for root block
} else {
let basedBlock;
try {
basedBlock = yield dal.getBlockByNumberAndHash(ms.number, ms.fpr);
} catch (e) {
throw Error('Membership based on an unexisting block');
}
let current = yield dal.getCurrentBlockOrNull();
if (current && current.medianTime > basedBlock.medianTime + conf.msValidity) {
throw Error('Membership has expired');
}
return basedBlock;
}
});
}
function checkCertificationIsValid (block, cert, findIdtyFunc, conf, dal) {
return co(function *() {
if (block.number == 0 && cert.block_number != 0) {
throw Error('Number must be 0 for root block\'s certifications');
} else {
let basedBlock = {
hash: constants.SPECIAL_HASH
};
if (block.number != 0) {
try {
basedBlock = yield dal.getBlock(cert.block_number);
} catch (e) {
throw Error('Certification based on an unexisting block');
}
try {
const issuer = yield dal.getWrittenIdtyByPubkey(cert.from)
if (!issuer || !issuer.member) {
throw Error('Issuer is not a member')
}
} catch (e) {
throw Error('Certifier must be a member')
}
}
let idty = yield findIdtyFunc(block, cert.to, dal);
let current = block.number == 0 ? null : yield dal.getCurrentBlockOrNull();
if (!idty) {
throw Error('Identity does not exist for certified');
}
else if (current && current.medianTime > basedBlock.medianTime + conf.sigValidity) {
throw Error('Certification has expired');
}
else if (cert.from == idty.pubkey)
throw Error('Rejected certification: certifying its own self-certification has no meaning');
else {
const buid = [cert.block_number, basedBlock.hash].join('-');
if (cert.block_hash && buid != [cert.block_number, cert.block_hash].join('-'))
throw Error('Certification based on an unexisting block buid. from ' + cert.from.substring(0,8) + ' to ' + idty.pubkey.substring(0,8));
idty.currency = conf.currency;
const raw = rawer.getOfficialCertification(_.extend(idty, {
idty_issuer: idty.pubkey,
idty_uid: idty.uid,
idty_buid: idty.buid,
idty_sig: idty.sig,
issuer: cert.from,
buid: buid,
sig: ''
}));
const verified = keyring.verify(raw, cert.sig, cert.from);
if (!verified) {
throw constants.ERRORS.WRONG_SIGNATURE_FOR_CERT
}
return true;
}
}
});
}
module.exports = {
setLogger: (newLogger) => logger = newLogger,
rules
};
"use strict";
const common = require('duniter-common');
const constants = common.constants
module.exports = {
maxAcceleration
}
function maxAcceleration (conf) {
let maxGenTime = Math.ceil(conf.avgGenTime * constants.POW_DIFFICULTY_RANGE_RATIO);
return Math.ceil(maxGenTime * conf.medianTimeBlocks);
}
\ No newline at end of file
"use strict";
const _ = require('underscore');
const co = require('co');
const common = require('duniter-common');
const local_rules = require('../rules/local_rules')
const global_rules = require('../rules/global_rules').rules
const Block = common.document.Block
let rules = {};
rules.LOCAL = local_rules.FUNCTIONS;
rules.GLOBAL = global_rules.FUNCTIONS;
rules.HELPERS = {};
_.extend(rules.HELPERS, local_rules.HELPERS);
_.extend(rules.HELPERS, global_rules.HELPERS);
rules.ALIAS = {
ALL_LOCAL: (block, conf) => co(function *() {
yield rules.LOCAL.checkParameters(block);
yield rules.LOCAL.checkProofOfWork(block);
yield rules.LOCAL.checkInnerHash(block);
yield rules.LOCAL.checkPreviousHash(block);
yield rules.LOCAL.checkPreviousIssuer(block);
yield rules.LOCAL.checkUnitBase(block);
yield rules.LOCAL.checkBlockSignature(block);
yield rules.LOCAL.checkBlockTimes(block, conf);
yield rules.LOCAL.checkIdentitiesSignature(block);
yield rules.LOCAL.checkIdentitiesUserIDConflict(block, conf);
yield rules.LOCAL.checkIdentitiesPubkeyConflict(block, conf);
yield rules.LOCAL.checkIdentitiesMatchJoin(block, conf);
yield rules.LOCAL.checkMembershipUnicity(block, conf);
yield rules.LOCAL.checkRevokedUnicity(block, conf);
yield rules.LOCAL.checkRevokedAreExcluded(block, conf);
yield rules.LOCAL.checkMembershipsSignature(block);
yield rules.LOCAL.checkPubkeyUnicity(block);
yield rules.LOCAL.checkCertificationOneByIssuer(block, conf);
yield rules.LOCAL.checkCertificationUnicity(block, conf);
yield rules.LOCAL.checkCertificationIsntForLeaverOrExcluded(block, conf);
yield rules.LOCAL.checkTxVersion(block);
yield rules.LOCAL.checkTxIssuers(block);
yield rules.LOCAL.checkTxSources(block);
yield rules.LOCAL.checkTxRecipients(block);
yield rules.LOCAL.checkTxAmounts(block);
yield rules.LOCAL.checkTxSignature(block);
}),
ALL_LOCAL_BUT_POW_AND_SIGNATURE: (block, conf) => co(function *() {
yield rules.LOCAL.checkParameters(block);
yield rules.LOCAL.checkInnerHash(block);
yield rules.LOCAL.checkPreviousHash(block);
yield rules.LOCAL.checkPreviousIssuer(block);
yield rules.LOCAL.checkUnitBase(block);
yield rules.LOCAL.checkBlockTimes(block, conf);
yield rules.LOCAL.checkIdentitiesSignature(block);
yield rules.LOCAL.checkIdentitiesUserIDConflict(block, conf);
yield rules.LOCAL.checkIdentitiesPubkeyConflict(block, conf);
yield rules.LOCAL.checkIdentitiesMatchJoin(block, conf);
yield rules.LOCAL.checkMembershipUnicity(block, conf);
yield rules.LOCAL.checkRevokedUnicity(block, conf);
yield rules.LOCAL.checkRevokedAreExcluded(block, conf);
yield rules.LOCAL.checkMembershipsSignature(block);
yield rules.LOCAL.checkPubkeyUnicity(block);
yield rules.LOCAL.checkCertificationOneByIssuer(block, conf);
yield rules.LOCAL.checkCertificationUnicity(block, conf);
yield rules.LOCAL.checkCertificationIsntForLeaverOrExcluded(block, conf);
yield rules.LOCAL.checkTxVersion(block);
yield rules.LOCAL.checkTxIssuers(block);
yield rules.LOCAL.checkTxSources(block);
yield rules.LOCAL.checkTxRecipients(block);
yield rules.LOCAL.checkTxAmounts(block);
yield rules.LOCAL.checkTxSignature(block);
})
};
rules.CHECK = {
ASYNC: {
ALL_LOCAL: checkLocal(rules.ALIAS.ALL_LOCAL),
ALL_LOCAL_BUT_POW: checkLocal(rules.ALIAS.ALL_LOCAL_BUT_POW_AND_SIGNATURE)
}
};
function checkLocal(contract) {
return (b, conf, done) => {
return co(function *() {
try {
const block = Block.fromJSON(b);
yield contract(block, conf);
done && done();
} catch (err) {
if (done) return done(err);
throw err;
}
});
};
}
module.exports = rules;
"use strict";
const co = require('co');
const _ = require('underscore');
const common = require('duniter-common');
const indexer = require('../indexer');
const constants = common.constants
const hashf = common.hashf
const keyring = common.keyring
const rawer = common.rawer
const Block = common.document.Block
const Identity = common.document.Identity
const Membership = common.document.Membership
const Transaction = common.document.Transaction
const maxAcceleration = require('./helpers').maxAcceleration
let rules = {};
// TODO: make a global variable 'index' instead of recomputing the index each time
rules.FUNCTIONS = {
checkParameters: (block) => co(function *() {
if (block.number == 0 && !block.parameters) {
throw Error('Parameters must be provided for root block');
}
else if (block.number > 0 && block.parameters) {
throw Error('Parameters must not be provided for non-root block');
}
return true;
}),
checkProofOfWork: (block) => co(function *() {
let remainder = block.powMin % 16;
let nb_zeros = (block.powMin - remainder) / 16;
const powRegexp = new RegExp('^0{' + nb_zeros + '}');
if (!block.hash.match(powRegexp)) {
throw Error('Not a proof-of-work');
}
return true;
}),
checkInnerHash: (block) => co(function *() {
let inner_hash = hashf(block.getRawInnerPart()).toUpperCase();
if (block.inner_hash != inner_hash) {
throw Error('Wrong inner hash');
}
return true;
}),
checkPreviousHash: (block) => co(function *() {
if (block.number == 0 && block.previousHash) {
throw Error('PreviousHash must not be provided for root block');
}
else if (block.number > 0 && !block.previousHash) {
throw Error('PreviousHash must be provided for non-root block');
}
return true;
}),
checkPreviousIssuer: (block) => co(function *() {
if (block.number == 0 && block.previousIssuer)
throw Error('PreviousIssuer must not be provided for root block');
else if (block.number > 0 && !block.previousIssuer)
throw Error('PreviousIssuer must be provided for non-root block');
return true;
}),
checkUnitBase: (block) => co(function *() {
if (block.number == 0 && block.unitbase != 0) {
throw Error('UnitBase must equal 0 for root block');
}
return true;
}),
checkBlockSignature: (block) => co(function *() {
if (!keyring.verify(block.getSignedPart(), block.signature, block.issuer))
throw Error('Block\'s signature must match');
return true;
}),
checkBlockTimes: (block, conf) => co(function *() {
const time = parseInt(block.time);
const medianTime = parseInt(block.medianTime);
if (block.number > 0 && (time < medianTime || time > medianTime + maxAcceleration(conf)))
throw Error('A block must have its Time between MedianTime and MedianTime + ' + maxAcceleration(conf));
else if (block.number == 0 && time != medianTime)
throw Error('Root block must have Time equal MedianTime');
return true;
}),
checkIdentitiesSignature: (block) => co(function *() {
let i = 0;
let wrongSig = false;
while (!wrongSig && i < block.identities.length) {
const idty = Identity.fromInline(block.identities[i]);
idty.currency = block.currency;