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

Applying Global & Local rules to block's reception

parent 706737a1
......@@ -17,7 +17,9 @@ function GlobalValidator (conf, dao) {
this.checkSignatures = function (block, done) {
async.series([
async.apply(checkCertificationsAreValid, block)
], done);
], function (err) {
done(err);
});
};
this.validate = function (block, done) {
......@@ -34,7 +36,13 @@ function GlobalValidator (conf, dao) {
async.apply(checkCertificationsDelayIsRespected, block),
async.apply(checkJoinersHaveEnoughCertifications, block),
async.apply(checkJoinersAreNotOudistanced, block)
], done);
], function (err) {
done(err);
});
};
this.isOver3Hops = function (member, wot, newLinks, done) {
isOver3Hops(member, wot, newLinks, dao, done);
};
/*****************************
......
......@@ -151,7 +151,18 @@ IdentitySchema.statics.getMember = function(pubkey, done){
done('More than one matching pubkey & member for ' + pubkey);
return;
}
done(null, identities.length == 1 && identities[0]);
done(null, identities.length == 1 ? identities[0] : null);
});
}
IdentitySchema.statics.getMemberByUserID = function(uid, done){
var Identity = this.model('Identity');
Identity.find({ "uid": uid, "member": true }, function (err, identities) {
if(identities.length > 1){
done('More than one matching pubkey & member for uid ' + uid);
return;
}
done(null, identities.length == 1 ? identities[0] : null);
});
}
......@@ -165,9 +176,9 @@ IdentitySchema.statics.getToBeKicked = function(done){
Identity.find({ kick: true }, done);
};
IdentitySchema.statics.setKicked = function(pubkey, hash, distancedKeys, notEnoughLinks, done){
IdentitySchema.statics.setKicked = function(pubkey, hash, notEnoughLinks, done){
var Identity = this.model('Identity');
Identity.update({ "pubkey": pubkey, hash: hash }, { kick: (distancedKeys.length > 0 || notEnoughLinks), distanced: distancedKeys }, function (err) {
Identity.update({ "pubkey": pubkey, hash: hash }, { kick: notEnoughLinks }, function (err) {
done(err);
});
};
......
......@@ -28,6 +28,32 @@ LinkSchema.statics.currentValidLinks = function (fpr, done) {
Link.find({ target: fpr, obsolete: false }, done);
}
LinkSchema.statics.getValidLinksFrom = function (from, done) {
var Link = this.model('Link');
Link.find({ source: from, obsolete: false }, done);
}
LinkSchema.statics.getValidLinksTo = function (to, done) {
var Link = this.model('Link');
Link.find({ target: to, obsolete: false }, done);
}
LinkSchema.statics.getValidFromTo = function (from, to, done) {
var Link = this.model('Link');
Link.find({ source: from, target: to, obsolete: false }, done);
}
LinkSchema.statics.getObsoletesFromTo = function (from, to, done) {
var Link = this.model('Link');
Link
.find({ source: from, target: to, obsolete: true })
.sort({ 'timestamp': -1 })
.limit(1)
.exec(function (err, links) {
done(err, links);
});
}
/**
* Mark as obsolete the links with an age equal to or below a given date
**/
......@@ -48,256 +74,4 @@ LinkSchema.statics.unobsoletesAllLinks = function (done) {
});
}
/**
* Mark as obsolete the links with an age equal to or below a given date
**/
LinkSchema.statics.isStillOver3Steps = function (fpr, ofMembers, newLinks, done) {
var Link = this.model('Link');
var newCertifiers = newLinks[fpr] || [];
var remainingKeys = ofMembers.slice();
// Without self
remainingKeys = _(remainingKeys).difference([fpr]);
var dist1Links = [];
async.waterfall([
function (next){
// Remove direct links (dist 1)
remainingKeys = _(remainingKeys).difference(newCertifiers);
next();
},
function (next) {
if (remainingKeys.length > 0) {
// Look for 1 distance links
Link.find({ target: fpr, obsolete: false }, function (err, links) {
dist1Links = [];
links.forEach(function(lnk){
dist1Links.push(lnk.source);
});
// Add new certifiers as distance 1 links
dist1Links = _(dist1Links.concat(newCertifiers)).uniq();
next(err);
});
}
else next();
},
function (next){
// Remove distance 2 links (those for whom new links make 1 distance)
var found = [];
if (remainingKeys.length > 0) {
async.forEachSeries(remainingKeys, function(member, callback){
// Exists distance 1 link?
async.detect(dist1Links, function (dist1member, callbackDist1) {
// Look in newLinks
var signatories = (newLinks[dist1member] || []);
if (~signatories.indexOf(member)) {
callbackDist1(true);
return;
}
// dist1member signed 'fpr', so here we look for (member => dist1member => fpr sigchain)
Link.find({ source: member, target: dist1member, obsolete: false }, function (err, links) {
if (links && links.length > 0) {
found.push(member);
callbackDist1(true);
}
else callbackDist1(false);
});
}, function (detected) {
if (detected)
found.push(member);
callback();
});
}, function(err){
remainingKeys = _(remainingKeys).difference(found);
next(err);
});
}
else next();
},
function (next){
// Remove distance 3 links (those for whom new links make 2 distance)
var found = [];
if (remainingKeys.length > 0) {
async.forEachSeries(remainingKeys, function(member, callback){
var dist2Links = [];
async.waterfall([
function (next){
// Step 1. Detect distance 1 members from current member (potential dist 2 from 'fpr')
// Look in database
Link.find({ source: member, obsolete: false }, function (err, links) {
dist2Links = [];
links.forEach(function(lnk){
dist2Links.push(lnk.source);
});
next(err);
});
// Look in newLinks
_(newLinks).keys().forEach(function(signed){
(newLinks[signed] || []).forEach(function(signatories){
if (~signatories.indexOf(member)) {
dist2Links.push(signed);
}
});
});
},
function (next){
// Step 2. Detect links between distance 2 & distance 1 members
async.detect(dist2Links, function (dist2member, callbackDist2) {
// Exists distance 1 link?
async.detect(dist1Links, function (dist1member, callbackDist1) {
// Look in newLinks
var signatories = (newLinks[dist1member] || []);
if (~signatories.indexOf(dist2member)) {
callbackDist1(true);
return;
}
// dist1member signed 'fpr', so here we look for (member => dist1member => fpr sigchain)
Link.find({ source: dist2member, target: dist1member, obsolete: false }, function (err, links) {
if (links && links.length > 0) {
callbackDist1(true);
}
else callbackDist1(false);
});
}, callbackDist2);
}, function (detected) {
if (detected)
found.push(member);
callback();
});
},
], callback);
}, function(err){
remainingKeys = _(remainingKeys).difference(found);
next(err);
});
}
else next();
},
], function (err) {
done(err, remainingKeys);
});
}
/**
* Mark as obsolete the links with an age equal to or below a given date
**/
LinkSchema.statics.isOver3StepsOfAMember = function (key, members, done) {
var fpr = key.pubkey;
var remainingKeys = [];
members.forEach(function(m){
remainingKeys.push(m.pubkey);
});
// Without self
remainingKeys = _(remainingKeys).difference([fpr]);
var Link = this.model('Link');
var dist1Links = [];
async.waterfall([
function (next){
// Step 1 links: low cost
var found = [];
if (remainingKeys.length > 0) {
async.forEachSeries(remainingKeys, function(member, callback){
// Exists direct link?
Link.find({ source: member, target: fpr, obsolete: false }, function (err, links) {
if (links && links.length > 0) {
found.push(member);
}
callback();
});
}, function(err){
remainingKeys = _(remainingKeys).difference(found);
next(err);
});
}
else next();
},
function (next) {
if (remainingKeys.length > 0) {
// Look for 1 distance links
Link.find({ target: fpr, obsolete: false }, function (err, links) {
dist1Links = [];
links.forEach(function(lnk){
dist1Links.push(lnk.source);
});
next(err);
});
}
else next();
},
function (next){
// Step 2 links: medium cost
var found = [];
if (remainingKeys.length > 0) {
async.forEachSeries(remainingKeys, function(member, callback){
// Exists distance 1 link?
async.detect(dist1Links, function (dist1member, callbackDist1) {
// dist1member signed 'fpr', so here we look for (member => dist1member => fpr sigchain)
Link.find({ source: member, target: dist1member, obsolete: false }, function (err, links) {
if (links && links.length > 0) {
found.push(member);
callbackDist1(true);
}
else callbackDist1(false);
});
}, function (detected) {
if (detected)
found.push(member);
callback();
});
}, function(err){
remainingKeys = _(remainingKeys).difference(found);
next(err);
});
}
else next();
},
function (next){
// Step 3 links: high cost
var found = [];
if (remainingKeys.length > 0) {
async.forEachSeries(remainingKeys, function(member, callback){
var dist2Links = [];
async.waterfall([
function (next){
// Step 1. Detect distance 1 members from current member (potential dist 2 from 'fpr')
Link.find({ source: member, obsolete: false }, function (err, links) {
dist2Links = [];
links.forEach(function(lnk){
dist2Links.push(lnk.target);
});
next(err);
});
},
function (next){
// Step 2. Detect links between distance 2 & distance 1 members
async.detect(dist2Links, function (dist2member, callbackDist2) {
// Exists distance 1 link?
async.detect(dist1Links, function (dist1member, callbackDist1) {
// dist1member signed 'fpr', so here we look for (member => dist1member => fpr sigchain)
Link.find({ source: dist2member, target: dist1member, obsolete: false }, function (err, links) {
if (links && links.length > 0) {
callbackDist1(true);
}
else callbackDist1(false);
});
}, callbackDist2);
}, function (detected) {
if (detected)
found.push(member);
callback();
});
},
], callback);
}, function(err){
remainingKeys = _(remainingKeys).difference(found);
next(err);
});
}
else next();
},
], function (err) {
done(err, remainingKeys);
});
}
module.exports = LinkSchema;
var async = require('async');
var _ = require('underscore');
var merkle = require('merkle');
var sha1 = require('sha1');
var crypto = require('../lib/crypto');
var base64 = require('../lib/base64');
var dos2unix = require('../lib/dos2unix');
var parsers = require('../lib/streams/parsers/doc');
var logger = require('../lib/logger')('blockchain');
var signature = require('../lib/signature');
var constants = require('../lib/constants');
var validator = require('../lib/localValidator');
var moment = require('moment');
var inquirer = require('inquirer');
var async = require('async');
var _ = require('underscore');
var merkle = require('merkle');
var sha1 = require('sha1');
var moment = require('moment');
var inquirer = require('inquirer');
var crypto = require('../lib/crypto');
var base64 = require('../lib/base64');
var dos2unix = require('../lib/dos2unix');
var parsers = require('../lib/streams/parsers/doc');
var logger = require('../lib/logger')('blockchain');
var signature = require('../lib/signature');
var constants = require('../lib/constants');
var localValidator = require('../lib/localValidator');
var globalValidator = require('../lib/globalValidator');
module.exports.get = function (conn, conf, IdentityService, PeeringService) {
return new BlockchainService(conn, conf, IdentityService, PeeringService);
......@@ -98,43 +99,23 @@ function BlockchainService (conn, conf, IdentityService, PeeringService) {
var newLinks;
async.waterfall([
function (next){
validator().validate(block, next);
localValidator().validate(block, next);
},
function (validated, next){
validator().checkSignatures(block, next);
localValidator().checkSignatures(block, next);
},
function (validated, next){
globalValidator(conf, new BlockCheckerDao(block)).validate(block, next);
},
function (next){
globalValidator(conf, new BlockCheckerDao(block)).checkSignatures(block, next);
},
function (next){
Block.current(function (err, obj) {
next(null, obj || null);
})
},
function (current, next){
// Testing chaining
if (!current && block.number > 0) {
next('Requires root block first');
return;
}
if (current && block.number <= current.number) {
next('Too late for this block');
return;
}
if (current && block.number > current.number + 1) {
next('Too early for this block');
return;
}
if (current && block.number == current.number + 1 && block.previousHash != current.hash) {
next('PreviousHash does not target current block');
return;
}
if (current && block.number == current.number + 1 && block.previousIssuer != current.issuer) {
next('PreviousIssuer does not target current block');
return;
}
// Test timestamp
if (KeychainService.checkWithLocalTimestamp && Math.abs(block.timestamp - now.utcZero().timestamp()) > conf.tsInterval) {
next('Timestamp does not match this node\'s time');
return;
}
// Check the challenge depending on issuer
checkProofOfWork(current, block, next);
},
......@@ -143,11 +124,6 @@ function BlockchainService (conn, conf, IdentityService, PeeringService) {
checkIssuer(block, next);
},
function (next) {
// Check document's coherence
checkCoherence(block, next);
},
function (theNewLinks, next) {
newLinks = theNewLinks;
// If computation is started, stop it and wait for stop event
var isComputeProcessWaiting = computeNextCallback ? true : false;
if (computationActivated && !isComputeProcessWaiting) {
......@@ -160,7 +136,7 @@ function BlockchainService (conn, conf, IdentityService, PeeringService) {
function (next) {
newKeyblockCallback = null;
// Save block data + compute links obsolescence
saveBlockData(block, newLinks, next);
saveBlockData(block, next);
},
function (block, next) {
// If PoW computation process is waiting, trigger it
......@@ -207,73 +183,6 @@ function BlockchainService (conn, conf, IdentityService, PeeringService) {
return found;
}
function checkCoherence (block, done) {
var newLinks = {};
async.waterfall([
function (next){
// Check key changes
checkKeychanges(block, next);
},
function (theNewLinks, next) {
newLinks = theNewLinks;
_(newLinks).keys().forEach(function(target){
newLinks[target].forEach(function(source){
logger.debug('Sig %s --> %s', source.substring(0, 8), target.substring(0, 8));
});
});
// Check that new links won't kick other members (existing or incoming)
checkWoTStability(block, newLinks, next);
},
function (next) {
// Check that to be kicked members are kicked
checkKicked(block, newLinks, next);
},
function (next){
// Check members' changes (+ and -), root & count
checkCommunityChanges(block, next);
},
], function (err) {
done(err, newLinks);
});
}
function checkKeychanges(block, done) {
var newLinks = {};
var newKeys = {};
async.waterfall([
function (next){
// Memorize newKeys
async.forEach(block.identities, function(inlineIdty, callback){
var idty = Identity.fromInline(inlineIdty);
newKeys[idty.pubkey] = idty;
callback();
}, next);
},
function (next){
async.forEachSeries(block.certifications, function(inlineCert, callback) {
// Build cert
var cert = Certification.fromInline(inlineCert);
async.waterfall([
function (next){
// Check validty (signature, and that certified & certifier are members (old or newcomers))
checkCertificationOfKey(cert.from, cert.time.timestamp(), cert.sig, cert.to, newKeys, next);
},
function (next){
// Memorize new links from signatures
newLinks[cert.to] = newLinks[cert.to] || [];
newLinks[cert.to].push(cert.from);
next();
},
], callback);
}, function (err) {
next(err, newLinks);
});
},
], function (err, newLinks) {
done(err, newLinks);
});
}
function checkWoTStability (block, newLinks, done) {
if (block.number >= 0) {
// other blocks may introduce unstability with new members
......@@ -286,7 +195,7 @@ function BlockchainService (conn, conf, IdentityService, PeeringService) {
block.joiners.forEach(function (inlineMS) {
var fpr = inlineMS.split(':')[0];
newcomers.push(fpr);
members.push({ pubkey: fpr });
members.push(fpr);
});
async.forEachSeries(newcomers, function (newcomer, newcomerTested) {
async.waterfall([
......@@ -299,43 +208,14 @@ function BlockchainService (conn, conf, IdentityService, PeeringService) {
function (next) {
// Check the newcomer IS RECOGNIZED BY the WoT + other newcomers
// (check we have a path WoT => newcomer)
Link.isOver3StepsOfAMember(newcomer, members, next);
},
function (firstCheck, next) {
if (firstCheck.length > 0) {
// This means either:
// 1. WoT does not recognize the newcomer
// 2. Other newcomers do not recognize the newcomer since we haven't taken them into account
// So, we have to test with newcomers' links too
async.waterfall([
function (next) {
Link.isStillOver3Steps(newcomer, firstCheck, newLinks, next);
},
function (secondCheck) {
if (secondCheck.length > 0)
next('Newcomer ' + newcomer + ' is not recognized by the WoT for this block');
else
next();
}
], next);
} else next();
globalValidator(conf, new BlockCheckerDao(block)).isOver3Hops(newcomer, members, newLinks, next);
},
function (next) {
// Also check that the newcomer RECOGNIZES the WoT + other newcomers
// (check we have a path newcomer => WoT)
async.forEachSeries(members, function (member, memberRecognized) {
async.waterfall([
function (next) {
Link.isStillOver3Steps(member.pubkey, [newcomer], newLinks, next);
},
function (distances, next) {
if (distances.length > 0)
next('Newcomer ' + newcomer + ' cannot recognize member ' + member.pubkey + ': no path found or too much distance');
else
next();
}
], memberRecognized);
}, next);
function (outdistanced, next) {
if (outdistanced.length > 0) {
console.log(members, newLinks);
next('Newcomer ' + newcomer + ' is not recognized by the WoT for this block');
}
else next();
}
], newcomerTested);
}, next);
......@@ -407,96 +287,6 @@ function BlockchainService (conn, conf, IdentityService, PeeringService) {
], done);
}
function checkKicked (block, newLinks, done) {
var membersChanges = block.membersChanges;
async.waterfall([
function (next){
Identity.getToBeKicked(next);
},
function (keys, next){
async.forEach(keys, function(key, callback){
async.waterfall([
function (next){
var remainingKeys = [];
key.distanced.forEach(function(m){
remainingKeys.push(m);
});
async.parallel({