From 5fa88a0de40ad5b967555e6b2feaa919345dd340 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9dric=20Moreau?= <cem.moreau@gmail.com>
Date: Thu, 3 May 2018 17:02:07 +0200
Subject: [PATCH] [enh] Migrate Blockchain + Transactions to LokiJS database

---
 app/lib/computation/BlockchainContext.ts      | 10 +-
 app/lib/computation/QuickSync.ts              |  2 +
 app/lib/dal/drivers/LokiFsAdapter.ts          | 67 +++++++++++--
 app/lib/dal/drivers/LokiJsDriver.ts           |  7 +-
 app/lib/dal/fileDAL.ts                        | 14 ++-
 .../dal/indexDAL/abstract/BlockchainDAO.ts    |  6 ++
 app/lib/dal/indexDAL/abstract/TxsDAO.ts       |  2 +
 app/lib/dal/indexDAL/loki/LokiBlockchain.ts   | 71 ++++++++++++--
 app/lib/dal/indexDAL/loki/LokiIndex.ts        |  9 +-
 app/lib/dal/indexDAL/loki/LokiTransactions.ts | 56 ++++++++---
 app/lib/db/DBBlock.ts                         |  2 +-
 app/lib/other_constants.ts                    |  2 +-
 app/lib/system/directory.ts                   |  4 +-
 app/modules/config.ts                         | 95 +++++++++++++++++++
 app/service/BlockchainService.ts              |  7 +-
 bin/duniter                                   |  2 +-
 server.ts                                     |  2 +
 test/dal/loki.ts                              | 42 +++++++-
 test/dal/source_dal.js                        |  4 +-
 test/fast/cfs.js                              |  2 +-
 test/fast/dal/basic-loki.ts                   |  4 +-
 test/fast/dal/iindex-loki.ts                  |  5 +-
 test/integration/branches2.ts                 |  4 +-
 test/integration/branches_revert2.js          |  4 +-
 test/integration/branches_revert_balance.js   |  2 +-
 test/integration/register-fork-blocks.js      |  6 +-
 test/integration/revocation-test.js           |  2 +-
 test/integration/v1.1-dividend.js             |  8 +-
 28 files changed, 375 insertions(+), 66 deletions(-)

diff --git a/app/lib/computation/BlockchainContext.ts b/app/lib/computation/BlockchainContext.ts
index a1f3dcbc6..84173d9d4 100644
--- a/app/lib/computation/BlockchainContext.ts
+++ b/app/lib/computation/BlockchainContext.ts
@@ -42,14 +42,14 @@ export class BlockchainContext {
    */
   private vHEAD_1:any
 
-  private HEADrefreshed: Promise<any> | null = Promise.resolve();
+  private HEADrefreshed: Promise<void> = Promise.resolve();
 
   /**
    * Refresh the virtual HEAD value for determined variables of the next coming block, avoiding to recompute them
    * each time a new block arrives to check if the values are correct. We can know and store them early on, in vHEAD.
    */
   private refreshHead(): Promise<void> {
-    this.HEADrefreshed = (async (): Promise<void> => {
+    this.HEADrefreshed = (async () => {
       this.vHEAD_1 = await this.dal.head(1);
       // We suppose next block will have same version #, and no particular data in the block (empty index)
       let block;
@@ -122,7 +122,7 @@ export class BlockchainContext {
 
   private 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
+    this.vHEAD_1 = this.vHEAD = null
     return block
   }
 
@@ -150,6 +150,10 @@ export class BlockchainContext {
     const block = forks[0];
     await this.checkAndAddBlock(BlockDTO.fromJSONObject(block))
     this.logger.debug('Applied block #%s', block.number);
+    if (block.number % 100 === 0) {
+      // Database trimming
+      await this.dal.loki.flushAndTrimData()
+    }
   }
 
   async checkAndAddBlock(block:BlockDTO) {
diff --git a/app/lib/computation/QuickSync.ts b/app/lib/computation/QuickSync.ts
index 4ebe5a288..071e07027 100644
--- a/app/lib/computation/QuickSync.ts
+++ b/app/lib/computation/QuickSync.ts
@@ -266,5 +266,7 @@ export class QuickSynchronizer {
         // sync_currConf = {};
       }
     }
+
+    await this.dal.loki.commitData()
   }
 }
diff --git a/app/lib/dal/drivers/LokiFsAdapter.ts b/app/lib/dal/drivers/LokiFsAdapter.ts
index de478b937..d19e58bf9 100644
--- a/app/lib/dal/drivers/LokiFsAdapter.ts
+++ b/app/lib/dal/drivers/LokiFsAdapter.ts
@@ -11,10 +11,11 @@
   serialization.
 */
 
-import {RealFS} from "../../system/directory"
+import {FileSystem} from "../../system/directory"
 import {DataErrors} from "../../common-libs/errors"
 import {CFSCore} from "../fileDALs/CFSCore"
 import {getNanosecondsTime} from "../../../ProcessCpuProfiler"
+import {NewLogger} from "../../logger"
 
 const fs = require('fs');
 const readline = require('readline');
@@ -33,6 +34,7 @@ interface IteratorResult<T> {
 
 export interface DBCommit {
   indexFile:string,
+  changes: string[]
   collections: {
     [coll:string]: string
   }
@@ -47,19 +49,45 @@ export class LokiFsAdapter {
   protected dbref = null
   protected dirtyPartitions: string[] = [];
 
-  constructor(dbDir:string) {
-    this.cfs = new CFSCore(dbDir, RealFS())
+  constructor(dbDir:string, fs:FileSystem) {
+    this.cfs = new CFSCore(dbDir, fs)
   }
 
   /**
-   * Main method to manually pilot the DB saving to disk.
+   * Main method to manually pilot the full DB saving to disk.
    * @param loki
    * @returns {Promise}
    */
-  async flush(loki:any) {
+  async dbDump(loki:any) {
     return new Promise(res => loki.saveDatabaseInternal(res))
   }
 
+  /**
+   * Flushes the DB changes to disk.
+   * @param loki
+   * @returns {Promise<number>} The number of changes detected.
+   */
+  async flush(loki:any): Promise<number> {
+    // If the database already has a commit file: incremental changes
+    if (await this.cfs.exists(LokiFsAdapter.COMMIT_FILE)) {
+      const commit = (await this.cfs.readJSON(LokiFsAdapter.COMMIT_FILE)) as DBCommit
+      const changesFilename = 'changes.' + getNanosecondsTime() + ".json"
+      const changes = JSON.parse(loki.serializeChanges())
+      await this.cfs.writeJSON(changesFilename, changes)
+      // Mark the changes as commited
+      commit.changes.push(changesFilename)
+      await this.cfs.writeJSON(LokiFsAdapter.COMMIT_FILE, commit)
+      // Forget about the changes now that we saved them
+      loki.clearChanges()
+      return changes.length
+    } else {
+      // Otherwise we make a full dump
+      await this.dbDump(loki)
+      loki.clearChanges()
+      return 0
+    }
+  }
+
   /**
    *
    * Method indirectly called by `flush`.
@@ -81,6 +109,7 @@ export class LokiFsAdapter {
     // Prepare the commit: inherit from existing commit
     let commit:DBCommit = {
       indexFile: 'index.db.' + getNanosecondsTime() + ".json",
+      changes: [],
       collections: {}
     }
     if (await this.cfs.exists(LokiFsAdapter.COMMIT_FILE)) {
@@ -193,6 +222,7 @@ export class LokiFsAdapter {
 
       for(idx=0; idx < dbcopy.collections.length; idx++) {
         dbcopy.collections[idx].data = [];
+        dbcopy.collections[idx].changes = [];
       }
 
       yield dbcopy.serialize({
@@ -219,7 +249,7 @@ export class LokiFsAdapter {
 
   /**
    *
-   * Automatically called by Loki.js on startup.
+   * Automatically called on startup.
    *
    * Loki persistence adapter interface function which outputs un-prototype db object reference to load from.
    *
@@ -242,7 +272,9 @@ export class LokiFsAdapter {
 
     // make sure file exists
     const dbname = this.cfs.getPath(commitObj.indexFile)
-    return new Promise((res, rej) => {
+
+    // Trimmed data first
+    await new Promise((res, rej) => {
       fs.stat(dbname, function (err:any, stats:any) {
         if (!err && stats.isFile()) {
           instream = fs.createReadStream(dbname);
@@ -274,6 +306,27 @@ export class LokiFsAdapter {
         }
       })
     })
+
+    // Changes data
+    for (const changeFile of commitObj.changes) {
+      const changes = await this.cfs.readJSON(changeFile)
+      let len = changes.length
+      for (let i = 1; i <= len; i++) {
+        const c = changes[i - 1]
+        const coll = loki.getCollection(c.name)
+        if (c.operation === 'I') {
+          c.obj.$loki = undefined
+          await coll.insert(c.obj)
+        }
+        else if (c.operation === 'U') {
+          await coll.update(c.obj)
+        }
+        else if (c.operation === 'R') {
+          await coll.remove(c.obj)
+        }
+        NewLogger().trace('[loki] Processed change %s (%s/%s)', c.name, i, len)
+      }
+    }
   };
 
 
diff --git a/app/lib/dal/drivers/LokiJsDriver.ts b/app/lib/dal/drivers/LokiJsDriver.ts
index 1be76ad45..b895cfefc 100644
--- a/app/lib/dal/drivers/LokiJsDriver.ts
+++ b/app/lib/dal/drivers/LokiJsDriver.ts
@@ -1,4 +1,5 @@
 import {LokiFsAdapter} from "./LokiFsAdapter"
+import {MemFS, RealFS} from "../../system/directory"
 
 const loki = require('lokijs')
 
@@ -10,7 +11,7 @@ export class LokiJsDriver {
   constructor(
     private dbFilePath:string = ''
   ) {
-    this.adapter = new LokiFsAdapter(dbFilePath)
+    this.adapter = new LokiFsAdapter(dbFilePath, dbFilePath ? RealFS() : MemFS())
     this.lokiInstance = new loki(dbFilePath + '/loki.db' || 'mem' + Date.now() + '.db', {
       adapter: this.adapter
     })
@@ -30,4 +31,8 @@ export class LokiJsDriver {
   async commitData() {
     return this.adapter.flush(this.lokiInstance)
   }
+
+  async flushAndTrimData() {
+    return this.adapter.dbDump(this.lokiInstance)
+  }
 }
diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts
index 171dfae91..775fd9e9b 100644
--- a/app/lib/dal/fileDAL.ts
+++ b/app/lib/dal/fileDAL.ts
@@ -19,7 +19,12 @@ import {BlockDTO} from "../dto/BlockDTO"
 import {DBHead} from "../db/DBHead"
 import {DBIdentity, IdentityDAL} from "./sqliteDAL/IdentityDAL"
 import {
-  CindexEntry, FullCindexEntry, FullMindexEntry, FullSindexEntry, IindexEntry, IndexEntry,
+  CindexEntry,
+  FullCindexEntry,
+  FullMindexEntry,
+  FullSindexEntry,
+  IindexEntry,
+  IndexEntry,
   SindexEntry
 } from "../indexer"
 import {DBPeer, PeerDAL} from "./sqliteDAL/PeerDAL"
@@ -37,7 +42,7 @@ import {MetaDAL} from "./sqliteDAL/MetaDAL"
 import {DataErrors} from "../common-libs/errors"
 import {BasicRevocableIdentity, IdentityDTO} from "../dto/IdentityDTO"
 import {BlockDAL} from "./sqliteDAL/BlockDAL"
-import {Directory, FileSystem} from "../system/directory"
+import {FileSystem} from "../system/directory"
 import {WoTBInstance} from "../wot"
 import {IIndexDAO} from "./indexDAL/abstract/IIndexDAO"
 import {LokiIIndex} from "./indexDAL/loki/LokiIIndex"
@@ -552,6 +557,11 @@ export class FileDAL {
     return idty
   }
 
+  // Duniter-UI dependency
+  async getWrittenIdtyByPubkey(pub:string) {
+    return !!(await this.iindexDAL.getFromPubkey(pub))
+  }
+
   async getWrittenIdtyByPubkeyForExistence(uid:string) {
     return !!(await this.iindexDAL.getFromPubkey(uid))
   }
diff --git a/app/lib/dal/indexDAL/abstract/BlockchainDAO.ts b/app/lib/dal/indexDAL/abstract/BlockchainDAO.ts
index fa992be09..9f99c06e0 100644
--- a/app/lib/dal/indexDAL/abstract/BlockchainDAO.ts
+++ b/app/lib/dal/indexDAL/abstract/BlockchainDAO.ts
@@ -23,6 +23,8 @@ export interface BlockchainDAO extends GenericDAO<DBBlock> {
 
   lastBlockOfIssuer(issuer:string): Promise<DBBlock|null>
 
+  lastBlockWithDividend(): Promise<DBBlock|null>
+
   getCountOfBlocksIssuedBy(issuer:string): Promise<number>
 
   saveBunch(blocks:DBBlock[]): Promise<void>
@@ -30,4 +32,8 @@ export interface BlockchainDAO extends GenericDAO<DBBlock> {
   dropNonForkBlocksAbove(number: number): Promise<void>
 
   setSideBlock(number:number, previousBlock:DBBlock|null): Promise<void>
+
+  removeForkBlock(number:number): Promise<void>
+
+  removeForkBlockAboveOrEqual(number:number): Promise<void>
 }
diff --git a/app/lib/dal/indexDAL/abstract/TxsDAO.ts b/app/lib/dal/indexDAL/abstract/TxsDAO.ts
index 40baff42a..f4189f592 100644
--- a/app/lib/dal/indexDAL/abstract/TxsDAO.ts
+++ b/app/lib/dal/indexDAL/abstract/TxsDAO.ts
@@ -27,5 +27,7 @@ export interface TxsDAO extends GenericDAO<DBTx> {
 
   removeTX(hash:string): Promise<DBTx|null>
 
+  removeAll(): Promise<void>
+
   sandbox:SandBox<{ issuers: string[], output_base:number, output_amount:number }>
 }
diff --git a/app/lib/dal/indexDAL/loki/LokiBlockchain.ts b/app/lib/dal/indexDAL/loki/LokiBlockchain.ts
index 99ca9e0f4..4fe1109be 100644
--- a/app/lib/dal/indexDAL/loki/LokiBlockchain.ts
+++ b/app/lib/dal/indexDAL/loki/LokiBlockchain.ts
@@ -55,7 +55,6 @@ export class LokiBlockchain extends LokiIndex<DBBlock> implements BlockchainDAO
   }
 
   async insert(record: DBBlock): Promise<void> {
-    this.current = record
     return super.insert(record);
   }
 
@@ -63,6 +62,26 @@ export class LokiBlockchain extends LokiIndex<DBBlock> implements BlockchainDAO
     // Never remove blocks
   }
 
+  async removeForkBlock(number:number): Promise<void> {
+    await this.collection
+      .chain()
+      .find({
+        fork: true,
+        number
+      })
+      .remove()
+  }
+
+  async removeForkBlockAboveOrEqual(number:number): Promise<void> {
+    await this.collection
+      .chain()
+      .find({
+        fork: true,
+        number: { $gte: number }
+      })
+      .remove()
+  }
+
   async getAbsoluteBlock(number: number, hash: string): Promise<DBBlock | null> {
     return this.collection
       .chain()
@@ -113,7 +132,7 @@ export class LokiBlockchain extends LokiIndex<DBBlock> implements BlockchainDAO
       .find({
         fork: true,
         number: { $between: [numberStart, maxNumber] },
-        medianTime: { $gt: medianTimeStart }
+        medianTime: { $gte: medianTimeStart }
       })
       .simplesort('number')
       .data()
@@ -130,18 +149,46 @@ export class LokiBlockchain extends LokiIndex<DBBlock> implements BlockchainDAO
       .data()[0]
   }
 
+  async lastBlockWithDividend(): Promise<DBBlock | null> {
+    return this.collection
+      .chain()
+      .find({
+        fork: false,
+        dividend: { $gt: 0 }
+      })
+      .simplesort('number', true)
+      .data()[0]
+  }
+
   async saveBlock(block: DBBlock): Promise<DBBlock> {
-    block.fork = false
-    await this.insert(block)
     if (!this.current || this.current.number < block.number) {
-      this.current = block
+      this.current = block;
     }
-    return block
+    return this.insertOrUpdate(block, false)
   }
 
   async saveSideBlock(block: DBBlock): Promise<DBBlock> {
-    block.fork = true
-    await this.insert(block)
+    return this.insertOrUpdate(block, true)
+  }
+
+  async insertOrUpdate(block: DBBlock, isFork:boolean): Promise<DBBlock> {
+    block.fork = isFork
+    const conditions = { number: block.number, hash: block.hash }
+    const existing = (await this.findRaw(conditions))[0]
+    if (existing && existing.fork !== isFork) {
+      // Existing block: we only allow to change the fork flag
+      this.collection
+        .chain()
+        .find(conditions)
+        .update(b => {
+          b.fork = isFork
+          b.monetaryMass = block.monetaryMass
+          b.dividend = block.dividend
+        })
+    }
+    else if (!existing) {
+      await this.insert(block)
+    }
     return block
   }
 
@@ -164,6 +211,14 @@ export class LokiBlockchain extends LokiIndex<DBBlock> implements BlockchainDAO
       .update((b:DBBlock) => {
         b.fork = true
       })
+    // Also update the cache if concerned
+    if (this.current && this.current.number === number) {
+      if (previousBlock && this.current.previousHash === previousBlock.hash) {
+        this.current = previousBlock
+      } else {
+        this.current = null
+      }
+    }
   }
 
 }
diff --git a/app/lib/dal/indexDAL/loki/LokiIndex.ts b/app/lib/dal/indexDAL/loki/LokiIndex.ts
index 2c6d45cf0..8e978153a 100644
--- a/app/lib/dal/indexDAL/loki/LokiIndex.ts
+++ b/app/lib/dal/indexDAL/loki/LokiIndex.ts
@@ -1,8 +1,4 @@
 import {LokiCollection} from "./LokiTypes"
-import {IindexEntry} from "../../../indexer"
-import {CIndexDAO} from "../abstract/CIndexDAO"
-import {ReduceableDAO} from "../abstract/ReduceableDAO"
-import {Initiable} from "../../sqliteDAL/Initiable"
 import {GenericDAO} from "../abstract/GenericDAO"
 import {NewLogger} from "../../../logger"
 import {LokiProxyCollection} from "./LokiCollection"
@@ -29,7 +25,10 @@ export abstract class LokiIndex<T extends IndexData> implements GenericDAO<T> {
   }
 
   public triggerInit() {
-    const coll = this.loki.addCollection(this.collectionName, { indices: this.indices })
+    const coll = this.loki.addCollection(this.collectionName, {
+      indices: this.indices,
+      disableChangesApi: false
+    })
     this.collection = new LokiProxyCollection(coll, this.collectionName)
     this.resolveCollection()
   }
diff --git a/app/lib/dal/indexDAL/loki/LokiTransactions.ts b/app/lib/dal/indexDAL/loki/LokiTransactions.ts
index dc8997aef..1987b4a71 100644
--- a/app/lib/dal/indexDAL/loki/LokiTransactions.ts
+++ b/app/lib/dal/indexDAL/loki/LokiTransactions.ts
@@ -1,7 +1,4 @@
 import {LokiIndex} from "./LokiIndex"
-import {NewLogger} from "../../../logger"
-import {BlockchainDAO} from "../abstract/BlockchainDAO"
-import {DBBlock} from "../../../db/DBBlock"
 import {DBTx} from "../../sqliteDAL/TxsDAL"
 import {TxsDAO} from "../abstract/TxsDAO"
 import {SandBox} from "../../sqliteDAL/SandBox"
@@ -53,7 +50,7 @@ export class LokiTransactions extends LokiIndex<DBTx> implements TxsDAO {
     dbTx.written = true
     dbTx.removed = false
     dbTx.hash = tx.getHash()
-    await this.insert(dbTx)
+    await this.insertOrUpdate(dbTx)
     return dbTx
   }
 
@@ -61,7 +58,30 @@ export class LokiTransactions extends LokiIndex<DBTx> implements TxsDAO {
     dbTx.received = moment().unix()
     dbTx.written = false
     dbTx.removed = false
-    await this.insert(dbTx)
+    await this.insertOrUpdate(dbTx)
+    return dbTx
+  }
+
+  async insertOrUpdate(dbTx: DBTx): Promise<DBTx> {
+    const conditions = { hash: dbTx.hash }
+    const existing = (await this.findRaw(conditions))[0]
+    if (existing) {
+      // Existing block: we only allow to change the fork flag
+      this.collection
+        .chain()
+        .find(conditions)
+        .update(tx => {
+          tx.block_number = dbTx.block_number
+          tx.time = dbTx.time
+          tx.received = dbTx.received
+          tx.written = dbTx.written
+          tx.removed = dbTx.removed
+          tx.hash = dbTx.hash
+        })
+    }
+    else if (!existing) {
+      await this.insert(dbTx)
+    }
     return dbTx
   }
 
@@ -112,14 +132,24 @@ export class LokiTransactions extends LokiIndex<DBTx> implements TxsDAO {
   }
 
   async removeTX(hash: string): Promise<DBTx | null> {
-    const tx = (await this.findRaw({
-      hash: hash
-    }))[0]
-    if (tx) {
-      tx.removed = true;
-      await this.insert(tx)
-    }
-    return tx
+    let txRemoved = null
+    await this.collection
+      .chain()
+      .find({
+        hash: hash
+      })
+      .update(tx => {
+        tx.removed = true
+        txRemoved = tx
+      })
+    return txRemoved
+  }
+
+  async removeAll(): Promise<void> {
+    await this.collection
+      .chain()
+      .find({})
+      .remove()
   }
 
   async trimExpiredNonWrittenTxs(limitTime: number): Promise<void> {
diff --git a/app/lib/db/DBBlock.ts b/app/lib/db/DBBlock.ts
index abcd55421..05e54ba8f 100644
--- a/app/lib/db/DBBlock.ts
+++ b/app/lib/db/DBBlock.ts
@@ -68,7 +68,7 @@ export class DBBlock {
     dbb.previousHash = b.previousHash
     dbb.issuer = b.issuer
     dbb.previousIssuer = b.previousIssuer
-    dbb.dividend = b.dividend
+    dbb.dividend = (b.dividend === null || b.dividend === undefined ? b.dividend : parseInt(String(b.dividend)))
     dbb.time = b.time
     dbb.powMin = b.powMin
     dbb.unitbase = b.unitbase
diff --git a/app/lib/other_constants.ts b/app/lib/other_constants.ts
index 6fc9b81f4..d10528e99 100644
--- a/app/lib/other_constants.ts
+++ b/app/lib/other_constants.ts
@@ -14,7 +14,7 @@
 export const OtherConstants = {
 
   MUTE_LOGS_DURING_UNIT_TESTS: false,
-  SQL_TRACES: true,
+  SQL_TRACES: false,
 
   BC_EVENT: {
     SWITCHED: 'switched',
diff --git a/app/lib/system/directory.ts b/app/lib/system/directory.ts
index 0031143d0..931d98a9b 100644
--- a/app/lib/system/directory.ts
+++ b/app/lib/system/directory.ts
@@ -80,7 +80,7 @@ export const RealFS = (): FileSystem => {
   return new QioFileSystem(qfs)
 }
 
-export const MockFS = (initialTree:{ [folder:string]: { [file:string]: string }} = {}): FileSystem => {
+export const MemFS = (initialTree:{ [folder:string]: { [file:string]: string }} = {}): FileSystem => {
   return new QioFileSystem(require('q-io/fs-mock')(initialTree))
 }
 
@@ -99,7 +99,7 @@ export const Directory = {
     const home = theHome || Directory.getHome()
     const params = {
       home: home,
-      fs: isMemory ? MockFS() : RealFS()
+      fs: isMemory ? MemFS() : RealFS()
     }
     if (makeTree) {
       await params.fs.fsMakeDirectory(home)
diff --git a/app/modules/config.ts b/app/modules/config.ts
index e37bd9258..88556edb7 100644
--- a/app/modules/config.ts
+++ b/app/modules/config.ts
@@ -15,6 +15,9 @@
 import {ConfDTO} from "../lib/dto/ConfDTO"
 import {Server} from "../../server"
 import {CommonConstants} from "../lib/common-libs/constants"
+import {Directory} from "../lib/system/directory"
+
+const _ = require('underscore')
 
 module.exports = {
   duniter: {
@@ -35,6 +38,98 @@ module.exports = {
       desc: 'Register configuration in database',
       // The command does nothing particular, it just stops the process right after configuration phase is over
       onConfiguredExecute: (server:Server, conf:ConfDTO) => Promise.resolve(conf)
+    }, {
+      name: 'parse-logs',
+      desc: 'Extract data from logs.',
+      logs: true,
+      onConfiguredExecute: async (server:Server, conf:ConfDTO) => {
+        const fs = await Directory.getHomeFS(false, Directory.INSTANCE_HOME, false)
+        const lines = (await fs.fs.fsReadFile(Directory.INSTANCE_HOMELOG_FILE)).split('\n')
+        const aggregates = _.uniq(
+          lines
+          .map(l => l.match(/: (\[\w+\](\[\w+\])*)/))
+          .filter(l => l)
+          .map((l:string[]) => l[1])
+        )
+        console.log(aggregates)
+        const results = aggregates.map((a:string) => {
+          return {
+            name: a,
+            time: lines
+              .filter(l => l.match(new RegExp(a
+                .replace(/\[/g, '\\[')
+                .replace(/\]/g, '\\]')
+              )))
+              .map(l => {
+                const m = l.match(/ (\d+)(\.\d+)?(ms|µs)( \d+)?$/)
+                if (!m) {
+                  throw Error('Wrong match')
+                }
+                return m
+              })
+              .map(match => {
+                return {
+                  qty: parseInt(match[1]),
+                  unit: match[3],
+                }
+              })
+              .reduce((sumMicroSeconds, entry) => {
+                return sumMicroSeconds + (entry.qty * (entry.unit === 'ms' ? 1000 : 1))
+              }, 0) / 1000000
+          }
+        })
+        const root:Tree = {
+          name: 'root',
+          leaves: {}
+        }
+        for (const r of results) {
+          recursiveReduce(root, r.name, r.time)
+        }
+        recursiveDump(root)
+      }
     }]
   }
 }
+
+interface Leaf {
+  name:string
+  value:number
+}
+
+interface Tree {
+  name:string
+  leaves: { [k:string]: Tree|Leaf }
+}
+
+function recursiveReduce(tree:Tree, path:string, duration:number) {
+  if (path.match(/\]\[/)) {
+    const m = (path.match(/^(\[\w+\])(\[.+)/) as string[])
+    const key = m[1]
+    if (!tree.leaves[key]) {
+      tree.leaves[key] = {
+        name: key,
+        leaves: {}
+      }
+    }
+    recursiveReduce(tree.leaves[key] as Tree, m[2], duration)
+  } else {
+    tree.leaves[path] = {
+      name: path,
+      value: duration
+    }
+  }
+}
+
+function recursiveDump(tree:Tree, level = -1) {
+  if (level >= 0) {
+    console.log("  ".repeat(level), tree.name)
+  }
+  for (const k of Object.keys(tree.leaves)) {
+    const element = tree.leaves[k]
+    if ((<Tree>element).leaves) {
+      recursiveDump(<Tree>element, level + 1)
+    } else {
+      console.log("  ".repeat(level + 1), (<Leaf>element).name, (<Leaf>element).value + 's')
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/service/BlockchainService.ts b/app/service/BlockchainService.ts
index 4a4f47264..c9d5a7219 100644
--- a/app/service/BlockchainService.ts
+++ b/app/service/BlockchainService.ts
@@ -162,6 +162,10 @@ export class BlockchainService extends FIFOService {
     return this.mainContext.checkBlock(dto, withPoWAndSignature)
   }
 
+  /**
+   * Return the potential HEADs we could fork to (necessarily above us, since we don't fork on older branches).
+   * @returns {Promise<any>}
+   */
   async branches() {
     const current = await this.current()
     if (!current) {
@@ -193,7 +197,7 @@ export class BlockchainService extends FIFOService {
           throw CommonConstants.ERRORS.OUT_OF_FORK_WINDOW
         }
       }
-      const absolute = await this.dal.getAbsoluteBlockByNumberAndHash(obj.number, obj.hash)
+      const absolute = await this.dal.getAbsoluteBlockByNumberAndHash(parseInt(obj.number), obj.hash)
       if (!absolute) {
         // Save the block in the sandbox
         await this.mainContext.addSideBlock(dto);
@@ -207,6 +211,7 @@ export class BlockchainService extends FIFOService {
               await this.blockResolution()
               // Resolve the potential forks
               await this.forkResolution()
+              console.log(dto)
               const current = await this.current()
               this.push({
                 bcEvent: OtherConstants.BC_EVENT.RESOLUTION_DONE,
diff --git a/bin/duniter b/bin/duniter
index 60946d8e6..806c1602e 100755
--- a/bin/duniter
+++ b/bin/duniter
@@ -13,7 +13,7 @@ process.on('uncaughtException', (err) => {
   }
 });
 
-return co(function*() {
+return co(function* () {
 
   try {
     const duniter = require('../index');
diff --git a/server.ts b/server.ts
index cd32a0172..04137cba2 100644
--- a/server.ts
+++ b/server.ts
@@ -337,6 +337,8 @@ export class Server extends stream.Duplex implements HookableServer {
         await this.revert();
       }
     }
+    // Database trimming
+    await this.dal.loki.flushAndTrimData()
     // Eventual block resolution
     await this.BlockchainService.blockResolution()
     // Eventual fork resolution
diff --git a/test/dal/loki.ts b/test/dal/loki.ts
index 58a857de8..ebe6b3ccc 100644
--- a/test/dal/loki.ts
+++ b/test/dal/loki.ts
@@ -31,10 +31,10 @@ describe("Loki data layer", () => {
   })
 
   it('should be able to commit data', async () => {
-    const coll = driver.getLokiInstance().addCollection('block')
+    const coll = driver.getLokiInstance().addCollection('block', { disableChangesApi: false })
     coll.insert({ a: 1 })
     coll.insert({ b: 2 })
-    await driver.commitData()
+    await driver.flushAndTrimData()
   })
 
   it('should be able restart the DB and read the data', async () => {
@@ -45,6 +45,42 @@ describe("Loki data layer", () => {
     assert.equal(coll.find().length, 2)
   })
 
+  it('should be able to add few changes data', async () => {
+    const driver2 = new LokiJsDriver(dbPath)
+    await driver2.loadDatabase()
+    const coll = driver2.getLokiInstance().getCollection('block')
+    coll.insert({ c: 3 })
+    coll.chain().find({ c: 3 }).update((o:any) => o.c = 4)
+    coll.chain().find({ a: 1 }).remove()
+    const changesCount1 = await driver2.commitData()
+    assert.equal(changesCount1, 3)
+    const changesCount2 = await driver2.commitData()
+    assert.equal(changesCount2, 0)
+  })
+
+  it('should be able restart the DB and read the commited data', async () => {
+    const driver2 = new LokiJsDriver(dbPath)
+    await driver2.loadDatabase()
+    const coll = driver2.getLokiInstance().getCollection('block')
+    assert.equal(coll.find().length, 2)
+    assert.equal(coll.find({ a: 1 }).length, 0)
+    assert.equal(coll.find({ b: 2 }).length, 1)
+    assert.equal(coll.find({ c: 4 }).length, 1)
+  })
+
+  it('should be able to trim then restart the DB and read the commited data', async () => {
+    const driverTrim = new LokiJsDriver(dbPath)
+    await driverTrim.loadDatabase()
+    await driverTrim.flushAndTrimData()
+    const driver2 = new LokiJsDriver(dbPath)
+    await driver2.loadDatabase()
+    const coll = driver2.getLokiInstance().getCollection('block')
+    assert.equal(coll.find().length, 2)
+    assert.equal(coll.find({ a: 1 }).length, 0)
+    assert.equal(coll.find({ b: 2 }).length, 1)
+    assert.equal(coll.find({ c: 4 }).length, 1)
+  })
+
   it('should not see any data if commit file is absent', async () => {
     const rfs = RealFS()
     await rfs.fsUnlink(path.join(dbPath, 'commit.json'))
@@ -70,7 +106,7 @@ describe("Loki data layer", () => {
     const coll = driver4.getLokiInstance().addCollection('block')
     coll.insert({ a: 1 })
     coll.insert({ b: 2 })
-    await driver.commitData()
+    await driver.flushAndTrimData()
     const oldCommit:DBCommit = JSON.parse(await rfs.fsReadFile(path.join(dbPath, 'commit.json')))
     oldCommit.collections['block'] = 'wrong-file.json'
     const driver5 = new LokiJsDriver(dbPath)
diff --git a/test/dal/source_dal.js b/test/dal/source_dal.js
index 70ad9b194..0e618485a 100644
--- a/test/dal/source_dal.js
+++ b/test/dal/source_dal.js
@@ -45,7 +45,7 @@ describe("Source DAL", function(){
     source1.should.have.property('consumed').equal(true);
     const udSources = yield dal.sindexDAL.getUDSources('ABC');
     udSources.should.have.length(2);
-    udSources[0].should.have.property('consumed').equal(true);
-    udSources[1].should.have.property('consumed').equal(false);
+    udSources[0].should.have.property('consumed').equal(false);
+    udSources[1].should.have.property('consumed').equal(true);
   }));
 });
diff --git a/test/fast/cfs.js b/test/fast/cfs.js
index 8dbdfa9bb..518c4a11a 100644
--- a/test/fast/cfs.js
+++ b/test/fast/cfs.js
@@ -17,7 +17,7 @@ var assert = require('assert');
 var should = require('should');
 var co = require('co');
 var CFSCore = require('../../app/lib/dal/fileDALs/CFSCore').CFSCore;
-const mockFS = require('../../app/lib/system/directory').MockFS({
+const mockFS = require('../../app/lib/system/directory').MemFS({
   'B5_a': {
     "A.json": '{ "text": "Content of A from B5_a" }'
   },
diff --git a/test/fast/dal/basic-loki.ts b/test/fast/dal/basic-loki.ts
index 06b95f07c..d95f0b1a3 100644
--- a/test/fast/dal/basic-loki.ts
+++ b/test/fast/dal/basic-loki.ts
@@ -29,8 +29,10 @@ class TheIndex extends LokiIndex<TestEntity> {
 
 describe("Basic LokiJS database", () => {
 
-  before(() => {
+  before(async () => {
     lokiIndex = new TheIndex(new loki('index.db'), 'iindex', [])
+    await lokiIndex.triggerInit()
+    await lokiIndex.init()
   })
 
   it('should be able instanciate the index', async () => {
diff --git a/test/fast/dal/iindex-loki.ts b/test/fast/dal/iindex-loki.ts
index ea37b835b..107f32193 100644
--- a/test/fast/dal/iindex-loki.ts
+++ b/test/fast/dal/iindex-loki.ts
@@ -12,7 +12,6 @@
 // GNU Affero General Public License for more details.
 
 import * as assert from "assert"
-import {LokiIndex} from "../../../app/lib/dal/indexDAL/loki/LokiIndex"
 import {LokiIIndex} from "../../../app/lib/dal/indexDAL/loki/LokiIIndex"
 
 const loki = require('lokijs')
@@ -21,8 +20,10 @@ let lokiIndex:LokiIIndex
 
 describe("IIndex LokiJS", () => {
 
-  before(() => {
+  before(async () => {
     lokiIndex = new LokiIIndex(new loki('index.db'))
+    await lokiIndex.triggerInit()
+    await lokiIndex.init()
   })
 
   it('should be able instanciate the index', async () => {
diff --git a/test/integration/branches2.ts b/test/integration/branches2.ts
index 2000dacc6..5ca0a684c 100644
--- a/test/integration/branches2.ts
+++ b/test/integration/branches2.ts
@@ -115,7 +115,7 @@ describe("SelfFork", function() {
     yield waitToHaveBlock(s2, 2)
     let s2p = yield s2.PeeringService.peer();
 
-    yield commitS2();
+    yield commitS2(); // <-- block#3 is a fork block, S2 is committing another one than S1 issued
     yield commitS2();
     yield commitS2();
     yield commitS2();
@@ -223,7 +223,7 @@ describe("SelfFork", function() {
     });
 
     it('should have 2 branch', async () => {
-      const branches:any[] = await s1.BlockchainService.branches()
+      const branches = await s1.BlockchainService.branches()
       branches.should.have.length(1)
     })
   });
diff --git a/test/integration/branches_revert2.js b/test/integration/branches_revert2.js
index 93295937b..cb268cc91 100644
--- a/test/integration/branches_revert2.js
+++ b/test/integration/branches_revert2.js
@@ -196,9 +196,9 @@ describe("Revert two blocks", function() {
   describe("commit again (but send less, to check that the account is not cleaned this time)", () => {
 
     before(() => co(function*() {
-      yield s1.dal.txsDAL.sqlDeleteAll()
+      yield s1.dal.txsDAL.removeAll()
       yield cat.sendP(19, toc);
-      yield s1.dal.blockDAL.exec('DELETE FROM block WHERE fork AND number = 3')
+      yield s1.dal.blockDAL.removeBlock('DELETE FROM block WHERE fork AND number = 3')
       yield commit(s1)({ time: now + 1 });
     }))
 
diff --git a/test/integration/branches_revert_balance.js b/test/integration/branches_revert_balance.js
index b586b053e..b32d329a3 100644
--- a/test/integration/branches_revert_balance.js
+++ b/test/integration/branches_revert_balance.js
@@ -75,7 +75,7 @@ describe("Revert balance", () => {
 
   it('cat should be able to RE-send 60 units to tac', () => co(function*() {
     const txsPending = yield s1.dal.txsDAL.getAllPending(1)
-    yield s1.dal.blockDAL.exec('DELETE FROM block WHERE fork AND number = 3')
+    yield s1.dal.blockDAL.removeForkBlock(3)
     txsPending.should.have.length(1)
     yield s1.commit({ time: now + 1 })
     yield s1.expect('/tx/sources/' + cat.pub, (res) => {
diff --git a/test/integration/register-fork-blocks.js b/test/integration/register-fork-blocks.js
index b31bbe2d7..7e1c803dd 100644
--- a/test/integration/register-fork-blocks.js
+++ b/test/integration/register-fork-blocks.js
@@ -196,8 +196,10 @@ describe("Fork blocks", function() {
     yield s2.writeBlock(b6a)
     yield s2.writeBlock(b7a)
     yield s2.writeBlock(b8a)
-    yield s2.waitToHaveBlock(8)
-    yield s2.waitForkResolution(8)
+    yield Promise.all([
+      s2.waitToHaveBlock(8),
+      s2.waitForkResolution(8)
+    ])
   }))
 
   it('should exist a same current block on each node', () => co(function*() {
diff --git a/test/integration/revocation-test.js b/test/integration/revocation-test.js
index 0dad54510..c0a62ae78 100644
--- a/test/integration/revocation-test.js
+++ b/test/integration/revocation-test.js
@@ -198,7 +198,7 @@ describe("Revocation", function() {
   it('if we revert the commit, cat should not be revoked', () => co(function *() {
     yield s1.revert();
     yield s1.revert();
-    yield s1.dal.blockDAL.exec('DELETE FROM block WHERE fork AND number >= 2')
+    yield s1.dal.blockDAL.removeForkBlockAboveOrEqual(2)
     return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res) {
       res.should.have.property('results').length(1);
       res.results[0].should.have.property('uids').length(1);
diff --git a/test/integration/v1.1-dividend.js b/test/integration/v1.1-dividend.js
index c3579929c..475593909 100644
--- a/test/integration/v1.1-dividend.js
+++ b/test/integration/v1.1-dividend.js
@@ -95,14 +95,14 @@ describe("Protocol 1.1 Dividend", function() {
       res.sources[1].should.have.property('amount').equal(100);
       res.sources[2].should.have.property('amount').equal(101);
       res.sources[3].should.have.property('amount').equal(103);
-      res.sources[4].should.have.property('amount').equal(105);
-      res.sources[5].should.have.property('amount').equal(106);
+      res.sources[4].should.have.property('amount').equal(106);
+      res.sources[5].should.have.property('amount').equal(105);
       res.sources[0].should.have.property('type').equal('D');
       res.sources[1].should.have.property('type').equal('D');
       res.sources[2].should.have.property('type').equal('D');
       res.sources[3].should.have.property('type').equal('D');
-      res.sources[4].should.have.property('type').equal('T');
-      res.sources[5].should.have.property('type').equal('D');
+      res.sources[4].should.have.property('type').equal('D');
+      res.sources[5].should.have.property('type').equal('T');
     })
   }));
 
-- 
GitLab