From 0eecc2e54b8bd81cd6cbf9cd9015679cb065388c Mon Sep 17 00:00:00 2001 From: cgeek <cem.moreau@gmail.com> Date: Mon, 17 Jul 2017 15:03:12 +0200 Subject: [PATCH] [enh] #1037 Migrate Services --- .eslintignore | 1 + .gitignore | 1 + app/lib/blockchain/DuniterBlockchain.ts | 4 +- app/lib/computation/BlockchainContext.ts | 2 +- app/lib/dal/fileDAL.ts | 6 +- app/lib/dal/sqliteDAL/IdentityDAL.ts | 10 +- app/lib/dto/ConfDTO.ts | 13 +- app/service/BlockchainService.js | 395 ---------------- app/service/BlockchainService.ts | 428 ++++++++++++++++++ ...bstractService.js => GlobalFifoPromise.ts} | 21 +- ...{IdentityService.js => IdentityService.ts} | 173 +++---- app/service/MembershipService.js | 65 --- app/service/MembershipService.ts | 66 +++ .../{PeeringService.js => PeeringService.ts} | 152 ++++--- app/service/TransactionsService.js | 59 --- app/service/TransactionsService.ts | 58 +++ server.js | 24 +- test/eslint.js | 32 +- 18 files changed, 796 insertions(+), 714 deletions(-) delete mode 100644 app/service/BlockchainService.js create mode 100644 app/service/BlockchainService.ts rename app/service/{AbstractService.js => GlobalFifoPromise.ts} (75%) rename app/service/{IdentityService.js => IdentityService.ts} (53%) delete mode 100644 app/service/MembershipService.js create mode 100644 app/service/MembershipService.ts rename app/service/{PeeringService.js => PeeringService.ts} (68%) delete mode 100644 app/service/TransactionsService.js create mode 100644 app/service/TransactionsService.ts diff --git a/.eslintignore b/.eslintignore index f8cb13760..eddb5e16e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/.gitignore b/.gitignore index f386f2a39..f19d2281f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/app/lib/blockchain/DuniterBlockchain.ts b/app/lib/blockchain/DuniterBlockchain.ts index 2de2ea33e..b9615249d 100644 --- a/app/lib/blockchain/DuniterBlockchain.ts +++ b/app/lib/blockchain/DuniterBlockchain.ts @@ -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); } diff --git a/app/lib/computation/BlockchainContext.ts b/app/lib/computation/BlockchainContext.ts index 88ab213cc..2b267ece2 100644 --- a/app/lib/computation/BlockchainContext.ts +++ b/app/lib/computation/BlockchainContext.ts @@ -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 diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts index 3258a8a89..b2f5f0ca3 100644 --- a/app/lib/dal/fileDAL.ts +++ b/app/lib/dal/fileDAL.ts @@ -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) { diff --git a/app/lib/dal/sqliteDAL/IdentityDAL.ts b/app/lib/dal/sqliteDAL/IdentityDAL.ts index 173d737f2..1f1261d22 100644 --- a/app/lib/dal/sqliteDAL/IdentityDAL.ts +++ b/app/lib/dal/sqliteDAL/IdentityDAL.ts @@ -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; } diff --git a/app/lib/dto/ConfDTO.ts b/app/lib/dto/ConfDTO.ts index 6b135ca6a..7143a0634 100644 --- a/app/lib/dto/ConfDTO.ts +++ b/app/lib/dto/ConfDTO.ts @@ -1,3 +1,8 @@ +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 diff --git a/app/service/BlockchainService.js b/app/service/BlockchainService.js deleted file mode 100644 index 1e1ace7ab..000000000 --- a/app/service/BlockchainService.js +++ /dev/null @@ -1,395 +0,0 @@ -"use strict"; - -const _ = require('underscore'); -const co = require('co'); -const parsers = require('duniter-common').parsers; -const rules = require('../lib/rules') -const constants = require('../lib/constants'); -const blockchainCtx = require('../lib/computation/BlockchainContext').BlockchainContext -const Block = require('../lib/entity/block'); -const BlockDTO = require('../lib/dto/BlockDTO').BlockDTO -const Identity = require('../lib/entity/identity'); -const Transaction = require('../lib/entity/transaction'); -const AbstractService = require('./AbstractService'); -const QuickSynchronizer = require('../lib/computation/QuickSync').QuickSynchronizer - -const CHECK_ALL_RULES = true; - -module.exports = (server) => { - return new BlockchainService(server); -}; - -function BlockchainService (server) { - - AbstractService.call(this); - - let that = this; - const mainContext = new blockchainCtx(); - let conf, dal, logger, selfPubkey, quickSynchronizer - - this.getContext = () => mainContext; - - this.setConfDAL = (newConf, newDAL, newKeyPair) => { - dal = newDAL; - conf = newConf; - logger = require('../lib/logger')(dal.profile) - quickSynchronizer = new QuickSynchronizer(server.blockchain, conf, dal, logger) - mainContext.setConfDAL(conf, dal, server.blockchain, quickSynchronizer) - selfPubkey = newKeyPair.publicKey; - }; - - this.current = () => dal.getCurrentBlockOrNull(); - - this.promoted = (number) => co(function *() { - const bb = yield dal.getPromoted(number); - if (!bb) throw constants.ERRORS.BLOCK_NOT_FOUND; - return bb; - }); - - this.checkBlock = function(block) { - const dto = BlockDTO.fromJSONObject(block) - return mainContext.checkBlock(dto); - }; - - this.branches = () => co(function *() { - let forkBlocks = yield dal.blockDAL.getForkBlocks(); - forkBlocks = _.sortBy(forkBlocks, 'number'); - // Get the blocks refering current blockchain - const forkables = []; - for (const block of forkBlocks) { - const refered = yield dal.getBlockByNumberAndHashOrNull(block.number - 1, block.previousHash); - if (refered) { - forkables.push(block); - } - } - const branches = getBranches(forkables, _.difference(forkBlocks, forkables)); - const current = yield mainContext.current(); - const forks = branches.map((branch) => branch[branch.length - 1]); - return forks.concat([current]); - }); - - function getBranches(forkables, others) { - // All starting branches - let branches = forkables.map((fork) => [fork]); - // For each "pending" block, we try to add it to all branches - for (const other of others) { - for (let j = 0, len2 = branches.length; j < len2; j++) { - const branch = branches[j]; - const last = branch[branch.length - 1]; - if (other.number == last.number + 1 && other.previousHash == last.hash) { - branch.push(other); - } else if (branch[1]) { - // We try to find out if another fork block can be forked - const diff = other.number - branch[0].number; - if (diff > 0 && branch[diff - 1] && branch[diff - 1].hash == other.previousHash) { - // We duplicate the branch, and we add the block to this second branch - branches.push(branch.slice()); - // First we remove the blocks that are not part of the fork - branch.splice(diff, branch.length - diff); - branch.push(other); - j++; - } - } - } - } - branches = _.sortBy(branches, (branch) => -branch.length); - if (branches.length) { - const maxSize = branches[0].length; - const longestsBranches = []; - for (const branch of branches) { - if (branch.length == maxSize) { - longestsBranches.push(branch); - } - } - return longestsBranches; - } - return []; - } - - this.submitBlock = (obj, doCheck, forkAllowed) => this.pushFIFO(() => checkAndAddBlock(obj, doCheck, forkAllowed)); - - const checkAndAddBlock = (blockToAdd, doCheck, forkAllowed) => co(function *() { - // Check global format, notably version number - const obj = parsers.parseBlock.syncWrite(Block.statics.fromJSON(blockToAdd).getRawSigned()); - // Force usage of local currency name, do not accept other currencies documents - if (conf.currency) { - obj.currency = conf.currency || obj.currency; - } else { - conf.currency = obj.currency; - } - try { - Transaction.statics.cleanSignatories(obj.transactions); - } - catch (e) { - throw e; - } - let existing = yield dal.getBlockByNumberAndHashOrNull(obj.number, obj.hash); - if (existing) { - throw constants.ERRORS.BLOCK_ALREADY_PROCESSED; - } - let current = yield mainContext.current(); - let followsCurrent = !current || (obj.number == current.number + 1 && obj.previousHash == current.hash); - if (followsCurrent) { - // try to add it on main blockchain - const dto = BlockDTO.fromJSONObject(obj) - if (doCheck) { - const { index, HEAD } = yield mainContext.checkBlock(dto, constants.WITH_SIGNATURES_AND_POW); - return yield mainContext.addBlock(dto, index, HEAD) - } else { - return yield mainContext.addBlock(dto) - } - } else if (forkAllowed) { - // add it as side chain - if (current.number - obj.number + 1 >= conf.forksize) { - throw 'Block out of fork window'; - } - let absolute = yield dal.getAbsoluteBlockByNumberAndHash(obj.number, obj.hash); - let res = null; - if (!absolute) { - res = yield mainContext.addSideBlock(obj, doCheck); - } - yield that.tryToFork(current); - return res; - } else { - throw "Fork block rejected by " + selfPubkey; - } - }); - - - that.tryToFork = (current) => eventuallySwitchOnSideChain(current); - - const eventuallySwitchOnSideChain = (current) => co(function *() { - const branches = yield that.branches(); - const blocksAdvance = conf.swichOnTimeAheadBy / (conf.avgGenTime / 60); - const timeAdvance = conf.swichOnTimeAheadBy * 60; - let potentials = _.without(branches, current); - // We switch only to blockchain with X_MIN advance considering both theoretical time by block + written time - potentials = _.filter(potentials, (p) => p.number - current.number >= blocksAdvance - && p.medianTime - current.medianTime >= timeAdvance); - logger.trace('SWITCH: %s branches...', branches.length); - logger.trace('SWITCH: %s potential side chains...', potentials.length); - for (const potential of potentials) { - logger.info('SWITCH: get side chain #%s-%s...', potential.number, potential.hash); - const sideChain = yield getWholeForkBranch(potential); - logger.info('SWITCH: revert main chain to block #%s...', sideChain[0].number - 1); - yield revertToBlock(sideChain[0].number - 1); - try { - logger.info('SWITCH: apply side chain #%s-%s...', potential.number, potential.hash); - yield applySideChain(sideChain); - } catch (e) { - logger.warn('SWITCH: error %s', e.stack || e); - // Revert the revert (so we go back to original chain) - const revertedChain = yield getWholeForkBranch(current); - yield revertToBlock(revertedChain[0].number - 1); - yield applySideChain(revertedChain); - yield markSideChainAsWrong(sideChain); - } - } - }); - - const getWholeForkBranch = (topForkBlock) => co(function *() { - const fullBranch = []; - let isForkBlock = true; - let next = topForkBlock; - while (isForkBlock) { - fullBranch.push(next); - logger.trace('SWITCH: get absolute #%s-%s...', next.number - 1, next.previousHash); - next = yield dal.getAbsoluteBlockByNumberAndHash(next.number - 1, next.previousHash); - isForkBlock = next.fork; - } - //fullBranch.push(next); - // Revert order so we have a crescending branch - return fullBranch.reverse(); - }); - - const revertToBlock = (number) => co(function *() { - let nowCurrent = yield that.current(); - logger.trace('SWITCH: main chain current = #%s-%s...', nowCurrent.number, nowCurrent.hash); - while (nowCurrent.number > number) { - logger.trace('SWITCH: main chain revert #%s-%s...', nowCurrent.number, nowCurrent.hash); - yield mainContext.revertCurrentBlock(); - nowCurrent = yield that.current(); - } - }); - - const applySideChain = (chain) => co(function *() { - for (const block of chain) { - logger.trace('SWITCH: apply side block #%s-%s -> #%s-%s...', block.number, block.hash, block.number - 1, block.previousHash); - yield checkAndAddBlock(block, CHECK_ALL_RULES); - } - }); - - const markSideChainAsWrong = (chain) => co(function *() { - for (const block of chain) { - block.wrong = true; - // Saves the block (DAL) - yield dal.saveSideBlockInFile(block); - } - }); - - this.revertCurrentBlock = () => this.pushFIFO(() => mainContext.revertCurrentBlock()); - - this.applyNextAvailableFork = () => this.pushFIFO(() => mainContext.applyNextAvailableFork()); - - this.requirementsOfIdentities = (identities) => co(function *() { - let all = []; - let current = yield dal.getCurrentBlockOrNull(); - for (const obj of identities) { - let idty = new Identity(obj); - try { - let reqs = yield that.requirementsOfIdentity(idty, current); - all.push(reqs); - } catch (e) { - logger.warn(e); - } - } - return all; - }); - - this.requirementsOfIdentity = (idty, current) => co(function *() { - // TODO: this is not clear - let expired = false; - let outdistanced = false; - let isSentry = false; - let wasMember = false; - let expiresMS = 0; - let expiresPending = 0; - let certs = []; - let certsPending = []; - let mssPending = []; - try { - const join = yield server.generatorGetJoinData(current, idty.hash, 'a'); - const pubkey = join.identity.pubkey; - // Check WoT stability - const someNewcomers = join.identity.wasMember ? [] : [join.identity.pubkey]; - const nextBlockNumber = current ? current.number + 1 : 0; - const joinData = {}; - joinData[join.identity.pubkey] = join; - const updates = {}; - certsPending = yield dal.certDAL.getToTarget(idty.hash); - certsPending = certsPending.map((c) => { - c.blockstamp = [c.block_number, c.block_hash].join('-') - return c - }); - mssPending = yield dal.msDAL.getPendingINOfTarget(idty.hash) - mssPending = mssPending.map((ms) => { - ms.blockstamp = ms.block - ms.sig = ms.signature - ms.type = ms.membership - return ms - }); - const newCerts = yield server.generatorComputeNewCerts(nextBlockNumber, [join.identity.pubkey], joinData, updates); - const newLinks = yield server.generatorNewCertsToLinks(newCerts, updates); - const currentTime = current ? current.medianTime : 0; - certs = yield that.getValidCerts(pubkey, newCerts); - outdistanced = yield rules.HELPERS.isOver3Hops(pubkey, newLinks, someNewcomers, current, conf, dal); - // Expiration of current membershship - const currentMembership = yield dal.mindexDAL.getReducedMS(pubkey); - const currentMSN = currentMembership ? parseInt(currentMembership.created_on) : -1; - if (currentMSN >= 0) { - if (join.identity.member) { - const msBlock = yield dal.getBlock(currentMSN); - if (msBlock && msBlock.medianTime) { // special case for block #0 - expiresMS = Math.max(0, (msBlock.medianTime + conf.msValidity - currentTime)); - } - else { - expiresMS = conf.msValidity; - } - } else { - expiresMS = 0; - } - } - // Expiration of pending membership - const lastJoin = yield dal.lastJoinOfIdentity(idty.hash); - if (lastJoin) { - const msBlock = yield dal.getBlock(lastJoin.blockNumber); - if (msBlock && msBlock.medianTime) { // Special case for block#0 - expiresPending = Math.max(0, (msBlock.medianTime + conf.msValidity - currentTime)); - } - else { - expiresPending = conf.msValidity; - } - } - wasMember = idty.wasMember; - isSentry = idty.member && (yield dal.isSentry(idty.pub, conf)); - // Expiration of certifications - for (const cert of certs) { - cert.expiresIn = Math.max(0, cert.timestamp + conf.sigValidity - currentTime); - } - } catch (e) { - // We throw whatever isn't "Too old identity" error - if (!(e && e.uerr && e.uerr.ucode == constants.ERRORS.TOO_OLD_IDENTITY.uerr.ucode)) { - throw e; - } else { - expired = true; - } - } - return { - pubkey: idty.pubkey, - uid: idty.uid, - sig: idty.sig, - meta: { - timestamp: idty.buid - }, - revocation_sig: idty.revocation_sig, - revoked: idty.revoked, - revoked_on: idty.revoked_on, - expired: expired, - outdistanced: outdistanced, - isSentry: isSentry, - wasMember: wasMember, - certifications: certs, - pendingCerts: certsPending, - pendingMemberships: mssPending, - membershipPendingExpiresIn: expiresPending, - membershipExpiresIn: expiresMS - }; - }); - - this.getValidCerts = (newcomer, newCerts) => co(function *() { - const links = yield dal.getValidLinksTo(newcomer); - const certsFromLinks = links.map((lnk) => { return { from: lnk.issuer, to: lnk.receiver, timestamp: lnk.expires_on - conf.sigValidity }; }); - const certsFromCerts = []; - const certs = newCerts[newcomer] || []; - for (const cert of certs) { - const block = yield dal.getBlock(cert.block_number); - certsFromCerts.push({ - from: cert.from, - to: cert.to, - sig: cert.sig, - timestamp: block.medianTime - }); - } - return certsFromLinks.concat(certsFromCerts); - }); - - this.isMember = () => dal.isMember(selfPubkey); - this.getCountOfSelfMadePoW = () => dal.getCountOfPoW(selfPubkey); - - // This method is called by duniter-crawler 1.3.x - this.saveParametersForRootBlock = (block) => server.blockchain.saveParametersForRoot(block, conf, dal) - - this.blocksBetween = (from, count) => co(function *() { - if (count > 5000) { - throw 'Count is too high'; - } - const current = yield that.current(); - count = Math.min(current.number - from + 1, count); - if (!current || current.number < from) { - return []; - } - return dal.getBlocksBetween(from, from + count - 1); - }); - - /** - * Allows to quickly insert a bunch of blocks. To reach such speed, this method skips global rules and buffers changes. - * - * **This method should be used ONLY when a node is really far away from current blockchain HEAD (i.e several hundreds of blocks late). - * - * This method is called by duniter-crawler 1.3.x. - * - * @param blocks An array of blocks to insert. - * @param to The final block number of the fast insertion. - */ - this.fastBlockInsertions = (blocks, to) => mainContext.quickApplyBlocks(blocks, to) -} diff --git a/app/service/BlockchainService.ts b/app/service/BlockchainService.ts new file mode 100644 index 000000000..2c874f7d7 --- /dev/null +++ b/app/service/BlockchainService.ts @@ -0,0 +1,428 @@ +"use strict"; +import {GlobalFifoPromise} from "./GlobalFifoPromise" +import {BlockchainContext} from "../lib/computation/BlockchainContext" +import {ConfDTO} from "../lib/dto/ConfDTO" +import {FileDAL} from "../lib/dal/fileDAL" +import {QuickSynchronizer} from "../lib/computation/QuickSync" +import {BlockDTO} from "../lib/dto/BlockDTO" +import {DBIdentity} from "../lib/dal/sqliteDAL/IdentityDAL" +import {DBBlock} from "../lib/db/DBBlock" + +const _ = require('underscore'); +const co = require('co'); +const parsers = require('duniter-common').parsers; +const rules = require('../lib/rules') +const constants = require('../lib/constants'); +const Block = require('../lib/entity/block'); +const Identity = require('../lib/entity/identity'); +const Transaction = require('../lib/entity/transaction'); + +const CHECK_ALL_RULES = true; + +export class BlockchainService { + + mainContext:BlockchainContext + conf:ConfDTO + dal:FileDAL + logger:any + selfPubkey:string + quickSynchronizer:QuickSynchronizer + + constructor(private server:any) { + this.mainContext = new BlockchainContext() + } + + getContext() { + return this.mainContext + } + + + setConfDAL(newConf:ConfDTO, newDAL:FileDAL, newKeyPair:any) { + this.dal = newDAL; + this.conf = newConf; + this.logger = require('../lib/logger')(this.dal.profile) + this.quickSynchronizer = new QuickSynchronizer(this.server.blockchain, this.conf, this.dal, this.logger) + this.mainContext.setConfDAL(this.conf, this.dal, this.server.blockchain, this.quickSynchronizer) + this.selfPubkey = newKeyPair.publicKey; + } + + current() { + return this.dal.getCurrentBlockOrNull() + } + + + async promoted(number:number) { + const bb = await this.dal.getPromoted(number); + if (!bb) throw constants.ERRORS.BLOCK_NOT_FOUND; + return bb; + } + + checkBlock(block:any) { + const dto = BlockDTO.fromJSONObject(block) + return this.mainContext.checkBlock(dto); + } + + async branches() { + let forkBlocks = await this.dal.blockDAL.getForkBlocks(); + forkBlocks = _.sortBy(forkBlocks, 'number'); + // Get the blocks refering current blockchain + const forkables = []; + for (const block of forkBlocks) { + const refered = await this.dal.getBlockByNumberAndHashOrNull(block.number - 1, block.previousHash); + if (refered) { + forkables.push(block); + } + } + const branches = this.getBranches(forkables, _.difference(forkBlocks, forkables)); + const current = await this.mainContext.current(); + const forks = branches.map((branch) => branch[branch.length - 1]); + return forks.concat([current]); + } + + private getBranches(forkables:any[], others:any[]) { + // All starting branches + let branches = forkables.map((fork) => [fork]); + // For each "pending" block, we try to add it to all branches + for (const other of others) { + for (let j = 0, len2 = branches.length; j < len2; j++) { + const branch = branches[j]; + const last = branch[branch.length - 1]; + if (other.number == last.number + 1 && other.previousHash == last.hash) { + branch.push(other); + } else if (branch[1]) { + // We try to find out if another fork block can be forked + const diff = other.number - branch[0].number; + if (diff > 0 && branch[diff - 1] && branch[diff - 1].hash == other.previousHash) { + // We duplicate the branch, and we add the block to this second branch + branches.push(branch.slice()); + // First we remove the blocks this are not part of the fork + branch.splice(diff, branch.length - diff); + branch.push(other); + j++; + } + } + } + } + branches = _.sortBy(branches, (branch:any) => -branch.length); + if (branches.length) { + const maxSize = branches[0].length; + const longestsBranches = []; + for (const branch of branches) { + if (branch.length == maxSize) { + longestsBranches.push(branch); + } + } + return longestsBranches; + } + return []; + } + + submitBlock(obj:any, doCheck:boolean, forkAllowed:boolean) { + return GlobalFifoPromise.pushFIFO(() => { + return this.checkAndAddBlock(obj, doCheck, forkAllowed) + }) + } + + private async checkAndAddBlock(blockToAdd:any, doCheck:boolean, forkAllowed:boolean = false) { + // Check global format, notably version number + const obj = parsers.parseBlock.syncWrite(Block.statics.fromJSON(blockToAdd).getRawSigned()); + // Force usage of local currency name, do not accept other currencies documents + if (this.conf.currency) { + obj.currency = this.conf.currency || obj.currency; + } else { + this.conf.currency = obj.currency; + } + try { + Transaction.statics.cleanSignatories(obj.transactions); + } + catch (e) { + throw e; + } + let existing = await this.dal.getBlockByNumberAndHashOrNull(obj.number, obj.hash); + if (existing) { + throw constants.ERRORS.BLOCK_ALREADY_PROCESSED; + } + let current = await this.mainContext.current(); + let followsCurrent = !current || (obj.number == current.number + 1 && obj.previousHash == current.hash); + if (followsCurrent) { + // try to add it on main blockchain + const dto = BlockDTO.fromJSONObject(obj) + if (doCheck) { + const { index, HEAD } = await this.mainContext.checkBlock(dto, constants.WITH_SIGNATURES_AND_POW); + return await this.mainContext.addBlock(dto, index, HEAD) + } else { + return await this.mainContext.addBlock(dto) + } + } else if (forkAllowed) { + // add it as side chain + if (current.number - obj.number + 1 >= this.conf.forksize) { + throw 'Block out of fork window'; + } + let absolute = await this.dal.getAbsoluteBlockByNumberAndHash(obj.number, obj.hash) + let res = null; + if (!absolute) { + res = await this.mainContext.addSideBlock(obj) + } + await this.tryToFork(current); + return res; + } else { + throw "Fork block rejected by " + this.selfPubkey; + } + } + + + tryToFork(current:DBBlock) { + return this.eventuallySwitchOnSideChain(current) + } + + private async eventuallySwitchOnSideChain(current:DBBlock) { + const branches = await this.branches() + const blocksAdvance = this.conf.swichOnTimeAheadBy / (this.conf.avgGenTime / 60); + const timeAdvance = this.conf.swichOnTimeAheadBy * 60; + let potentials = _.without(branches, current); + // We switch only to blockchain with X_MIN advance considering both theoretical time by block + written time + potentials = _.filter(potentials, (p:DBBlock) => p.number - current.number >= blocksAdvance + && p.medianTime - current.medianTime >= timeAdvance); + this.logger.trace('SWITCH: %s branches...', branches.length); + this.logger.trace('SWITCH: %s potential side chains...', potentials.length); + for (const potential of potentials) { + this.logger.info('SWITCH: get side chain #%s-%s...', potential.number, potential.hash); + const sideChain = await this.getWholeForkBranch(potential) + this.logger.info('SWITCH: revert main chain to block #%s...', sideChain[0].number - 1); + await this.revertToBlock(sideChain[0].number - 1) + try { + this.logger.info('SWITCH: apply side chain #%s-%s...', potential.number, potential.hash); + await this.applySideChain(sideChain) + } catch (e) { + this.logger.warn('SWITCH: error %s', e.stack || e); + // Revert the revert (so we go back to original chain) + const revertedChain = await this.getWholeForkBranch(current) + await this.revertToBlock(revertedChain[0].number - 1) + await this.applySideChain(revertedChain) + await this.markSideChainAsWrong(sideChain) + } + } + } + + private async getWholeForkBranch(topForkBlock:DBBlock) { + const fullBranch = []; + let isForkBlock = true; + let next = topForkBlock; + while (isForkBlock) { + fullBranch.push(next); + this.logger.trace('SWITCH: get absolute #%s-%s...', next.number - 1, next.previousHash); + next = await this.dal.getAbsoluteBlockByNumberAndHash(next.number - 1, next.previousHash); + isForkBlock = next.fork; + } + //fullBranch.push(next); + // Revert order so we have a crescending branch + return fullBranch.reverse(); + } + + private async revertToBlock(number:number) { + let nowCurrent = await this.current(); + this.logger.trace('SWITCH: main chain current = #%s-%s...', nowCurrent.number, nowCurrent.hash); + while (nowCurrent.number > number) { + this.logger.trace('SWITCH: main chain revert #%s-%s...', nowCurrent.number, nowCurrent.hash); + await this.mainContext.revertCurrentBlock(); + nowCurrent = await this.current(); + } + } + + private async applySideChain(chain:DBBlock[]) { + for (const block of chain) { + this.logger.trace('SWITCH: apply side block #%s-%s -> #%s-%s...', block.number, block.hash, block.number - 1, block.previousHash); + await this.checkAndAddBlock(block, CHECK_ALL_RULES); + } + } + + private async markSideChainAsWrong(chain:DBBlock[]) { + for (const block of chain) { + block.wrong = true; + // Saves the block (DAL) + await this.dal.saveSideBlockInFile(block); + } + } + + revertCurrentBlock() { + return GlobalFifoPromise.pushFIFO(() => this.mainContext.revertCurrentBlock()) + } + + + applyNextAvailableFork() { + return GlobalFifoPromise.pushFIFO(() => this.mainContext.applyNextAvailableFork()) + } + + + async requirementsOfIdentities(identities:DBIdentity[]) { + let all = []; + let current = await this.dal.getCurrentBlockOrNull(); + for (const obj of identities) { + let idty = new Identity(obj); + try { + let reqs = await this.requirementsOfIdentity(idty, current); + all.push(reqs); + } catch (e) { + this.logger.warn(e); + } + } + return all; + } + + async requirementsOfIdentity(idty:DBIdentity, current:DBBlock) { + // TODO: this is not clear + let expired = false; + let outdistanced = false; + let isSentry = false; + let wasMember = false; + let expiresMS = 0; + let expiresPending = 0; + let certs = []; + let certsPending = []; + let mssPending = []; + try { + const join = await this.server.generatorGetJoinData(current, idty.hash, 'a'); + const pubkey = join.identity.pubkey; + // Check WoT stability + const someNewcomers = join.identity.wasMember ? [] : [join.identity.pubkey]; + const nextBlockNumber = current ? current.number + 1 : 0; + const joinData:any = {}; + joinData[join.identity.pubkey] = join; + const updates = {}; + certsPending = await this.dal.certDAL.getToTarget(idty.hash); + certsPending = certsPending.map((c:any) => { + c.blockstamp = [c.block_number, c.block_hash].join('-') + return c + }); + mssPending = await this.dal.msDAL.getPendingINOfTarget(idty.hash) + mssPending = mssPending.map((ms:any) => { + ms.blockstamp = ms.block + ms.sig = ms.signature + ms.type = ms.membership + return ms + }); + const newCerts = await this.server.generatorComputeNewCerts(nextBlockNumber, [join.identity.pubkey], joinData, updates); + const newLinks = await this.server.generatorNewCertsToLinks(newCerts, updates); + const currentTime = current ? current.medianTime : 0; + certs = await this.getValidCerts(pubkey, newCerts); + outdistanced = await rules.HELPERS.isOver3Hops(pubkey, newLinks, someNewcomers, current, this.conf, this.dal); + // Expiration of current membershship + const currentMembership = await this.dal.mindexDAL.getReducedMS(pubkey); + const currentMSN = currentMembership ? parseInt(currentMembership.created_on) : -1; + if (currentMSN >= 0) { + if (join.identity.member) { + const msBlock = await this.dal.getBlock(currentMSN); + if (msBlock && msBlock.medianTime) { // special case for block #0 + expiresMS = Math.max(0, (msBlock.medianTime + this.conf.msValidity - currentTime)); + } + else { + expiresMS = this.conf.msValidity; + } + } else { + expiresMS = 0; + } + } + // Expiration of pending membership + const lastJoin = await this.dal.lastJoinOfIdentity(idty.hash); + if (lastJoin) { + const msBlock = await this.dal.getBlock(lastJoin.blockNumber); + if (msBlock && msBlock.medianTime) { // Special case for block#0 + expiresPending = Math.max(0, (msBlock.medianTime + this.conf.msValidity - currentTime)); + } + else { + expiresPending = this.conf.msValidity; + } + } + wasMember = idty.wasMember; + isSentry = idty.member && (await this.dal.isSentry(idty.pubkey, this.conf)); + // Expiration of certifications + for (const cert of certs) { + cert.expiresIn = Math.max(0, cert.timestamp + this.conf.sigValidity - currentTime); + } + } catch (e) { + // We throw whatever isn't "Too old identity" error + if (!(e && e.uerr && e.uerr.ucode == constants.ERRORS.TOO_OLD_IDENTITY.uerr.ucode)) { + throw e; + } else { + expired = true; + } + } + return { + pubkey: idty.pubkey, + uid: idty.uid, + sig: idty.sig, + meta: { + timestamp: idty.buid + }, + revocation_sig: idty.revocation_sig, + revoked: idty.revoked, + revoked_on: idty.revoked_on, + expired: expired, + outdistanced: outdistanced, + isSentry: isSentry, + wasMember: wasMember, + certifications: certs, + pendingCerts: certsPending, + pendingMemberships: mssPending, + membershipPendingExpiresIn: expiresPending, + membershipExpiresIn: expiresMS + }; + } + + async getValidCerts(newcomer:string, newCerts:any) { + const links = await this.dal.getValidLinksTo(newcomer); + const certsFromLinks = links.map((lnk:any) => { return { from: lnk.issuer, to: lnk.receiver, timestamp: lnk.expires_on - this.conf.sigValidity }; }); + const certsFromCerts = []; + const certs = newCerts[newcomer] || []; + for (const cert of certs) { + const block = await this.dal.getBlock(cert.block_number); + certsFromCerts.push({ + from: cert.from, + to: cert.to, + sig: cert.sig, + timestamp: block.medianTime + }); + } + return certsFromLinks.concat(certsFromCerts); + } + + isMember() { + return this.dal.isMember(this.selfPubkey) + } + + getCountOfSelfMadePoW() { + return this.dal.getCountOfPoW(this.selfPubkey) + } + + + // This method is called by duniter-crawler 1.3.x + saveParametersForRootBlock(block:BlockDTO) { + return this.server.blockchain.saveParametersForRoot(block, this.conf, this.dal) + } + + async blocksBetween(from:number, count:number) { + if (count > 5000) { + throw 'Count is too high'; + } + const current = await this.current() + count = Math.min(current.number - from + 1, count); + if (!current || current.number < from) { + return []; + } + return this.dal.getBlocksBetween(from, from + count - 1); + } + + /** + * Allows to quickly insert a bunch of blocks. To reach such speed, this method skips global rules and buffers changes. + * + * **This method should be used ONLY when a node is really far away from current blockchain HEAD (i.e several hundreds of blocks late). + * + * This method is called by duniter-crawler 1.3.x. + * + * @param blocks An array of blocks to insert. + * @param to The final block number of the fast insertion. + */ + fastBlockInsertions(blocks:BlockDTO[], to:number | null) { + return this.mainContext.quickApplyBlocks(blocks, to) + } +} diff --git a/app/service/AbstractService.js b/app/service/GlobalFifoPromise.ts similarity index 75% rename from app/service/AbstractService.js rename to app/service/GlobalFifoPromise.ts index 28f7e1e79..85b7c2959 100644 --- a/app/service/AbstractService.js +++ b/app/service/GlobalFifoPromise.ts @@ -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 () { }); }); }; -}; +} diff --git a/app/service/IdentityService.js b/app/service/IdentityService.ts similarity index 53% rename from app/service/IdentityService.js rename to app/service/IdentityService.ts index dcb7ffe08..97a7d9657 100644 --- a/app/service/IdentityService.js +++ b/app/service/IdentityService.ts @@ -1,4 +1,9 @@ +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; } - })); - }; + }) + } } diff --git a/app/service/MembershipService.js b/app/service/MembershipService.js deleted file mode 100644 index ffce63db7..000000000 --- a/app/service/MembershipService.js +++ /dev/null @@ -1,65 +0,0 @@ -"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; - })); -} diff --git a/app/service/MembershipService.ts b/app/service/MembershipService.ts new file mode 100644 index 000000000..5fa1defa1 --- /dev/null +++ b/app/service/MembershipService.ts @@ -0,0 +1,66 @@ +"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; + }) + } +} diff --git a/app/service/PeeringService.js b/app/service/PeeringService.ts similarity index 68% rename from app/service/PeeringService.js rename to app/service/PeeringService.ts index 525ade941..31de19ac0 100644 --- a/app/service/PeeringService.js +++ b/app/service/PeeringService.ts @@ -1,5 +1,9 @@ -"use strict"; -const co = require('co'); +import {GlobalFifoPromise} from "./GlobalFifoPromise" +import {ConfDTO, Keypair} from "../lib/dto/ConfDTO" +import {FileDAL} from "../lib/dal/fileDAL" +import {DBPeer} from "../lib/dal/sqliteDAL/PeerDAL" +import {DBBlock} from "../lib/db/DBBlock" + const util = require('util'); const _ = require('underscore'); const Q = require('q'); @@ -13,41 +17,49 @@ const hashf = require('duniter-common').hashf; const rawer = require('duniter-common').rawer; const constants = require('../lib/constants'); const Peer = require('../lib/entity/peer'); -const AbstractService = require('./AbstractService'); -function PeeringService(server) { +export interface Keyring { + publicKey:string + secretKey:string +} - AbstractService.call(this); - let conf, dal, pair, selfPubkey; +export class PeeringService { - this.setConfDAL = (newConf, newDAL, newPair) => { - dal = newDAL; - conf = newConf; - pair = newPair; - this.pubkey = pair.publicKey; - selfPubkey = this.pubkey; - }; + conf:ConfDTO + dal:FileDAL + selfPubkey:string + pair:Keyring + pubkey:string + peerInstance:DBPeer | null - let peer = null; - const that = this; + constructor(private server:any) { + } - this.peer = (newPeer) => co(function *() { + setConfDAL(newConf:ConfDTO, newDAL:FileDAL, newPair:Keyring) { + this.dal = newDAL; + this.conf = newConf; + this.pair = newPair; + this.pubkey = this.pair.publicKey; + this.selfPubkey = this.pubkey; + } + + async peer(newPeer:DBPeer | null = null) { if (newPeer) { - peer = newPeer; + this.peerInstance = newPeer; } - let thePeer = peer; + let thePeer = this.peerInstance; if (!thePeer) { - thePeer = yield that.generateSelfPeer(conf, 0); + thePeer = await this.generateSelfPeer(this.conf, 0) } return Peer.statics.peerize(thePeer); - }); + } - this.mirrorEndpoints = () => co(function *() { - let localPeer = yield that.peer(); - return getOtherEndpoints(localPeer.endpoints, conf); - }); + async mirrorEndpoints() { + let localPeer = await this.peer(); + return this.getOtherEndpoints(localPeer.endpoints, this.conf); + } - this.checkPeerSignature = function (p) { + checkPeerSignature(p:DBPeer) { const raw = rawer.getPeerWithoutSignature(p); const sig = p.signature; const pub = p.pubkey; @@ -55,19 +67,19 @@ function PeeringService(server) { return !!signaturesMatching; }; - this.submitP = function(peering, eraseIfAlreadyRecorded, cautious){ + submitP(peering:DBPeer, eraseIfAlreadyRecorded = false, cautious = true) { // Force usage of local currency name, do not accept other currencies documents - peering.currency = conf.currency || peering.currency; + peering.currency = this.conf.currency || peering.currency; let thePeer = new Peer(peering); let sp = thePeer.block.split('-'); const blockNumber = parseInt(sp[0]); let blockHash = sp[1]; let sigTime = 0; - let block; + let block:DBBlock | null; let makeCheckings = cautious || cautious === undefined; - return that.pushFIFO(() => co(function *() { + return GlobalFifoPromise.pushFIFO(async () => { if (makeCheckings) { - let goodSignature = that.checkPeerSignature(thePeer); + let goodSignature = this.checkPeerSignature(thePeer); if (!goodSignature) { throw 'Signature from a peer must match'; } @@ -76,7 +88,7 @@ function PeeringService(server) { thePeer.statusTS = 0; thePeer.status = 'UP'; } else { - block = yield dal.getBlockByNumberAndHashOrNull(blockNumber, blockHash); + block = await this.dal.getBlockByNumberAndHashOrNull(blockNumber, blockHash); if (!block && makeCheckings) { throw constants.ERROR.PEER.UNKNOWN_REFERENCE_BLOCK; } else if (!block) { @@ -87,7 +99,7 @@ function PeeringService(server) { } sigTime = block ? block.medianTime : 0; thePeer.statusTS = sigTime; - let found = yield dal.getPeerOrNull(thePeer.pubkey); + let found = await this.dal.getPeerOrNull(thePeer.pubkey); let peerEntity = Peer.statics.peerize(found || thePeer); if(found){ // Already existing peer @@ -118,16 +130,16 @@ function PeeringService(server) { peerEntity.last_try = null; peerEntity.hash = String(hashf(peerEntity.getRawSigned())).toUpperCase(); peerEntity.raw = peerEntity.getRaw(); - yield dal.savePeer(peerEntity); + await this.dal.savePeer(peerEntity); let savedPeer = Peer.statics.peerize(peerEntity); - if (peerEntity.pubkey == selfPubkey) { - const localEndpoint = yield server.getMainEndpoint(conf); + if (peerEntity.pubkey == this.selfPubkey) { + const localEndpoint = await this.server.getMainEndpoint(this.conf); const localNodeNotListed = !peerEntity.containsEndpoint(localEndpoint); - const current = localNodeNotListed && (yield dal.getCurrentBlockOrNull()); + const current = localNodeNotListed && (await this.dal.getCurrentBlockOrNull()); if (!localNodeNotListed) { const indexOfThisNode = peerEntity.endpoints.indexOf(localEndpoint); if (indexOfThisNode !== -1) { - server.push({ + this.server.push({ nodeIndexInPeers: indexOfThisNode }); } else { @@ -136,24 +148,24 @@ function PeeringService(server) { } if (localNodeNotListed && (!current || current.number > blockNumber)) { // Document with pubkey of local peer, but doesn't contain local interface: we must add it - that.generateSelfPeer(conf, 0); + this.generateSelfPeer(this.conf, 0); } else { - peer = peerEntity; + this.peerInstance = peerEntity; } } return savedPeer; - })); - }; + }) + } - this.handleNewerPeer = (pretendedNewer) => { + handleNewerPeer(pretendedNewer:DBPeer) { logger.debug('Applying pretended newer peer document %s/%s', pretendedNewer.block); - return server.singleWritePromise(_.extend({ documentType: 'peer' }, pretendedNewer)); - }; + return this.server.singleWritePromise(_.extend({ documentType: 'peer' }, pretendedNewer)); + } - this.generateSelfPeer = (theConf, signalTimeInterval) => co(function*() { - const current = yield server.dal.getCurrentBlockOrNull(); + async generateSelfPeer(theConf:ConfDTO, signalTimeInterval:number) { + const current = await this.server.dal.getCurrentBlockOrNull(); const currency = theConf.currency || constants.DEFAULT_CURRENCY_NAME; - const peers = yield dal.findPeers(selfPubkey); + const peers = await this.dal.findPeers(this.selfPubkey); let p1 = { version: constants.DOCUMENTS_VERSION, currency: currency, @@ -163,22 +175,22 @@ function PeeringService(server) { if (peers.length != 0 && peers[0]) { p1 = _(peers[0]).extend({version: constants.DOCUMENTS_VERSION, currency: currency}); } - let endpoint = yield server.getMainEndpoint(theConf); - let otherPotentialEndpoints = getOtherEndpoints(p1.endpoints, theConf); + let endpoint = await this.server.getMainEndpoint(theConf); + let otherPotentialEndpoints = this.getOtherEndpoints(p1.endpoints, theConf); logger.info('Sibling endpoints:', otherPotentialEndpoints); - let reals = yield otherPotentialEndpoints.map((theEndpoint) => co(function*() { + let reals = await otherPotentialEndpoints.map(async (theEndpoint:string) => { let real = true; let remote = Peer.statics.endpoint2host(theEndpoint); try { // We test only BMA APIs, because other may exist and we cannot judge against them yet if (theEndpoint.startsWith('BASIC_MERKLED_API')) { - let answer = yield rp('http://' + remote + '/network/peering', { json: true }); - if (!answer || answer.pubkey != selfPubkey) { + let answer = await rp('http://' + remote + '/network/peering', { json: true }); + if (!answer || answer.pubkey != this.selfPubkey) { throw Error("Not same pubkey as local instance"); } } - // We also remove endpoints that are *asked* to be removed in the conf file - if ((conf.rmEndpoints || []).indexOf(theEndpoint) !== -1) { + // We also remove endpoints this are *asked* to be removed in the conf file + if ((this.conf.rmEndpoints || []).indexOf(theEndpoint) !== -1) { real = false; } } catch (e) { @@ -186,7 +198,7 @@ function PeeringService(server) { real = false; } return real; - })); + }) let toConserve = otherPotentialEndpoints.filter((ep, i) => reals[i]); if (!currency || endpoint == 'BASIC_MERKLED_API') { logger.error('It seems there is an issue with your configuration.'); @@ -202,34 +214,34 @@ function PeeringService(server) { } // The number cannot be superior to current block minBlock = Math.min(minBlock, current ? current.number : minBlock); - let targetBlock = yield server.dal.getBlock(minBlock); - const p2 = { + let targetBlock = await this.server.dal.getBlock(minBlock); + const p2:any = { version: constants.DOCUMENTS_VERSION, currency: currency, - pubkey: selfPubkey, + pubkey: this.selfPubkey, block: targetBlock ? [targetBlock.number, targetBlock.hash].join('-') : constants.PEER.SPECIAL_BLOCK, - endpoints: _.uniq([endpoint].concat(toConserve).concat(conf.endpoints || [])) + endpoints: _.uniq([endpoint].concat(toConserve).concat(this.conf.endpoints || [])) }; const raw2 = dos2unix(new Peer(p2).getRaw()); logger.info('External access:', new Peer(p2).getURL()); logger.debug('Generating server\'s peering entry based on block#%s...', p2.block.split('-')[0]); - p2.signature = yield server.sign(raw2); - p2.pubkey = selfPubkey; + p2.signature = await this.server.sign(raw2); + p2.pubkey = this.selfPubkey; p2.documentType = 'peer'; // Remember this is now local peer value - peer = p2; + this.peerInstance = p2; // Submit & share with the network - yield server.submitP(p2, false); - const selfPeer = yield dal.getPeer(selfPubkey); + await this.server.submitP(p2, false); + const selfPeer = await this.dal.getPeer(this.selfPubkey); // Set peer's statut to UP selfPeer.documentType = 'selfPeer'; - yield that.peer(selfPeer); - server.streamPush(selfPeer); + await this.peer(selfPeer); + this.server.streamPush(selfPeer); logger.info("Next peering signal in %s min", signalTimeInterval / 1000 / 60); return selfPeer; - }); + } - function getOtherEndpoints(endpoints, theConf) { + private getOtherEndpoints(endpoints:string[], theConf:ConfDTO) { return endpoints.filter((ep) => { return !ep.match(constants.BMA_REGEXP) || ( !(ep.includes(' ' + theConf.remoteport) && ( @@ -239,7 +251,3 @@ function PeeringService(server) { } util.inherits(PeeringService, events.EventEmitter); - -module.exports = function (server, pair, dal) { - return new PeeringService(server, pair, dal); -}; diff --git a/app/service/TransactionsService.js b/app/service/TransactionsService.js deleted file mode 100644 index bb5f89a20..000000000 --- a/app/service/TransactionsService.js +++ /dev/null @@ -1,59 +0,0 @@ -"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; - } - })); -} diff --git a/app/service/TransactionsService.ts b/app/service/TransactionsService.ts new file mode 100644 index 000000000..d987d811f --- /dev/null +++ b/app/service/TransactionsService.ts @@ -0,0 +1,58 @@ +"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; + } + }) + } +} diff --git a/server.js b/server.js index 3d8a77bb8..d07d87c2f 100644 --- a/server.js +++ b/server.js @@ -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 diff --git a/test/eslint.js b/test/eslint.js index 24994f994..e66ef6503 100644 --- a/test/eslint.js +++ b/test/eslint.js @@ -1,17 +1,21 @@ -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 -- GitLab