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

[enh] #1037 Migrate Services

parent a9875021
......@@ -10,5 +10,6 @@ app/lib/dal/sqliteDAL/*.js
app/lib/dal/sqliteDAL/index/*.js
app/lib/dal/fileDALs/*.js
app/lib/dal/fileDAL.js
app/service/*.js
test/*.js
test/**/*.js
\ No newline at end of file
......@@ -47,4 +47,5 @@ app/lib/dal/sqliteDAL/*.js*
app/lib/dal/sqliteDAL/index/*.js*
app/lib/dal/fileDALs/*.js*
app/lib/dal/fileDAL.js*
app/service/*.js*
app/lib/wot.js*
\ No newline at end of file
......@@ -161,7 +161,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain {
return { index, HEAD }
}
async pushTheBlock(obj:BlockDTO, index:IndexEntry[], HEAD:DBHead, conf:ConfDTO, dal:any, logger:any) {
async pushTheBlock(obj:BlockDTO, index:IndexEntry[], HEAD:DBHead | null, conf:ConfDTO, dal:any, logger:any) {
const start = Date.now();
const block = new Block(obj);
try {
......@@ -188,7 +188,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain {
// await supra.recordIndex(index)
}
async saveBlockData(current:DBBlock, block:BlockDTO, conf:ConfDTO, dal:any, logger:any, index:IndexEntry[], HEAD:DBHead) {
async saveBlockData(current:DBBlock, block:BlockDTO, conf:ConfDTO, dal:any, logger:any, index:IndexEntry[], HEAD:DBHead | null) {
if (block.number == 0) {
await this.saveParametersForRoot(block, conf, dal);
}
......
......@@ -105,7 +105,7 @@ export class BlockchainContext {
return this.blockchain.checkBlock(block, withPoWAndSignature, this.conf, this.dal)
}
async addBlock(obj: BlockDTO, index: any, HEAD: DBHead): Promise<any> {
async addBlock(obj: BlockDTO, index: any = null, HEAD: DBHead | null = null): Promise<any> {
const block = await this.blockchain.pushTheBlock(obj, index, HEAD, this.conf, this.dal, this.logger)
this.vHEAD_1 = this.vHEAD = this.HEADrefreshed = null
return block
......
......@@ -361,7 +361,11 @@ export class FileDAL {
const nonPendings = _.filter(writtens, (w:IindexEntry) => {
return _.where(pendings, { pubkey: w.pub }).length == 0;
});
const found = pendings.concat(nonPendings);
const found = pendings.concat(nonPendings.map((i:any) => {
// Use the correct field
i.pubkey = i.pub
return i
}));
return await Promise.all(found.map(async (f:any) => {
const ms = await this.mindexDAL.getReducedMS(f.pub);
if (ms) {
......
......@@ -19,8 +19,12 @@ export interface DBIdentity {
hash: string
written: boolean
wotb_id: number | null
expires_on: number,
certsCount: number,
revoked_on: number | null
expires_on: number
}
export interface DBSandboxIdentity extends DBIdentity {
certsCount: number
ref_block: number
}
......@@ -164,7 +168,7 @@ export class IdentityDAL extends AbstractSQLite<DBIdentity> {
return this.query('SELECT * FROM sandbox_idty LIMIT ' + (this.sandbox.maxSize), [])
}
sandbox = new SandBox(constants.SANDBOX_SIZE_IDENTITIES, this.getSandboxIdentities.bind(this), (compared:DBIdentity, reference:DBIdentity) => {
sandbox = new SandBox(constants.SANDBOX_SIZE_IDENTITIES, this.getSandboxIdentities.bind(this), (compared:DBSandboxIdentity, reference:DBSandboxIdentity) => {
if (compared.certsCount < reference.certsCount) {
return -1;
}
......
export interface Keypair {
pub: string
sec: string
}
export class ConfDTO {
constructor(
......@@ -31,9 +36,15 @@ export class ConfDTO {
public idtyWindow: number,
public msWindow: number,
public sigWindow: number,
public swichOnTimeAheadBy: number,
public pair: Keypair | null,
public remoteport: number,
public remotehost: string,
public remoteipv4: string,
public remoteipv6: string,
) {}
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)
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, "", "", "")
}
}
\ No newline at end of file
......@@ -3,27 +3,26 @@ const async = require('async');
const Q = require('q');
const co = require('co');
const fifo = async.queue(function (task, callback) {
const fifo = async.queue(function (task:any, callback:any) {
task(callback);
}, 1);
module.exports = function AbstractService () {
export class GlobalFifoPromise {
/**
* Gets the queue object for advanced flow control.
*/
this.getFIFO = () => fifo;
static getLen() {
return fifo.length()
}
/**
* Adds a promise to a FIFO stack of promises, so the given promise will be executed against a shared FIFO stack.
* @param p
* @returns {Q.Promise<T>} A promise wrapping the promise given in the parameter.
*/
this.pushFIFO = (p) => {
static pushFIFO(p: () => Promise<any>) {
// Return a promise that will be done after the fifo has executed the given promise
return Q.Promise((resolve, reject) => {
return Q.Promise((resolve:any, reject:any) => {
// Push the promise on the stack
fifo.push(function (cb) {
fifo.push(function (cb:any) {
co(function*(){
// OK its the turn of given promise, execute it
try {
......@@ -35,7 +34,7 @@ module.exports = function AbstractService () {
cb(e);
}
});
}, (err, res) => {
}, (err:any, res:any) => {
// An error occured => reject promise
if (err) return reject(err);
// Success => we resolve with given promise result
......@@ -43,4 +42,4 @@ module.exports = function AbstractService () {
});
});
};
};
}
import {GlobalFifoPromise} from "./GlobalFifoPromise";
"use strict";
import {FileDAL} from "../lib/dal/fileDAL"
import {ConfDTO} from "../lib/dto/ConfDTO"
import {DBIdentity} from "../lib/dal/sqliteDAL/IdentityDAL"
const Q = require('q');
const rules = require('../lib/rules')
const keyring = require('duniter-common').keyring;
......@@ -7,152 +12,164 @@ const Block = require('../../app/lib/entity/block');
const Identity = require('../../app/lib/entity/identity');
const Certification = require('../../app/lib/entity/certification');
const Revocation = require('../../app/lib/entity/revocation');
const AbstractService = require('./AbstractService');
const co = require('co');
const BY_ABSORPTION = true;
module.exports = () => {
return new IdentityService();
};
function IdentityService () {
export class IdentityService {
AbstractService.call(this);
dal:FileDAL
conf:ConfDTO
logger:any
const that = this;
let dal, conf, logger;
constructor() {}
this.setConfDAL = (newConf, newDAL) => {
dal = newDAL;
conf = newConf;
logger = require('../lib/logger')(dal.profile);
};
setConfDAL(newConf:ConfDTO, newDAL:FileDAL) {
this.dal = newDAL;
this.conf = newConf;
this.logger = require('../lib/logger')(this.dal.profile);
}
this.searchIdentities = (search) => dal.searchJustIdentities(search);
searchIdentities(search:string) {
return this.dal.searchJustIdentities(search)
}
this.findMember = (search) => co(function *() {
async findMember(search:string) {
let idty = null;
if (search.match(constants.PUBLIC_KEY)) {
idty = yield dal.getWrittenIdtyByPubkey(search);
idty = await this.dal.getWrittenIdtyByPubkey(search);
}
else {
idty = yield dal.getWrittenIdtyByUID(search);
idty = await this.dal.getWrittenIdtyByUID(search);
}
if (!idty) {
throw constants.ERRORS.NO_MEMBER_MATCHING_PUB_OR_UID;
}
yield dal.fillInMembershipsOfIdentity(Q(idty));
await this.dal.fillInMembershipsOfIdentity(Q(idty));
return new Identity(idty);
});
}
this.findMemberWithoutMemberships = (search) => co(function *() {
async findMemberWithoutMemberships(search:string) {
let idty = null;
if (search.match(constants.PUBLIC_KEY)) {
idty = yield dal.getWrittenIdtyByPubkey(search);
idty = await this.dal.getWrittenIdtyByPubkey(search)
}
else {
idty = yield dal.getWrittenIdtyByUID(search);
idty = await this.dal.getWrittenIdtyByUID(search)
}
if (!idty) {
throw constants.ERRORS.NO_MEMBER_MATCHING_PUB_OR_UID;
}
return new Identity(idty);
});
}
this.getWrittenByPubkey = (pubkey) => dal.getWrittenIdtyByPubkey(pubkey);
getWrittenByPubkey(pubkey:string) {
return this.dal.getWrittenIdtyByPubkey(pubkey)
}
this.getPendingFromPubkey = (pubkey) => dal.getNonWritten(pubkey);
getPendingFromPubkey(pubkey:string) {
return this.dal.getNonWritten(pubkey)
}
this.submitIdentity = (obj, byAbsorption) => {
submitIdentity(obj:DBIdentity, byAbsorption = false) {
let idty = new Identity(obj);
// Force usage of local currency name, do not accept other currencies documents
idty.currency = conf.currency || idty.currency;
idty.currency = this.conf.currency;
const createIdentity = idty.rawWithoutSig();
return that.pushFIFO(() => co(function *() {
logger.info('⬇ IDTY %s %s', idty.pubkey, idty.uid);
return GlobalFifoPromise.pushFIFO(async () => {
this.logger.info('⬇ IDTY %s %s', idty.pubkey, idty.uid);
// Check signature's validity
let verified = keyring.verify(createIdentity, idty.sig, idty.pubkey);
if (!verified) {
throw constants.ERRORS.SIGNATURE_DOES_NOT_MATCH;
}
let existing = yield dal.getIdentityByHashOrNull(idty.hash);
let existing = await this.dal.getIdentityByHashOrNull(idty.hash);
if (existing) {
throw constants.ERRORS.ALREADY_UP_TO_DATE;
}
else if (!existing) {
// Create if not already written uid/pubkey
let used = yield dal.getWrittenIdtyByPubkey(idty.pubkey);
let used = await this.dal.getWrittenIdtyByPubkey(idty.pubkey);
if (used) {
throw constants.ERRORS.PUBKEY_ALREADY_USED;
}
used = yield dal.getWrittenIdtyByUID(idty.uid);
used = await this.dal.getWrittenIdtyByUID(idty.uid);
if (used) {
throw constants.ERRORS.UID_ALREADY_USED;
}
const current = yield dal.getCurrentBlockOrNull();
const current = await this.dal.getCurrentBlockOrNull();
if (idty.buid == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855' && current) {
throw constants.ERRORS.BLOCKSTAMP_DOES_NOT_MATCH_A_BLOCK;
} else if (current) {
let basedBlock = yield dal.getBlockByBlockstamp(idty.buid);
let basedBlock = await this.dal.getBlockByBlockstamp(idty.buid);
if (!basedBlock) {
throw constants.ERRORS.BLOCKSTAMP_DOES_NOT_MATCH_A_BLOCK;
}
idty.expires_on = basedBlock.medianTime + conf.idtyWindow;
idty.expires_on = basedBlock.medianTime + this.conf.idtyWindow;
}
yield rules.GLOBAL.checkIdentitiesAreWritable({ identities: [idty.inline()], version: (current && current.version) || constants.BLOCK_GENERATED_VERSION }, conf, dal);
await rules.GLOBAL.checkIdentitiesAreWritable({ identities: [idty.inline()], version: (current && current.version) || constants.BLOCK_GENERATED_VERSION }, this.conf, this.dal);
idty = new Identity(idty);
if (byAbsorption !== BY_ABSORPTION) {
idty.ref_block = parseInt(idty.buid.split('-')[0]);
if (!(yield dal.idtyDAL.sandbox.acceptNewSandBoxEntry(idty, conf.pair && conf.pair.pub))) {
if (!(await this.dal.idtyDAL.sandbox.acceptNewSandBoxEntry(idty, this.conf.pair && this.conf.pair.pub))) {
throw constants.ERRORS.SANDBOX_FOR_IDENTITY_IS_FULL;
}
}
yield dal.savePendingIdentity(idty);
logger.info('✔ IDTY %s %s', idty.pubkey, idty.uid);
await this.dal.savePendingIdentity(idty);
this.logger.info('✔ IDTY %s %s', idty.pubkey, idty.uid);
return idty;
}
}));
};
})
}
this.submitCertification = (obj) => co(function *() {
const current = yield dal.getCurrentBlockOrNull();
async submitCertification(obj:any) {
const current = await this.dal.getCurrentBlockOrNull();
// Prepare validator for certifications
const potentialNext = new Block({ currency: conf.currency, identities: [], number: current ? current.number + 1 : 0 });
const potentialNext = new Block({ currency: this.conf.currency, identities: [], number: current ? current.number + 1 : 0 });
// Force usage of local currency name, do not accept other currencies documents
obj.currency = conf.currency || obj.currency;
obj.currency = this.conf.currency || obj.currency;
const cert = Certification.statics.fromJSON(obj);
const targetHash = cert.getTargetHash();
let idty = yield dal.getIdentityByHashOrNull(targetHash);
let idty = await this.dal.getIdentityByHashOrNull(targetHash);
let idtyAbsorbed = false
if (!idty) {
idtyAbsorbed = true
idty = yield that.submitIdentity({
currency: cert.currency,
issuer: cert.idty_issuer,
idty = await this.submitIdentity({
pubkey: cert.idty_issuer,
uid: cert.idty_uid,
buid: cert.idty_buid,
sig: cert.idty_sig
sig: cert.idty_sig,
written: false,
revoked: false,
member: false,
wasMember: false,
kick: false,
leaving: false,
hash: '',
wotb_id: null,
expires_on: 0,
revoked_on: null,
revocation_sig: null,
currentMSN: null,
currentINN: null
}, BY_ABSORPTION);
}
return that.pushFIFO(() => co(function *() {
logger.info('⬇ CERT %s block#%s -> %s', cert.from, cert.block_number, idty.uid);
return GlobalFifoPromise.pushFIFO(async () => {
this.logger.info('⬇ CERT %s block#%s -> %s', cert.from, cert.block_number, idty.uid);
try {
yield rules.HELPERS.checkCertificationIsValid(cert, potentialNext, () => Q(idty), conf, dal);
await rules.HELPERS.checkCertificationIsValid(cert, potentialNext, () => Q(idty), this.conf, this.dal);
} catch (e) {
cert.err = e;
}
if (!cert.err) {
try {
let basedBlock = yield dal.getBlock(cert.block_number);
let basedBlock = await this.dal.getBlock(cert.block_number);
if (cert.block_number == 0 && !basedBlock) {
basedBlock = {
number: 0,
hash: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855'
};
} else {
cert.expires_on = basedBlock.medianTime + conf.sigWindow;
cert.expires_on = basedBlock.medianTime + this.conf.sigWindow;
}
cert.block_hash = basedBlock.hash;
const mCert = new Certification({
......@@ -164,13 +181,13 @@ function IdentityService () {
to: idty.pubkey,
expires_on: cert.expires_on
});
let existingCert = yield dal.existsCert(mCert);
let existingCert = await this.dal.existsCert(mCert);
if (!existingCert) {
if (!(yield dal.certDAL.getSandboxForKey(cert.from).acceptNewSandBoxEntry(mCert, conf.pair && conf.pair.pub))) {
if (!(await this.dal.certDAL.getSandboxForKey(cert.from).acceptNewSandBoxEntry(mCert, this.conf.pair && this.conf.pair.pub))) {
throw constants.ERRORS.SANDBOX_FOR_CERT_IS_FULL;
}
yield dal.registerNewCertification(new Certification(mCert));
logger.info('✔ CERT %s', mCert.from);
await this.dal.registerNewCertification(new Certification(mCert));
this.logger.info('✔ CERT %s', mCert.from);
} else {
throw constants.ERRORS.ALREADY_UP_TO_DATE;
}
......@@ -180,30 +197,30 @@ function IdentityService () {
}
if (cert.err) {
if (idtyAbsorbed) {
yield dal.idtyDAL.deleteByHash(targetHash)
await this.dal.idtyDAL.deleteByHash(targetHash)
}
const err = cert.err
const errMessage = (err.uerr && err.uerr.message) || err.message || err
logger.info('✘ CERT %s %s', cert.from, errMessage);
this.logger.info('✘ CERT %s %s', cert.from, errMessage);
throw cert.err;
}
return cert;
}));
});
})
}
this.submitRevocation = (obj) => {
submitRevocation(obj:any) {
// Force usage of local currency name, do not accept other currencies documents
obj.currency = conf.currency || obj.currency;
obj.currency = this.conf.currency || obj.currency;
const revoc = new Revocation(obj);
const raw = revoc.rawWithoutSig();
return that.pushFIFO(() => co(function *() {
return GlobalFifoPromise.pushFIFO(async () => {
try {
logger.info('⬇ REVOCATION %s %s', revoc.pubkey, revoc.uid);
this.logger.info('⬇ REVOCATION %s %s', revoc.pubkey, revoc.uid);
let verified = keyring.verify(raw, revoc.revocation, revoc.pubkey);
if (!verified) {
throw 'Wrong signature for revocation';
}
const existing = yield dal.getIdentityByHashOrNull(obj.hash);
const existing = await this.dal.getIdentityByHashOrNull(obj.hash);
if (existing) {
// Modify
if (existing.revoked) {
......@@ -212,8 +229,8 @@ function IdentityService () {
else if (existing.revocation_sig) {
throw 'Revocation already registered';
} else {
yield dal.setRevocating(existing, revoc.revocation);
logger.info('✔ REVOCATION %s %s', revoc.pubkey, revoc.uid);
await this.dal.setRevocating(existing, revoc.revocation);
this.logger.info('✔ REVOCATION %s %s', revoc.pubkey, revoc.uid);
revoc.json = function() {
return {
result: true
......@@ -228,11 +245,11 @@ function IdentityService () {
idty.revocation_sig = revoc.signature;
idty.certsCount = 0;
idty.ref_block = parseInt(idty.buid.split('-')[0]);
if (!(yield dal.idtyDAL.sandbox.acceptNewSandBoxEntry(idty, conf.pair && conf.pair.pub))) {
if (!(await this.dal.idtyDAL.sandbox.acceptNewSandBoxEntry(idty, this.conf.pair && this.conf.pair.pub))) {
throw constants.ERRORS.SANDBOX_FOR_IDENTITY_IS_FULL;
}
yield dal.savePendingIdentity(idty);
logger.info('✔ REVOCATION %s %s', revoc.pubkey, revoc.uid);
await this.dal.savePendingIdentity(idty);
this.logger.info('✔ REVOCATION %s %s', revoc.pubkey, revoc.uid);
revoc.json = function() {
return {
result: true
......@@ -241,9 +258,9 @@ function IdentityService () {
return revoc;
}
} catch (e) {
logger.info('✘ REVOCATION %s %s', revoc.pubkey, revoc.uid);
this.logger.info('✘ REVOCATION %s %s', revoc.pubkey, revoc.uid);
throw e;
}
}));
};
})
}
}
"use strict";
const co = require('co');
const rules = require('../lib/rules')
const hashf = require('duniter-common').hashf;
const constants = require('../lib/constants');
const Membership = require('../lib/entity/membership');
const AbstractService = require('./AbstractService');
module.exports = () => {
return new MembershipService();
};
function MembershipService () {
AbstractService.call(this);
let conf, dal, logger;
this.setConfDAL = (newConf, newDAL) => {
dal = newDAL;
conf = newConf;
logger = require('../lib/logger')(dal.profile);
};
this.current = () => dal.getCurrentBlockOrNull();
this.submitMembership = (ms) => this.pushFIFO(() => co(function *() {
const entry = new Membership(ms);
// Force usage of local currency name, do not accept other currencies documents
entry.currency = conf.currency || entry.currency;
entry.idtyHash = (hashf(entry.userid + entry.certts + entry.issuer) + "").toUpperCase();
logger.info('⬇ %s %s', entry.issuer, entry.membership);
if (!rules.HELPERS.checkSingleMembershipSignature(entry)) {
throw constants.ERRORS.WRONG_SIGNATURE_MEMBERSHIP;
}
// Get already existing Membership with same parameters
const mostRecentNumber = yield dal.getMostRecentMembershipNumberForIssuer(entry.issuer);
const thisNumber = parseInt(entry.block);
if (mostRecentNumber == thisNumber) {
throw constants.ERRORS.ALREADY_RECEIVED_MEMBERSHIP;
} else if (mostRecentNumber > thisNumber) {
throw constants.ERRORS.A_MORE_RECENT_MEMBERSHIP_EXISTS;
}
const isMember = yield dal.isMember(entry.issuer);
const isJoin = entry.membership == 'IN';
if (!isMember && !isJoin) {
// LEAVE
throw constants.ERRORS.MEMBERSHIP_A_NON_MEMBER_CANNOT_LEAVE;
}
const current = yield dal.getCurrentBlockOrNull();
const basedBlock = yield rules.HELPERS.checkMembershipBlock(entry, current, conf, dal);
if (basedBlock) {
entry.expires_on = basedBlock.medianTime + conf.msWindow;
}
entry.pubkey = entry.issuer;
if (!(yield dal.msDAL.sandbox.acceptNewSandBoxEntry(entry, conf.pair && conf.pair.pub))) {
throw constants.ERRORS.SANDBOX_FOR_MEMERSHIP_IS_FULL;
}
// Saves entry
yield dal.savePendingMembership(entry);
logger.info('✔ %s %s', entry.issuer, entry.membership);
return entry;
}));
}
"use strict";
import {GlobalFifoPromise} from "./GlobalFifoPromise"
import {ConfDTO} from "../lib/dto/ConfDTO"
import {FileDAL} from "../lib/dal/fileDAL"
const rules = require('../lib/rules')
const hashf = require('duniter-common').hashf;
const constants = require('../lib/constants');
const Membership = require('../lib/entity/membership');
export class MembershipService {
conf:ConfDTO
dal:FileDAL
logger:any
setConfDAL(newConf:ConfDTO, newDAL:FileDAL) {
this.dal = newDAL;
this.conf = newConf;
this.logger = require('../lib/logger')(this.dal.profile);
}
current() {