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

[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 () {
});
});
};
};
}
"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() {
return this.dal.getCurrentBlockOrNull()
}
submitMembership(ms:any) {
return GlobalFifoPromise.pushFIFO(async () => {
const entry = new Membership(ms);
// Force usage of local currency name, do not accept other currencies documents
entry.currency = this.conf.currency || entry.currency;
entry.idtyHash = (hashf(entry.userid + entry.certts + entry.issuer) + "").toUpperCase();
this.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 = await this.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 = await this.dal.isMember(entry.issuer);
const isJoin = entry.membership == 'IN';
if (!isMember && !isJoin) {
// LEAVE
throw constants.ERRORS.MEMBERSHIP_A_NON_MEMBER_CANNOT_LEAVE;
}
const current = await this.dal.getCurrentBlockOrNull();
const basedBlock = await rules.HELPERS.checkMembershipBlock(entry, current, this.conf, this.dal);
if (basedBlock) {
entry.expires_on = basedBlock.medianTime + this.conf.msWindow;
}
entry.pubkey = entry.issuer;
if (!(await this.dal.msDAL.sandbox.acceptNewSandBoxEntry(entry, this.conf.pair && this.conf.pair.pub))) {
throw constants.ERRORS.SANDBOX_FOR_MEMERSHIP_IS_FULL;
}
// Saves entry
await this.dal.savePendingMembership(entry);
this.logger.info('✔ %s %s', entry.issuer, entry.membership);
return entry;
})
}
}
"use strict";
const co = require('co');
const Q = require('q');
const constants = require('../lib/constants');
const rules = require('../lib/rules')
const Transaction = require('../lib/entity/transaction');
const AbstractService = require('./AbstractService');
const TransactionDTO = require('../lib/dto/TransactionDTO').TransactionDTO
module.exports = () => {
return new TransactionService();
};
function TransactionService () {
AbstractService.call(this);
const CHECK_PENDING_TRANSACTIONS = true;
let conf, dal, logger;
this.setConfDAL = (newConf, newDAL) => {
dal = newDAL;
conf = newConf;
logger = require('../lib/logger')(dal.profile);
};
this.processTx = (txObj) => this.pushFIFO(() => co(function *() {
const tx = new Transaction(txObj, conf.currency);
try {
logger.info('⬇ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers);
const existing = yield dal.getTxByHash(tx.hash);
const current = yield dal.getCurrentBlockOrNull();
if (existing) {
throw constants.ERRORS.TX_ALREADY_PROCESSED;
}
// Start checks...
const transaction = tx.getTransaction();
const nextBlockWithFakeTimeVariation = { medianTime: current.medianTime + 1 };
const dto = TransactionDTO.fromJSONObject(tx)
yield Q.nbind(rules.HELPERS.checkSingleTransactionLocally, rules.HELPERS)(dto);
yield rules.HELPERS.checkTxBlockStamp(transaction, dal);
yield rules.HELPERS.checkSingleTransaction(dto, nextBlockWithFakeTimeVariation, conf, dal, CHECK_PENDING_TRANSACTIONS);
const server_pubkey = conf.pair && conf.pair.pub;
transaction.pubkey = transaction.issuers.indexOf(server_pubkey) !== -1 ? server_pubkey : '';
if (!(yield dal.txsDAL.sandbox.acceptNewSandBoxEntry(transaction, server_pubkey))) {
throw constants.ERRORS.SANDBOX_FOR_TRANSACTION_IS_FULL;
}
tx.blockstampTime = transaction.blockstampTime;
yield dal.saveTransaction(tx);
logger.info('✔ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers);
return tx;
} catch (e) {
logger.info('✘ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers);
throw e;
}
}));
}
"use strict";
import {GlobalFifoPromise} from "./GlobalFifoPromise"
import {ConfDTO} from "../lib/dto/ConfDTO"
import {FileDAL} from "../lib/dal/fileDAL"
import {TransactionDTO} from "../lib/dto/TransactionDTO"
const co = require('co');
const Q = require('q');
const constants = require('../lib/constants');
const rules = require('../lib/rules')
const Transaction = require('../lib/entity/transaction');
const CHECK_PENDING_TRANSACTIONS = true
export class TransactionService {
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);
}
processTx(txObj:any) {
return GlobalFifoPromise.pushFIFO(async () => {
const tx = new Transaction(txObj, this.conf.currency);
try {
this.logger.info('⬇ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers);
const existing = await this.dal.getTxByHash(tx.hash);
const current = await this.dal.getCurrentBlockOrNull();
if (existing) {
throw constants.ERRORS.TX_ALREADY_PROCESSED;
}
// Start checks...
const transaction = tx.getTransaction();
const nextBlockWithFakeTimeVariation = { medianTime: current.medianTime + 1 };
const dto = TransactionDTO.fromJSONObject(tx)
await Q.nbind(rules.HELPERS.checkSingleTransactionLocally, rules.HELPERS)(dto);
await rules.HELPERS.checkTxBlockStamp(transaction, this.dal);
await rules.HELPERS.checkSingleTransaction(dto, nextBlockWithFakeTimeVariation, this.conf, this.dal, CHECK_PENDING_TRANSACTIONS);
const server_pubkey = this.conf.pair && this.conf.pair.pub;
transaction.pubkey = transaction.issuers.indexOf(server_pubkey) !== -1 ? server_pubkey : '';
if (!(await this.dal.txsDAL.sandbox.acceptNewSandBoxEntry(transaction, server_pubkey))) {
throw constants.ERRORS.SANDBOX_FOR_TRANSACTION_IS_FULL;
}
tx.blockstampTime = transaction.blockstampTime;
await this.dal.saveTransaction(tx);
this.logger.info('✔ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers);
return tx;
} catch (e) {
this.logger.info('✘ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers);
throw e;
}
})
}
}
......@@ -33,21 +33,21 @@ function Server (home, memoryOnly, overrideConf) {
that.logger = logger;
that.MerkleService = require("./app/lib/helpers/merkle");
that.IdentityService = require('./app/service/IdentityService')();
that.MembershipService = require('./app/service/MembershipService')();
that.PeeringService = require('./app/service/PeeringService')(that);
that.BlockchainService = require('./app/service/BlockchainService')(that);
that.TransactionsService = require('./app/service/TransactionsService')();
that.IdentityService = new (require('./app/service/IdentityService').IdentityService)()
that.MembershipService = new (require('./app/service/MembershipService').MembershipService)()
that.PeeringService = new (require('./app/service/PeeringService').PeeringService)(that)
that.BlockchainService = new (require('./app/service/BlockchainService').BlockchainService)(that)
that.TransactionsService = new (require('./app/service/TransactionsService').TransactionService)()
// Create document mapping
const documentsMapping = {
'identity': { action: that.IdentityService.submitIdentity, parser: parsers.parseIdentity },
'certification': { action: that.IdentityService.submitCertification, parser: parsers.parseCertification},
'revocation': { action: that.IdentityService.submitRevocation, parser: parsers.parseRevocation },
'membership': { action: that.MembershipService.submitMembership, parser: parsers.parseMembership },
'peer': { action: that.PeeringService.submitP, parser: parsers.parsePeer },
'transaction': { action: that.TransactionsService.processTx, parser: parsers.parseTransaction },
'block': { action: _.partial(that.BlockchainService.submitBlock, _, true, constants.NO_FORK_ALLOWED), parser: parsers.parseBlock }
'identity': { action: (obj) => that.IdentityService.submitIdentity(obj), parser: parsers.parseIdentity },
'certification': { action: (obj) => that.IdentityService.submitCertification(obj), parser: parsers.parseCertification},
'revocation': { action: (obj) => that.IdentityService.submitRevocation(obj), parser: parsers.parseRevocation },
'membership': { action: (obj) => that.MembershipService.submitMembership(obj), parser: parsers.parseMembership },
'peer': { action: (obj) => that.PeeringService.submitP(obj), parser: parsers.parsePeer },
'transaction': { action: (obj) => that.TransactionsService.processTx(obj), parser: parsers.parseTransaction },
'block': { action: (obj) => that.BlockchainService.submitBlock(obj, true, constants.NO_FORK_ALLOWED), parser: parsers.parseBlock }
};
// Unused, but made mandatory by Duplex interface
......
const lint = require('mocha-eslint');
describe('Linting', () => {
// Array of paths to lint
// Note: a seperate Mocha test will be run for each path and each file which
// matches a glob pattern
const paths = [
'app',
'bin/duniter',
'test'
];
const lint = require('mocha-eslint');
// Specify style of output
const options = {};
options.formatter = 'stylish';
// Array of paths to lint
// Note: a seperate Mocha test will be run for each path and each file which
// matches a glob pattern
const paths = [
'app',
'bin/duniter',
'test'
];
// Run the tests
lint(paths, options);
// Specify style of output
const options = {};
options.formatter = 'stylish';
// Run the tests
lint(paths, options);
})
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment