From 07505ece506d1096a274940c6562ae6f9b07aca9 Mon Sep 17 00:00:00 2001 From: cgeek <cem.moreau@gmail.com> Date: Sat, 15 Jul 2017 19:39:53 +0200 Subject: [PATCH] [enh] #1037 Migrate SQLite DALs --- .eslintignore | 2 + .eslintrc | 2 +- .gitignore | 4 +- app/lib/blockchain/SqlIndex.ts | 10 +- app/lib/computation/QuickSync.ts | 4 +- app/lib/dal/fileDAL.js | 36 +-- app/lib/dal/sqliteDAL/AbstractIndex.js | 38 --- app/lib/dal/sqliteDAL/AbstractIndex.ts | 48 +++ app/lib/dal/sqliteDAL/AbstractSQLite.js | 290 ----------------- app/lib/dal/sqliteDAL/AbstractSQLite.ts | 291 ++++++++++++++++++ app/lib/dal/sqliteDAL/BlockDAL.js | 158 ---------- app/lib/dal/sqliteDAL/BlockDAL.ts | 193 ++++++++++++ app/lib/dal/sqliteDAL/CertDAL.js | 113 ------- app/lib/dal/sqliteDAL/CertDAL.ts | 144 +++++++++ app/lib/dal/sqliteDAL/IdentityDAL.js | 146 --------- app/lib/dal/sqliteDAL/IdentityDAL.ts | 192 ++++++++++++ app/lib/dal/sqliteDAL/IndexDAL.js | 10 - app/lib/dal/sqliteDAL/MembershipDAL.js | 116 ------- app/lib/dal/sqliteDAL/MembershipDAL.ts | 154 +++++++++ .../dal/sqliteDAL/{MetaDAL.js => MetaDAL.ts} | 248 ++++++++------- app/lib/dal/sqliteDAL/PeerDAL.js | 66 ---- app/lib/dal/sqliteDAL/PeerDAL.ts | 89 ++++++ app/lib/dal/sqliteDAL/SandBox.js | 29 -- app/lib/dal/sqliteDAL/SandBox.ts | 30 ++ app/lib/dal/sqliteDAL/TxsDAL.js | 187 ----------- app/lib/dal/sqliteDAL/TxsDAL.ts | 257 ++++++++++++++++ app/lib/dal/sqliteDAL/WalletDAL.js | 47 --- app/lib/dal/sqliteDAL/WalletDAL.ts | 56 ++++ app/lib/dal/sqliteDAL/index/BIndexDAL.js | 113 ------- app/lib/dal/sqliteDAL/index/BIndexDAL.ts | 117 +++++++ app/lib/dal/sqliteDAL/index/CIndexDAL.js | 129 -------- app/lib/dal/sqliteDAL/index/CIndexDAL.ts | 138 +++++++++ app/lib/dal/sqliteDAL/index/IIndexDAL.js | 145 --------- app/lib/dal/sqliteDAL/index/IIndexDAL.ts | 175 +++++++++++ app/lib/dal/sqliteDAL/index/MIndexDAL.js | 72 ----- app/lib/dal/sqliteDAL/index/MIndexDAL.ts | 73 +++++ app/lib/dal/sqliteDAL/index/SIndexDAL.js | 128 -------- app/lib/dal/sqliteDAL/index/SIndexDAL.ts | 129 ++++++++ app/lib/db/DBTransaction.ts | 1 + app/lib/dto/ConfDTO.ts | 4 + app/lib/dto/TransactionDTO.ts | 37 ++- app/lib/indexer.ts | 12 +- test/blockchain/basic-blockchain.ts | 9 +- test/blockchain/misc-sql-blockchain.ts | 15 +- 44 files changed, 2296 insertions(+), 1961 deletions(-) delete mode 100644 app/lib/dal/sqliteDAL/AbstractIndex.js create mode 100644 app/lib/dal/sqliteDAL/AbstractIndex.ts delete mode 100644 app/lib/dal/sqliteDAL/AbstractSQLite.js create mode 100644 app/lib/dal/sqliteDAL/AbstractSQLite.ts delete mode 100644 app/lib/dal/sqliteDAL/BlockDAL.js create mode 100644 app/lib/dal/sqliteDAL/BlockDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/CertDAL.js create mode 100644 app/lib/dal/sqliteDAL/CertDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/IdentityDAL.js create mode 100644 app/lib/dal/sqliteDAL/IdentityDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/IndexDAL.js delete mode 100644 app/lib/dal/sqliteDAL/MembershipDAL.js create mode 100644 app/lib/dal/sqliteDAL/MembershipDAL.ts rename app/lib/dal/sqliteDAL/{MetaDAL.js => MetaDAL.ts} (70%) delete mode 100644 app/lib/dal/sqliteDAL/PeerDAL.js create mode 100644 app/lib/dal/sqliteDAL/PeerDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/SandBox.js create mode 100644 app/lib/dal/sqliteDAL/SandBox.ts delete mode 100644 app/lib/dal/sqliteDAL/TxsDAL.js create mode 100644 app/lib/dal/sqliteDAL/TxsDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/WalletDAL.js create mode 100644 app/lib/dal/sqliteDAL/WalletDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/index/BIndexDAL.js create mode 100644 app/lib/dal/sqliteDAL/index/BIndexDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/index/CIndexDAL.js create mode 100644 app/lib/dal/sqliteDAL/index/CIndexDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/index/IIndexDAL.js create mode 100644 app/lib/dal/sqliteDAL/index/IIndexDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/index/MIndexDAL.js create mode 100644 app/lib/dal/sqliteDAL/index/MIndexDAL.ts delete mode 100644 app/lib/dal/sqliteDAL/index/SIndexDAL.js create mode 100644 app/lib/dal/sqliteDAL/index/SIndexDAL.ts diff --git a/.eslintignore b/.eslintignore index c9abbdbe3..bd0ff0f1e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,5 +6,7 @@ app/lib/dto/*.js app/lib/indexer.js app/lib/common.js app/lib/dal/drivers/*.js +app/lib/dal/sqliteDAL/*.js +app/lib/dal/sqliteDAL/index/*.js test/blockchain/*.js test/blockchain/lib/*.js \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 06403faf2..1fe67b099 100644 --- a/.eslintrc +++ b/.eslintrc @@ -35,7 +35,7 @@ "comma-dangle": [1], "eol-last": [1], "no-shadow": [1], - "no-unused-vars": ["warning", { "varsIgnorePattern": "should"}], + "no-unused-vars": [1, { "varsIgnorePattern": "should"}], "space-infix-ops": [1], "handle-callback-err": [1], "no-extra-semi": [1] diff --git a/.gitignore b/.gitignore index 19d6ac634..cc5097572 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,6 @@ app/lib/common.js* app/lib/db/*.js* app/lib/dto/*.js* app/lib/indexer.js* -app/lib/dal/drivers/*.js* \ No newline at end of file +app/lib/dal/drivers/*.js* +app/lib/dal/sqliteDAL/*.js* +app/lib/dal/sqliteDAL/index/*.js* \ No newline at end of file diff --git a/app/lib/blockchain/SqlIndex.ts b/app/lib/blockchain/SqlIndex.ts index 3ab081d5d..60ca27de5 100644 --- a/app/lib/blockchain/SqlIndex.ts +++ b/app/lib/blockchain/SqlIndex.ts @@ -1,7 +1,7 @@ "use strict" import {IndexOperator} from "./interfaces/IndexOperator" +import {AbstractIndex} from "../dal/sqliteDAL/AbstractIndex"; -const IndexDAL = require('../dal/sqliteDAL/IndexDAL') const _ = require('underscore') export class SQLIndex implements IndexOperator { @@ -19,18 +19,14 @@ export class SQLIndex implements IndexOperator { this.indexes[k] = this.definitions[k].handler } else { // Internal table: managed here - const indexTable = new IndexDAL(this.db); - const pk = pkFields[k].pk - indexTable.table = k - indexTable.fields = this.definitions[k].fields - indexTable.booleans = this.definitions[k].booleans + const indexTable = new AbstractIndex<any>(this.db, k, [], this.definitions[k].fields, [], this.definitions[k].booleans) this.indexes[k] = indexTable indexTable.init = () => { return indexTable.exec('BEGIN;' + 'CREATE TABLE IF NOT EXISTS ' + indexTable.table + ' (' + this.definitions[k].sqlFields.join(',') + ');' + - 'COMMIT;', []) + 'COMMIT;') } await indexTable.init() } diff --git a/app/lib/computation/QuickSync.ts b/app/lib/computation/QuickSync.ts index 261878e4f..3406ff3e7 100644 --- a/app/lib/computation/QuickSync.ts +++ b/app/lib/computation/QuickSync.ts @@ -31,7 +31,7 @@ const sync_memoryDAL = { } }, sindexDAL: { - getAvailableForConditions: null + getAvailableForConditions: (conditions:string) => null } } @@ -90,7 +90,7 @@ export class QuickSynchronizer { async quickApplyBlocks(blocks:BlockDTO[], to: number | null): Promise<void> { - sync_memoryDAL.sindexDAL = { getAvailableForConditions: this.dal.sindexDAL.getAvailableForConditions } + sync_memoryDAL.sindexDAL = { getAvailableForConditions: (conditions:string) => this.dal.sindexDAL.getAvailableForConditions(conditions) } let blocksToSave: BlockDTO[] = []; for (const block of blocks) { diff --git a/app/lib/dal/fileDAL.js b/app/lib/dal/fileDAL.js index ad57c658c..f6fd59cf3 100644 --- a/app/lib/dal/fileDAL.js +++ b/app/lib/dal/fileDAL.js @@ -8,6 +8,7 @@ const logger = require('../logger')('filedal'); const Configuration = require('../entity/configuration'); const Merkle = require('../entity/merkle'); const Transaction = require('../entity/transaction'); +const TransactionDTO = require('../dto/TransactionDTO').TransactionDTO const constants = require('../constants'); const ConfDAL = require('./fileDALs/confDAL'); const StatDAL = require('./fileDALs/statDAL'); @@ -29,20 +30,20 @@ function FileDAL(params) { // DALs this.confDAL = new ConfDAL(rootPath, myFS, null, that, CFSStorage); - this.metaDAL = new (require('./sqliteDAL/MetaDAL'))(sqliteDriver); - this.peerDAL = new (require('./sqliteDAL/PeerDAL'))(sqliteDriver); - this.blockDAL = new (require('./sqliteDAL/BlockDAL'))(sqliteDriver); - this.txsDAL = new (require('./sqliteDAL/TxsDAL'))(sqliteDriver); + this.metaDAL = new (require('./sqliteDAL/MetaDAL').MetaDAL)(sqliteDriver); + this.peerDAL = new (require('./sqliteDAL/PeerDAL').PeerDAL)(sqliteDriver); + this.blockDAL = new (require('./sqliteDAL/BlockDAL').BlockDAL)(sqliteDriver); + this.txsDAL = new (require('./sqliteDAL/TxsDAL').TxsDAL)(sqliteDriver); this.statDAL = new StatDAL(rootPath, myFS, null, that, CFSStorage); - this.idtyDAL = new (require('./sqliteDAL/IdentityDAL'))(sqliteDriver); - this.certDAL = new (require('./sqliteDAL/CertDAL'))(sqliteDriver); - this.msDAL = new (require('./sqliteDAL/MembershipDAL'))(sqliteDriver); - this.walletDAL = new (require('./sqliteDAL/WalletDAL'))(sqliteDriver); - this.bindexDAL = new (require('./sqliteDAL/index/BIndexDAL'))(sqliteDriver); - this.mindexDAL = new (require('./sqliteDAL/index/MIndexDAL'))(sqliteDriver); - this.iindexDAL = new (require('./sqliteDAL/index/IIndexDAL'))(sqliteDriver); - this.sindexDAL = new (require('./sqliteDAL/index/SIndexDAL'))(sqliteDriver); - this.cindexDAL = new (require('./sqliteDAL/index/CIndexDAL'))(sqliteDriver); + this.idtyDAL = new (require('./sqliteDAL/IdentityDAL').IdentityDAL)(sqliteDriver); + this.certDAL = new (require('./sqliteDAL/CertDAL').CertDAL)(sqliteDriver); + this.msDAL = new (require('./sqliteDAL/MembershipDAL').MembershipDAL)(sqliteDriver); + this.walletDAL = new (require('./sqliteDAL/WalletDAL').WalletDAL)(sqliteDriver); + this.bindexDAL = new (require('./sqliteDAL/index/BIndexDAL').BIndexDAL)(sqliteDriver); + this.mindexDAL = new (require('./sqliteDAL/index/MIndexDAL').MIndexDAL)(sqliteDriver); + this.iindexDAL = new (require('./sqliteDAL/index/IIndexDAL').IIndexDAL)(sqliteDriver); + this.sindexDAL = new (require('./sqliteDAL/index/SIndexDAL').SIndexDAL)(sqliteDriver); + this.cindexDAL = new (require('./sqliteDAL/index/CIndexDAL').CIndexDAL)(sqliteDriver); this.newDals = { 'metaDAL': that.metaDAL, @@ -527,7 +528,7 @@ function FileDAL(params) { block.wrong = false; yield [ that.saveBlockInFile(block), - that.saveTxsInFiles(block.transactions, {block_number: block.number, time: block.medianTime, currency: block.currency }) + that.saveTxsInFiles(block.transactions, block.number, block.medianTime) ]; }); @@ -591,14 +592,13 @@ function FileDAL(params) { this.saveSideBlockInFile = (block) => that.writeSideFileOfBlock(block); - this.saveTxsInFiles = (txs, extraProps) => { + this.saveTxsInFiles = (txs, block_number, medianTime) => { return Q.all(txs.map((tx) => co(function*() { - _.extend(tx, extraProps); const sp = tx.blockstamp.split('-'); tx.blockstampTime = (yield that.getBlockByNumberAndHash(sp[0], sp[1])).medianTime; const txEntity = new Transaction(tx); txEntity.computeAllHashes(); - return that.txsDAL.addLinked(txEntity); + return that.txsDAL.addLinked(TransactionDTO.fromJSONObject(txEntity), block_number, medianTime); }))); }; @@ -628,7 +628,7 @@ function FileDAL(params) { this.registerNewCertification = (cert) => that.certDAL.saveNewCertification(cert); - this.saveTransaction = (tx) => that.txsDAL.addPending(tx); + this.saveTransaction = (tx) => that.txsDAL.addPending(TransactionDTO.fromJSONObject(tx)) this.getTransactionsHistory = (pubkey) => co(function*() { const history = { diff --git a/app/lib/dal/sqliteDAL/AbstractIndex.js b/app/lib/dal/sqliteDAL/AbstractIndex.js deleted file mode 100644 index 1078a8257..000000000 --- a/app/lib/dal/sqliteDAL/AbstractIndex.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const _ = require('underscore'); -const co = require('co'); -const indexer = require('../../indexer').Indexer - -module.exports = AbstractIndex; - -function AbstractIndex() { - - "use strict"; - - const that = this; - - this.getWrittenOn = (blockstamp) => that.query('SELECT * FROM ' + that.table + ' WHERE written_on = ?', [blockstamp]); - - this.trimRecords = (belowNumber) => co(function*() { - const belowRecords = yield that.query('SELECT COUNT(*) as nbRecords, pub FROM ' + that.table + ' ' + - 'WHERE CAST(written_on as int) < ? ' + - 'GROUP BY pub ' + - 'HAVING nbRecords > 1', [belowNumber]); - const reducedByPub = indexer.DUP_HELPERS.reduceBy(belowRecords, ['pub']); - for (const record of reducedByPub) { - const recordsOfPub = yield that.query('SELECT * FROM ' + that.table + ' WHERE pub = ?', [record.pub]); - const toReduce = _.filter(recordsOfPub, (rec) => parseInt(rec.written_on) < belowNumber); - if (toReduce.length) { - // Clean the records in the DB - yield that.exec('DELETE FROM ' + that.table + ' WHERE pub = \'' + record.pub + '\''); - const nonReduced = _.filter(recordsOfPub, (rec) => parseInt(rec.written_on) >= belowNumber); - const reduced = indexer.DUP_HELPERS.reduce(toReduce); - // Persist - yield that.insertBatch([reduced].concat(nonReduced)); - } - } - }); -} diff --git a/app/lib/dal/sqliteDAL/AbstractIndex.ts b/app/lib/dal/sqliteDAL/AbstractIndex.ts new file mode 100644 index 000000000..6c4e073e7 --- /dev/null +++ b/app/lib/dal/sqliteDAL/AbstractIndex.ts @@ -0,0 +1,48 @@ +import {AbstractSQLite, BeforeSaveHook} from "./AbstractSQLite"; +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {IndexEntry, Indexer} from "../../indexer"; + +const _ = require('underscore'); + +export class AbstractIndex<T extends IndexEntry> extends AbstractSQLite<T> { + + constructor( + driver:SQLiteDriver, + table: string, + pkFields: string[] = [], + fields: string[] = [], + arrays: string[] = [], + booleans: string[] = [], + bigintegers: string[] = [], + transientFields: string[] = [], + beforeSaveHook: BeforeSaveHook<T> | null = null + ) { + super(driver, table, pkFields, fields, arrays, booleans, bigintegers, transientFields, beforeSaveHook) + } + + public async init() {} + + getWrittenOn(blockstamp:string) { + return this.query('SELECT * FROM ' + this.table + ' WHERE written_on = ?', [blockstamp]) + } + + async trimRecords(belowNumber:number) { + const belowRecords:T[] = await this.query('SELECT COUNT(*) as nbRecords, pub FROM ' + this.table + ' ' + + 'WHERE CAST(written_on as int) < ? ' + + 'GROUP BY pub ' + + 'HAVING nbRecords > 1', [belowNumber]); + const reducedByPub = Indexer.DUP_HELPERS.reduceBy(belowRecords, ['pub']); + for (const record of reducedByPub) { + const recordsOfPub = await this.query('SELECT * FROM ' + this.table + ' WHERE pub = ?', [record.pub]); + const toReduce = _.filter(recordsOfPub, (rec:T) => parseInt(rec.written_on) < belowNumber); + if (toReduce.length) { + // Clean the records in the DB + await this.exec('DELETE FROM ' + this.table + ' WHERE pub = \'' + record.pub + '\''); + const nonReduced = _.filter(recordsOfPub, (rec:T) => parseInt(rec.written_on) >= belowNumber); + const reduced = Indexer.DUP_HELPERS.reduce(toReduce); + // Persist + await this.insertBatch([reduced].concat(nonReduced)); + } + } + } +} diff --git a/app/lib/dal/sqliteDAL/AbstractSQLite.js b/app/lib/dal/sqliteDAL/AbstractSQLite.js deleted file mode 100644 index 2cdeb954d..000000000 --- a/app/lib/dal/sqliteDAL/AbstractSQLite.js +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const _ = require('underscore'); -const co = require('co'); -const colors = require('colors'); -const logger = require('../../logger')('sqlite'); - -module.exports = AbstractSQLite; - -function AbstractSQLite(driver) { - - "use strict"; - - const that = this; - - this.ASC = false; - this.DESC = true; - this.arrays = []; - this.booleans = []; - this.bigintegers = []; - this.translated = {}; - - this.query = (sql, params) => co(function *() { - try { - //logger.trace(sql, JSON.stringify(params || [])); - const start = new Date(); - const res = yield driver.executeAll(sql, params || []); - const duration = (new Date()) - start; - const entities = res.map(toEntity); - // Display result - let msg = sql + ' | %s\t==> %s rows in %s ms'; - if (duration <= 2) { - msg = colors.green(msg); - } else if(duration <= 5) { - msg = colors.yellow(msg); - } else if (duration <= 10) { - msg = colors.magenta(msg); - } else if (duration <= 100) { - msg = colors.red(msg); - } - logger.query(msg, JSON.stringify(params || []), entities.length, duration); - return entities; - } catch (e) { - logger.error('ERROR >> %s', sql, JSON.stringify(params || []), e.stack || e.message || e); - throw e; - } - }); - - this.cleanData = () => this.query("DELETE FROM " + this.table); - - this.sqlListAll = () => this.query("SELECT * FROM " + this.table); - - this.sqlDeleteAll = () => this.exec("DELETE FROM " + this.table); - - this.sqlFind = (obj, sortObj) => co(function *() { - const conditions = toConditionsArray(obj).join(' and '); - const values = toParams(obj); - const sortKeys = _.keys(sortObj); - const sort = sortKeys.length ? ' ORDER BY ' + sortKeys.map((k) => "`" + k + "` " + (sortObj[k] ? 'DESC' : 'ASC')).join(',') : ''; - return that.query('SELECT * FROM ' + that.table + ' WHERE ' + conditions + sort, values); - }); - - this.sqlFindOne = (obj, sortObj) => co(function *() { - const res = yield that.sqlFind(obj, sortObj); - return res[0]; - }); - - this.sqlFindLikeAny = (obj, sort) => co(function *() { - const keys = _.keys(obj); - return that.query('SELECT * FROM ' + that.table + ' WHERE ' + keys.map((k) => 'UPPER(`' + k + '`) like ?').join(' or '), keys.map((k) => obj[k].toUpperCase()), sort); - }); - - this.sqlRemoveWhere = (obj) => co(function *() { - const keys = _.keys(obj); - return that.query('DELETE FROM ' + that.table + ' WHERE ' + keys.map((k) => '`' + k + '` = ?').join(' and '), keys.map((k) => obj[k])); - }); - - this.sqlExisting = (entity) => that.getEntity(entity); - - this.saveEntity = (entity) => co(function *() { - let toSave = entity; - if (that.beforeSaveHook) { - that.beforeSaveHook(toSave); - } - const existing = yield that.getEntity(toSave); - if (existing) { - toSave = toRow(toSave); - const valorizations = that.fields.map((field) => '`' + field + '` = ?').join(', '); - const conditions = getPKFields().map((field) => '`' + field + '` = ?').join(' and '); - const setValues = that.fields.map((field) => toSave[field]); - const condValues = getPKFields().map((k) => toSave[k]); - return that.query('UPDATE ' + that.table + ' SET ' + valorizations + ' WHERE ' + conditions, setValues.concat(condValues)); - } - yield that.insert(toSave); - }); - - this.insert = (entity) => co(function *() { - const row = toRow(entity); - const values = that.fields.map((f) => row[f]); - yield that.query(that.getInsertQuery(), values); - }); - - this.getEntity = (entity) => co(function *() { - const conditions = getPKFields().map((field) => '`' + field + '` = ?').join(' and '); - const params = toParams(entity, getPKFields()); - return (yield that.query('SELECT * FROM ' + that.table + ' WHERE ' + conditions, params))[0]; - }); - - this.deleteEntity = (entity) => co(function *() { - const toSave = toRow(entity); - if (that.beforeSaveHook) { - that.beforeSaveHook(toSave); - } - const conditions = getPKFields().map((field) => '`' + field + '` = ?').join(' and '); - const condValues = getPKFields().map((k) => toSave[k]); - return that.query('DELETE FROM ' + that.table + ' WHERE ' + conditions, condValues); - }); - - this.exec = (sql) => co(function *() { - try { - // logger.trace(sql); - return driver.executeSql(sql); - } catch (e) { - logger.error('ERROR >> %s', sql); - throw e; - } - }); - - this.getInsertQuery = () => - "INSERT INTO " + that.table + " (" + getFields(that.fields).map(f => '`' + f + '`').join(',') + ") VALUES (" + "?,".repeat(that.fields.length - 1) + "?);"; - - this.getInsertHead = () => { - const valuesKeys = getFields(that.fields); - return 'INSERT INTO ' + that.table + " (" + valuesKeys.map(f => '`' + f + '`').join(',') + ") VALUES "; - }; - - this.getInsertValue = (toSave) => { - if (that.beforeSaveHook) { - that.beforeSaveHook(toSave); - } - const row = toRow(toSave); - const valuesKeys = getFields(that.fields); - const values = valuesKeys.map((field) => escapeToSQLite(row[field])); - return "(" + values.join(',') + ")"; - }; - - this.toInsertValues = (entity) => { - const row = toRow(entity); - const values = that.fields.map((f) => row[f]); - const formatted = values.map(escapeToSQLite); - return "(" + formatted.join(',') + ")"; - }; - - /** - * Make a batch insert. - * @param records The records to insert as a batch. - */ - this.insertBatch = (records) => co(function *() { - const queries = []; - if (records.length) { - const insert = that.getInsertHead(); - const values = records.map((src) => that.getInsertValue(src)); - queries.push(insert + '\n' + values.join(',\n') + ';'); - } - if (queries.length) { - return that.exec(queries.join('\n')); - } - }); - - function toConditionsArray(obj) { - return _.keys(obj).map((k) => { - if (obj[k].$lte !== undefined) { - return '`' + k + '` <= ?'; - } else if (obj[k].$gte !== undefined) { - return '`' + k + '` >= ?'; - } else if (obj[k].$gt !== undefined) { - return '`' + k + '` > ?'; - } else if (obj[k].$lt !== undefined) { - return '`' + k + '` < ?'; - } else if (obj[k].$null !== undefined) { - return '`' + k + '` IS ' + (!obj[k].$null ? 'NOT' : '') + ' NULL'; - } else if (obj[k].$contains !== undefined) { - return '`' + k + '` LIKE ?'; - } else { - return '`' + k + '` = ?'; - } - }); - } - - const toParams = (obj, fields) => { - let params = []; - (fields || _.keys(obj)).forEach((f) => { - if (obj[f].$null === undefined) { - let pValue; - if (obj[f].$lte !== undefined) { pValue = obj[f].$lte; } - else if (obj[f].$gte !== undefined) { pValue = obj[f].$gte; } - else if (obj[f].$gt !== undefined) { pValue = obj[f].$gt; } - else if (obj[f].$lt !== undefined) { pValue = obj[f].$lt; } - else if (obj[f].$null !== undefined) { pValue = obj[f].$null; } - else if (obj[f].$contains !== undefined) { pValue = "%" + obj[f].$contains + "%"; } - else if (~that.bigintegers.indexOf(f) && typeof obj[f] !== "string") { - pValue = String(obj[f]); - } else { - pValue = obj[f]; - } - params.push(pValue); - } - }); - return params; - }; - - const escapeToSQLite = (val) => { - if (typeof val == "boolean") { - // SQLite specific: true => 1, false => 0 - if (val !== null && val !== undefined) { - return val ? 1 : 0; - } else { - return null; - } - } - else if (typeof val == "string") { - return "'" + val.replace(/'/g, "\\'") + "'"; - } - else if (val === undefined) { - return "null"; - } else { - return JSON.stringify(val); - } - }; - - const getPKFields = () => getFields(that.pkFields); - - const getFields = (fields) => fields.map((f) => getField(f)); - - const getField = (f) => that.translated[f] || f; - - function toEntity(row) { - for (const arr of that.arrays) { - row[arr] = row[arr] ? JSON.parse(row[arr]) : []; - } - // Big integers are stored as strings to avoid data loss - for (const bigint of that.bigintegers) { - if (row[bigint] !== null && row[bigint] !== undefined) { - row[bigint] = parseInt(row[bigint]); - } - } - // Translate some DB fields to obj fields - let toTranslate = that.translated || {}; - let toDBFields = _.keys(toTranslate); - for (const objField of toDBFields) { - row[objField] = row[toTranslate[objField]]; - } - // Booleans - for (const f of that.booleans) { - row[f] = row[f] !== null ? Boolean(row[f]) : null; - } - // Transient - for (const f of (that.transientFields || [])) { - row[f] = row[f]; - } - return row; - } - - function toRow(entity) { - let row = {}; - for (const f of that.fields) { - row[f] = entity[f] - } - for (const arr of that.arrays) { - row[arr] = JSON.stringify(row[arr] || []); - } - // Big integers are stored as strings to avoid data loss - for (const bigint of that.bigintegers) { - if (entity[bigint] === null || entity[bigint] === undefined) { - row[bigint] = null; - } else { - row[bigint] = String(entity[bigint]); - } - } - // Translate some obj fields to DB field name (because of DB keywords) - let toTranslate = that.translated || {}; - let toDBFields = _.keys(toTranslate); - for (const objField of toDBFields) { - row[toTranslate[objField]] = row[objField]; - } - return row; - } -} diff --git a/app/lib/dal/sqliteDAL/AbstractSQLite.ts b/app/lib/dal/sqliteDAL/AbstractSQLite.ts new file mode 100644 index 000000000..b74dcf022 --- /dev/null +++ b/app/lib/dal/sqliteDAL/AbstractSQLite.ts @@ -0,0 +1,291 @@ +import {SQLiteDriver} from "../drivers/SQLiteDriver" +/** + * Created by cgeek on 22/08/15. + */ + +const _ = require('underscore'); +const co = require('co'); +const colors = require('colors'); +const logger = require('../../logger')('sqlite'); + +export interface BeforeSaveHook<T> { + (t:T): void +} + +export abstract class AbstractSQLite<T> { + + constructor( + private driver:SQLiteDriver, + public readonly table: string, + private pkFields: string[] = [], + protected fields: string[] = [], + private arrays: string[] = [], + private booleans: string[] = [], + private bigintegers: string[] = [], + private transientFields: string[] = [], + private beforeSaveHook: BeforeSaveHook<T> | null = null + ) { + } + + async query(sql:string, params: any[] = []): Promise<T[]> { + try { + //logger.trace(sql, JSON.stringify(params || [])); + const start = Date.now() + const res = await this.driver.executeAll(sql, params || []); + const duration = Date.now() - start; + const entities = res.map((t:T) => this.toEntity(t)) + // Display result + let msg = sql + ' | %s\t==> %s rows in %s ms'; + if (duration <= 2) { + msg = colors.green(msg); + } else if(duration <= 5) { + msg = colors.yellow(msg); + } else if (duration <= 10) { + msg = colors.magenta(msg); + } else if (duration <= 100) { + msg = colors.red(msg); + } + logger.query(msg, JSON.stringify(params || []), entities.length, duration); + return entities; + } catch (e) { + logger.error('ERROR >> %s', sql, JSON.stringify(params || []), e.stack || e.message || e); + throw e; + } + } + + cleanData(): Promise<void> { + return this.exec("DELETE FROM " + this.table) + } + + sqlListAll(): Promise<T[]> { + return this.query("SELECT * FROM " + this.table) + } + + sqlDeleteAll() { + return this.cleanData() + } + + sqlFind(obj:any, sortObj:any = {}): Promise<T[]> { + const conditions = this.toConditionsArray(obj).join(' and '); + const values = this.toParams(obj); + const sortKeys: string[] = _.keys(sortObj); + const sort = sortKeys.length ? ' ORDER BY ' + sortKeys.map((k) => "`" + k + "` " + (sortObj[k] ? 'DESC' : 'ASC')).join(',') : ''; + return this.query('SELECT * FROM ' + this.table + ' WHERE ' + conditions + sort, values); + } + + async sqlFindOne(obj:any, sortObj:any = null): Promise<T> { + const res = await this.sqlFind(obj, sortObj) + return res[0] + } + + sqlFindLikeAny(obj:any): Promise<T[]> { + const keys:string[] = _.keys(obj); + return this.query('SELECT * FROM ' + this.table + ' WHERE ' + keys.map((k) => 'UPPER(`' + k + '`) like ?').join(' or '), keys.map((k) => obj[k].toUpperCase())) + } + + async sqlRemoveWhere(obj:any): Promise<void> { + const keys:string[] = _.keys(obj); + await this.query('DELETE FROM ' + this.table + ' WHERE ' + keys.map((k) => '`' + k + '` = ?').join(' and '), keys.map((k) => obj[k])) + } + + sqlExisting(entity:T): Promise<T> { + return this.getEntity(entity) + } + + async saveEntity(entity:any): Promise<void> { + let toSave:any = entity; + if (this.beforeSaveHook) { + this.beforeSaveHook(toSave); + } + const existing = await this.getEntity(toSave); + if (existing) { + toSave = this.toRow(toSave); + const valorizations = this.fields.map((field) => '`' + field + '` = ?').join(', '); + const conditions = this.getPKFields().map((field) => '`' + field + '` = ?').join(' and '); + const setValues = this.fields.map((field) => toSave[field]); + const condValues = this.getPKFields().map((k) => toSave[k]); + await this.query('UPDATE ' + this.table + ' SET ' + valorizations + ' WHERE ' + conditions, setValues.concat(condValues)); + return + } + await this.insert(toSave); + } + + async insert(entity:T): Promise<void> { + const row = this.toRow(entity); + const values = this.fields.map((f) => row[f]); + await this.query(this.getInsertQuery(), values) + } + + async getEntity(entity:any): Promise<T> { + const conditions = this.getPKFields().map((field) => '`' + field + '` = ?').join(' and '); + const params = this.toParams(entity, this.getPKFields()); + return (await this.query('SELECT * FROM ' + this.table + ' WHERE ' + conditions, params))[0]; + } + + async deleteEntity(entity:T): Promise<void> { + const toSave = this.toRow(entity); + if (this.beforeSaveHook) { + this.beforeSaveHook(toSave); + } + const conditions = this.getPKFields().map((field) => '`' + field + '` = ?').join(' and '); + const condValues = this.getPKFields().map((k) => toSave[k]); + await this.query('DELETE FROM ' + this.table + ' WHERE ' + conditions, condValues) + } + + exec(sql:string): Promise<void> { + try { + //console.warn(sql); + return this.driver.executeSql(sql); + } catch (e) { + //console.error('ERROR >> %s', sql); + throw e; + } + } + + getInsertQuery(): string { + return "INSERT INTO " + this.table + " (" + this.fields.map(f => '`' + f + '`').join(',') + ") VALUES (" + "?,".repeat(this.fields.length - 1) + "?);" + } + + getInsertHead(): string { + const valuesKeys = this.fields + return 'INSERT INTO ' + this.table + " (" + valuesKeys.map(f => '`' + f + '`').join(',') + ") VALUES "; + } + + getInsertValue(toSave:T): string { + if (this.beforeSaveHook) { + this.beforeSaveHook(toSave); + } + const row = this.toRow(toSave); + const valuesKeys = this.fields + const values = valuesKeys.map((field) => this.escapeToSQLite(row[field])); + return "(" + values.join(',') + ")"; + } + + toInsertValues(entity:T): string { + const row = this.toRow(entity); + const values = this.fields.map((f) => row[f]); + const formatted = values.map((s:string) => this.escapeToSQLite(s)) + return "(" + formatted.join(',') + ")"; + } + + /** + * Make a batch insert. + * @param records The records to insert as a batch. + */ + async insertBatch(records:T[]): Promise<void> { + const queries = []; + if (records.length) { + const insert = this.getInsertHead(); + const values = records.map((src) => this.getInsertValue(src)); + queries.push(insert + '\n' + values.join(',\n') + ';'); + } + if (queries.length) { + await this.exec(queries.join('\n')) + } + } + + private toConditionsArray(obj:any): string[] { + return _.keys(obj).map((k:string) => { + if (obj[k].$lte !== undefined) { + return '`' + k + '` <= ?'; + } else if (obj[k].$gte !== undefined) { + return '`' + k + '` >= ?'; + } else if (obj[k].$gt !== undefined) { + return '`' + k + '` > ?'; + } else if (obj[k].$lt !== undefined) { + return '`' + k + '` < ?'; + } else if (obj[k].$null !== undefined) { + return '`' + k + '` IS ' + (!obj[k].$null ? 'NOT' : '') + ' NULL'; + } else if (obj[k].$contains !== undefined) { + return '`' + k + '` LIKE ?'; + } else { + return '`' + k + '` = ?'; + } + }); + } + + private toParams(obj:any, fields:string[] | null = null): any[] { + let params:any[] = []; + (fields || _.keys(obj)).forEach((f:string) => { + if (obj[f].$null === undefined) { + let pValue; + if (obj[f].$lte !== undefined) { pValue = obj[f].$lte; } + else if (obj[f].$gte !== undefined) { pValue = obj[f].$gte; } + else if (obj[f].$gt !== undefined) { pValue = obj[f].$gt; } + else if (obj[f].$lt !== undefined) { pValue = obj[f].$lt; } + else if (obj[f].$null !== undefined) { pValue = obj[f].$null; } + else if (obj[f].$contains !== undefined) { pValue = "%" + obj[f].$contains + "%"; } + else if (~this.bigintegers.indexOf(f) && typeof obj[f] !== "string") { + pValue = String(obj[f]); + } else { + pValue = obj[f]; + } + params.push(pValue); + } + }); + return params; + } + + private escapeToSQLite(val:string): any { + if (typeof val == "boolean") { + // SQLite specific: true => 1, false => 0 + if (val !== null && val !== undefined) { + return val ? 1 : 0; + } else { + return null; + } + } + else if (typeof val == "string") { + return "'" + val.replace(/'/g, "\\'") + "'"; + } + else if (val === undefined) { + return "null"; + } else { + return JSON.stringify(val); + } + } + + private getPKFields(): string[] { + return this.pkFields + } + + private toEntity(row:any): T { + for (const arr of this.arrays) { + row[arr] = row[arr] ? JSON.parse(row[arr]) : []; + } + // Big integers are stored as strings to avoid data loss + for (const bigint of this.bigintegers) { + if (row[bigint] !== null && row[bigint] !== undefined) { + row[bigint] = parseInt(row[bigint]); + } + } + // Booleans + for (const f of this.booleans) { + row[f] = row[f] !== null ? Boolean(row[f]) : null; + } + // Transient + for (const f of (this.transientFields || [])) { + row[f] = row[f]; + } + return row; + } + + private toRow(entity:any): any { + let row:any = {}; + for (const f of this.fields) { + row[f] = entity[f] + } + for (const arr of this.arrays) { + row[arr] = JSON.stringify(row[arr] || []); + } + // Big integers are stored as strings to avoid data loss + for (const bigint of this.bigintegers) { + if (entity[bigint] === null || entity[bigint] === undefined) { + row[bigint] = null; + } else { + row[bigint] = String(entity[bigint]); + } + } + return row; + } +} diff --git a/app/lib/dal/sqliteDAL/BlockDAL.js b/app/lib/dal/sqliteDAL/BlockDAL.js deleted file mode 100644 index dce074db5..000000000 --- a/app/lib/dal/sqliteDAL/BlockDAL.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const Q = require('q'); -const co = require('co'); -const constants = require('../../constants'); -const AbstractSQLite = require('./AbstractSQLite'); - -module.exports = BlockDAL; - -const IS_FORK = true; -const IS_NOT_FORK = false; - -function BlockDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - let current = null; - let that = this; - - this.table = 'block'; - this.fields = ['fork', 'hash', 'inner_hash', 'signature', 'currency', 'issuer', 'issuersCount', 'issuersFrame', 'issuersFrameVar', 'parameters', 'previousHash', 'previousIssuer', 'version', 'membersCount', 'monetaryMass', 'UDTime', 'medianTime', 'dividend', 'unitbase', 'time', 'powMin', 'number', 'nonce', 'transactions', 'certifications', 'identities', 'joiners', 'actives', 'leavers', 'revoked', 'excluded', 'len']; - this.arrays = ['identities','certifications','actives','revoked','excluded','leavers','joiners','transactions']; - this.bigintegers = ['monetaryMass']; - this.booleans = ['wrong']; - this.pkFields = ['number','hash']; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'fork BOOLEAN NOT NULL,' + - 'hash VARCHAR(64) NOT NULL,' + - 'inner_hash VARCHAR(64) NOT NULL,' + - 'signature VARCHAR(100) NOT NULL,' + - 'currency VARCHAR(50) NOT NULL,' + - 'issuer VARCHAR(50) NOT NULL,' + - 'parameters VARCHAR(255),' + - 'previousHash VARCHAR(64),' + - 'previousIssuer VARCHAR(50),' + - 'version INTEGER NOT NULL,' + - 'membersCount INTEGER NOT NULL,' + - 'monetaryMass VARCHAR(100) DEFAULT \'0\',' + - 'UDTime DATETIME,' + - 'medianTime DATETIME NOT NULL,' + - 'dividend INTEGER DEFAULT \'0\',' + - 'unitbase INTEGER NULL,' + - 'time DATETIME NOT NULL,' + - 'powMin INTEGER NOT NULL,' + - 'number INTEGER NOT NULL,' + - 'nonce INTEGER NOT NULL,' + - 'transactions TEXT,' + - 'certifications TEXT,' + - 'identities TEXT,' + - 'joiners TEXT,' + - 'actives TEXT,' + - 'leavers TEXT,' + - 'revoked TEXT,' + - 'excluded TEXT,' + - 'created DATETIME DEFAULT NULL,' + - 'updated DATETIME DEFAULT NULL,' + - 'PRIMARY KEY (number,hash)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_block_hash ON block (hash);' + - 'CREATE INDEX IF NOT EXISTS idx_block_fork ON block (fork);' + - 'COMMIT;', []); - }); - - this.cleanCache = () => current = null; - - /** - * Periodically cleans the current block cache. - * It seems the cache is not always correct and may stuck the node, so it is preferable to reset it periodically. - */ - setInterval(this.cleanCache, constants.CURRENT_BLOCK_CACHE_DURATION); - - this.getCurrent = () => co(function *() { - if (!current) { - current = (yield that.query('SELECT * FROM block WHERE NOT fork ORDER BY number DESC LIMIT 1'))[0]; - } - return Q(current); - }); - - this.getBlock = (number) => co(function *() { - return (yield that.query('SELECT * FROM block WHERE number = ? and NOT fork', [parseInt(number)]))[0]; - }); - - this.getAbsoluteBlock = (number, hash) => co(function *() { - return (yield that.query('SELECT * FROM block WHERE number = ? and hash = ?', [parseInt(number), hash]))[0]; - }); - - this.getBlocks = (start, end) => { - return that.query('SELECT * FROM block WHERE number BETWEEN ? and ? and NOT fork ORDER BY number ASC', [start, end]); - }; - - this.lastBlockWithDividend = () => co(function *() { - return (yield that.query('SELECT * FROM block WHERE dividend > 0 and NOT fork ORDER BY number DESC LIMIT 1'))[0]; - }); - - this.lastBlockOfIssuer = (issuer) => co(function *() { - return (yield that.query('SELECT * FROM block WHERE issuer = ? and NOT fork ORDER BY number DESC LIMIT 1', [issuer]))[0]; - }); - - this.getCountOfBlocksIssuedBy = (issuer) => co(function *() { - let res = yield that.query('SELECT COUNT(*) as quantity FROM block WHERE issuer = ? and NOT fork', [issuer]); - return res[0].quantity; - }); - - this.getForkBlocks = () => { - return that.query('SELECT * FROM block WHERE fork ORDER BY number'); - }; - - this.getDividendBlocks = () => { - return that.query('SELECT * FROM block WHERE dividend IS NOT NULL ORDER BY number'); - }; - - this.saveBunch = (blocks) => co(function *() { - let queries = "INSERT INTO block (" + that.fields.join(',') + ") VALUES "; - for (let i = 0, len = blocks.length; i < len; i++) { - let block = blocks[i]; - queries += that.toInsertValues(block); - if (i + 1 < len) { - queries += ",\n"; - } - } - yield that.exec(queries); - that.cleanCache(); - }); - - this.saveBlock = (block) => co(function *() { - let saved = yield saveBlockAs(block, IS_NOT_FORK); - if (!current || current.number < block.number) { - current = block; - } - return saved; - }); - - this.saveSideBlock = (block) => - saveBlockAs(block, IS_FORK); - - function saveBlockAs(block, fork) { - return co(function *() { - block.fork = fork; - return yield that.saveEntity(block); - }); - } - - this.setSideBlock = (number, previousBlock) => co(function *() { - yield that.query('UPDATE block SET fork = ? WHERE number = ?', [true, number]); - current = previousBlock; - }); - - this.getNextForkBlocks = (number, hash) => { - return that.query('SELECT * FROM block WHERE fork AND number = ? AND previousHash like ? ORDER BY number', [number + 1, hash]); - }; -} diff --git a/app/lib/dal/sqliteDAL/BlockDAL.ts b/app/lib/dal/sqliteDAL/BlockDAL.ts new file mode 100644 index 000000000..35b38d241 --- /dev/null +++ b/app/lib/dal/sqliteDAL/BlockDAL.ts @@ -0,0 +1,193 @@ +import {AbstractSQLite} from "./AbstractSQLite"; +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +const Q = require('q'); +const constants = require('../../constants'); + +const IS_FORK = true; +const IS_NOT_FORK = false; + +export interface DBBlock { + fork: boolean + hash: string + inner_hash: string + signature: string + currency: string + issuer: string + parameters: string + previousHash: string + previousIssuer: string + version: string + membersCount: string + monetaryMass: string + UDTime: string + medianTime: string + dividend: string + unitbase: string + time: string + powMin: string + number: string + nonce: string + transactions: string + certifications: string + identities: string + joiners: string + actives: string + leavers: string + revoked: string + excluded: string + created: string + updated: string +} + +export class BlockDAL extends AbstractSQLite<DBBlock> { + + private current: any + + constructor(driver:SQLiteDriver) { + super( + driver, + 'block', + // PK fields + ['number','hash'], + // Fields + ['fork', 'hash', 'inner_hash', 'signature', 'currency', 'issuer', 'issuersCount', 'issuersFrame', 'issuersFrameVar', 'parameters', 'previousHash', 'previousIssuer', 'version', 'membersCount', 'monetaryMass', 'UDTime', 'medianTime', 'dividend', 'unitbase', 'time', 'powMin', 'number', 'nonce', 'transactions', 'certifications', 'identities', 'joiners', 'actives', 'leavers', 'revoked', 'excluded', 'len'], + // Arrays + ['identities','certifications','actives','revoked','excluded','leavers','joiners','transactions'], + // Booleans + ['wrong'], + // BigIntegers + ['monetaryMass'], + // Transient + [] + ) + + /** + * Periodically cleans the current block cache. + * It seems the cache is not always correct and may stuck the node, so it is preferable to reset it periodically. + */ + setInterval(this.cleanCache, constants.CURRENT_BLOCK_CACHE_DURATION); + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'fork BOOLEAN NOT NULL,' + + 'hash VARCHAR(64) NOT NULL,' + + 'inner_hash VARCHAR(64) NOT NULL,' + + 'signature VARCHAR(100) NOT NULL,' + + 'currency VARCHAR(50) NOT NULL,' + + 'issuer VARCHAR(50) NOT NULL,' + + 'parameters VARCHAR(255),' + + 'previousHash VARCHAR(64),' + + 'previousIssuer VARCHAR(50),' + + 'version INTEGER NOT NULL,' + + 'membersCount INTEGER NOT NULL,' + + 'monetaryMass VARCHAR(100) DEFAULT \'0\',' + + 'UDTime DATETIME,' + + 'medianTime DATETIME NOT NULL,' + + 'dividend INTEGER DEFAULT \'0\',' + + 'unitbase INTEGER NULL,' + + 'time DATETIME NOT NULL,' + + 'powMin INTEGER NOT NULL,' + + 'number INTEGER NOT NULL,' + + 'nonce INTEGER NOT NULL,' + + 'transactions TEXT,' + + 'certifications TEXT,' + + 'identities TEXT,' + + 'joiners TEXT,' + + 'actives TEXT,' + + 'leavers TEXT,' + + 'revoked TEXT,' + + 'excluded TEXT,' + + 'created DATETIME DEFAULT NULL,' + + 'updated DATETIME DEFAULT NULL,' + + 'PRIMARY KEY (number,hash)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_block_hash ON block (hash);' + + 'CREATE INDEX IF NOT EXISTS idx_block_fork ON block (fork);' + + 'COMMIT;') + } + + cleanCache() { + this.current = null + } + + async getCurrent() { + if (!this.current) { + this.current = (await this.query('SELECT * FROM block WHERE NOT fork ORDER BY number DESC LIMIT 1'))[0]; + } + return Q(this.current); + } + + async getBlock(number:string | number) { + return (await this.query('SELECT * FROM block WHERE number = ? and NOT fork', [parseInt(String(number))]))[0]; + } + + async getAbsoluteBlock(number:number, hash:string) { + return (await this.query('SELECT * FROM block WHERE number = ? and hash = ?', [number, hash]))[0]; + } + + getBlocks(start:number, end:number) { + return this.query('SELECT * FROM block WHERE number BETWEEN ? and ? and NOT fork ORDER BY number ASC', [start, end]); + } + + async lastBlockWithDividend() { + return (await this.query('SELECT * FROM block WHERE dividend > 0 and NOT fork ORDER BY number DESC LIMIT 1'))[0]; + } + + async lastBlockOfIssuer(issuer:string) { + return (await this.query('SELECT * FROM block WHERE issuer = ? and NOT fork ORDER BY number DESC LIMIT 1', [issuer]))[0] + } + + async getCountOfBlocksIssuedBy(issuer:string) { + let res: any = await this.query('SELECT COUNT(*) as quantity FROM block WHERE issuer = ? and NOT fork', [issuer]); + return res[0].quantity; + } + + getForkBlocks() { + return this.query('SELECT * FROM block WHERE fork ORDER BY number'); + } + + getDividendBlocks() { + return this.query('SELECT * FROM block WHERE dividend IS NOT NULL ORDER BY number'); + } + + async saveBunch(blocks:DBBlock[]) { + let queries = "INSERT INTO block (" + this.fields.join(',') + ") VALUES "; + for (let i = 0, len = blocks.length; i < len; i++) { + let block = blocks[i]; + queries += this.toInsertValues(block); + if (i + 1 < len) { + queries += ",\n"; + } + } + await this.exec(queries); + this.cleanCache(); + } + + async saveBlock(block:DBBlock) { + let saved = await this.saveBlockAs(block, IS_NOT_FORK); + if (!this.current || this.current.number < block.number) { + this.current = block; + } + return saved; + } + + saveSideBlock(block:DBBlock) { + return this.saveBlockAs(block, IS_FORK) + } + + private async saveBlockAs(block:DBBlock, fork:boolean) { + block.fork = fork; + return await this.saveEntity(block); + } + + async setSideBlock(number:number, previousBlock:DBBlock) { + await this.query('UPDATE block SET fork = ? WHERE number = ?', [true, number]); + this.current = previousBlock; + } + + getNextForkBlocks(number:number, hash:string) { + return this.query('SELECT * FROM block WHERE fork AND number = ? AND previousHash like ? ORDER BY number', [number + 1, hash]); + } +} diff --git a/app/lib/dal/sqliteDAL/CertDAL.js b/app/lib/dal/sqliteDAL/CertDAL.js deleted file mode 100644 index 7a2c773db..000000000 --- a/app/lib/dal/sqliteDAL/CertDAL.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const Q = require('q'); -const co = require('co'); -const AbstractSQLite = require('./AbstractSQLite'); -const constants = require('../../constants'); -const SandBox = require('./SandBox'); - -module.exports = CertDAL; - -function CertDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - const that = this; - - this.table = 'cert'; - this.fields = [ - 'linked', - 'written', - 'written_block', - 'written_hash', - 'sig', - 'block_number', - 'block_hash', - 'target', - 'to', - 'from', - 'block', - 'expired', - 'expires_on' - ]; - this.arrays = []; - this.booleans = ['linked', 'written']; - this.pkFields = ['from','target','sig']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - '`from` VARCHAR(50) NOT NULL,' + - '`to` VARCHAR(50) NOT NULL,' + - 'target CHAR(64) NOT NULL,' + - 'sig VARCHAR(100) NOT NULL,' + - 'block_number INTEGER NOT NULL,' + - 'block_hash VARCHAR(64),' + - 'block INTEGER NOT NULL,' + - 'linked BOOLEAN NOT NULL,' + - 'written BOOLEAN NOT NULL,' + - 'written_block INTEGER,' + - 'written_hash VARCHAR(64),' + - 'expires_on INTEGER NULL,' + - 'PRIMARY KEY (`from`, target, sig, written_block)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_cert_from ON cert (`from`);' + - 'CREATE INDEX IF NOT EXISTS idx_cert_target ON cert (target);' + - 'CREATE INDEX IF NOT EXISTS idx_cert_linked ON cert (linked);' + - 'COMMIT;', []); - }); - - this.beforeSaveHook = function(entity) { - entity.written = entity.written || !!(entity.written_hash); - }; - - this.getToTarget = (hash) => this.sqlFind({ - target: hash - }); - - this.getFromPubkeyCerts = (pubkey) => this.sqlFind({ - from: pubkey - }); - - this.getNotLinked = () => this.sqlFind({ - linked: false - }); - - this.getNotLinkedToTarget = (hash) => this.sqlFind({ - target: hash, - linked: false - }); - - this.saveNewCertification = (cert) => this.saveEntity(cert); - - this.existsGivenCert = (cert) => Q(this.sqlExisting(cert)); - - this.deleteCert = (cert) => this.deleteEntity(cert); - - this.trimExpiredCerts = (medianTime) => this.exec('DELETE FROM ' + this.table + ' WHERE expires_on IS NULL OR expires_on < ' + medianTime); - - /************************** - * SANDBOX STUFF - */ - - this.getSandboxForKey = (pub) => { - const getRecorded = () => that.query('SELECT * FROM cert WHERE `from` = ? ORDER BY block_number ASC LIMIT ' + constants.SANDBOX_SIZE_CERTIFICATIONS, [pub]) - const compare = (compared, reference) => { - if (compared.block_number < reference.block_number) { - return -1 - } - else if (compared.block_number > reference.block_number) { - return 1 - } - else { - return 0 - } - } - return new SandBox(constants.SANDBOX_SIZE_CERTIFICATIONS, getRecorded, compare) - } -} diff --git a/app/lib/dal/sqliteDAL/CertDAL.ts b/app/lib/dal/sqliteDAL/CertDAL.ts new file mode 100644 index 000000000..39e324471 --- /dev/null +++ b/app/lib/dal/sqliteDAL/CertDAL.ts @@ -0,0 +1,144 @@ +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {AbstractSQLite} from "./AbstractSQLite"; +import {SandBox} from "./SandBox"; + +const constants = require('../../constants'); + +export interface DBCert { + linked:boolean + written:boolean + written_block:null + written_hash:null + sig:string + block_number:number + block_hash:string + target:string + to:string + from:string + block:number + expired: boolean | null + expires_on: number +} + +export class CertDAL extends AbstractSQLite<DBCert> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'cert', + // PK fields + ['from','target','sig'], + // Fields + [ + 'linked', + 'written', + 'written_block', + 'written_hash', + 'sig', + 'block_number', + 'block_hash', + 'target', + 'to', + 'from', + 'block', + 'expired', + 'expires_on' + ], + // Arrays + [], + // Booleans + ['linked', 'written'], + // BigIntegers + [], + // Transient + [], + (entity:DBCert) => { + entity.written = entity.written || !!(entity.written_hash) + } + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + '`from` VARCHAR(50) NOT NULL,' + + '`to` VARCHAR(50) NOT NULL,' + + 'target CHAR(64) NOT NULL,' + + 'sig VARCHAR(100) NOT NULL,' + + 'block_number INTEGER NOT NULL,' + + 'block_hash VARCHAR(64),' + + 'block INTEGER NOT NULL,' + + 'linked BOOLEAN NOT NULL,' + + 'written BOOLEAN NOT NULL,' + + 'written_block INTEGER,' + + 'written_hash VARCHAR(64),' + + 'expires_on INTEGER NULL,' + + 'PRIMARY KEY (`from`, target, sig, written_block)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_cert_from ON cert (`from`);' + + 'CREATE INDEX IF NOT EXISTS idx_cert_target ON cert (target);' + + 'CREATE INDEX IF NOT EXISTS idx_cert_linked ON cert (linked);' + + 'COMMIT;') + } + + getToTarget(hash:string) { + return this.sqlFind({ + target: hash + }) + } + + getFromPubkeyCerts(pubkey:string) { + return this.sqlFind({ + from: pubkey + }) + } + + getNotLinked() { + return this.sqlFind({ + linked: false + }) + } + + getNotLinkedToTarget(hash:string) { + return this.sqlFind({ + target: hash, + linked: false + }) + } + + saveNewCertification(cert:DBCert) { + return this.saveEntity(cert) + } + + existsGivenCert(cert:DBCert) { + return this.sqlExisting(cert) + } + + deleteCert(cert:DBCert) { + return this.deleteEntity(cert) + } + + async trimExpiredCerts(medianTime:number) { + await this.exec('DELETE FROM ' + this.table + ' WHERE expires_on IS NULL OR expires_on < ' + medianTime) + } + + /************************** + * SANDBOX STUFF + */ + + getSandboxForKey = (pub:string) => { + const getRecorded = () => this.query('SELECT * FROM cert WHERE `from` = ? ORDER BY block_number ASC LIMIT ' + constants.SANDBOX_SIZE_CERTIFICATIONS, [pub]) + const compare = (compared:DBCert, reference:DBCert) => { + if (compared.block_number < reference.block_number) { + return -1 + } + else if (compared.block_number > reference.block_number) { + return 1 + } + else { + return 0 + } + } + return new SandBox(constants.SANDBOX_SIZE_CERTIFICATIONS, getRecorded, compare) + } +} diff --git a/app/lib/dal/sqliteDAL/IdentityDAL.js b/app/lib/dal/sqliteDAL/IdentityDAL.js deleted file mode 100644 index a0562427e..000000000 --- a/app/lib/dal/sqliteDAL/IdentityDAL.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const constants = require('../../constants'); -const AbstractSQLite = require('./AbstractSQLite'); -const SandBox = require('./SandBox'); - -module.exports = IdentityDAL; - -function IdentityDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - const that = this; - - this.table = 'idty'; - this.fields = [ - 'revoked', - 'revoked_on', - 'revocation_sig', - 'currentMSN', - 'currentINN', - 'buid', - 'member', - 'kick', - 'leaving', - 'wasMember', - 'pubkey', - 'uid', - 'sig', - 'hash', - 'written', - 'wotb_id', - 'expired', - 'expires_on', - 'removed' - ]; - this.arrays = []; - this.booleans = ['revoked', 'member', 'kick', 'leaving', 'wasMember', 'written', 'removed']; - this.pkFields = ['pubkey', 'uid', 'hash']; - this.transientFields = ['certsCount', 'ref_block']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'revoked BOOLEAN NOT NULL,' + - 'currentMSN INTEGER NULL,' + - 'currentINN INTEGER NULL,' + - 'buid VARCHAR(100) NOT NULL,' + - 'member BOOLEAN NOT NULL,' + - 'kick BOOLEAN NOT NULL,' + - 'leaving BOOLEAN NULL,' + - 'wasMember BOOLEAN NOT NULL,' + - 'pubkey VARCHAR(50) NOT NULL,' + - 'uid VARCHAR(255) NOT NULL,' + - 'sig VARCHAR(100) NOT NULL,' + - 'revocation_sig VARCHAR(100) NULL,' + - 'hash VARCHAR(64) NOT NULL,' + - 'written BOOLEAN NULL,' + - 'wotb_id INTEGER NULL,' + - 'expires_on INTEGER NULL,' + - 'PRIMARY KEY (pubkey,uid,hash)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_idty_pubkey ON idty (pubkey);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_uid ON idty (uid);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_kick ON idty (kick);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_member ON idty (member);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_wasMember ON idty (wasMember);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_hash ON idty (hash);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_written ON idty (written);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_currentMSN ON idty (currentMSN);' + - 'CREATE INDEX IF NOT EXISTS idx_idty_currentINN ON idty (currentINN);' + - 'COMMIT;', []); - }); - - this.revokeIdentity = (pubkey) => this.exec('DELETE FROM ' + this.table + ' WHERE pubkey = \'' + pubkey + '\''); - - this.removeUnWrittenWithPubkey = (pubkey) => this.sqlRemoveWhere({ - pubkey: pubkey, - written: false - }); - - this.removeUnWrittenWithUID = (uid) => this.sqlRemoveWhere({ - uid: uid, - written: false - }); - - this.getByHash = (hash) => this.sqlFindOne({ - hash: hash - }); - - this.saveIdentity = (idty) => - this.saveEntity(idty); - - this.deleteByHash = (hash) => this.exec('UPDATE ' + this.table + ' SET removed = 1 where hash = \'' + hash + '\''); - - this.getToRevoke = () => that.sqlFind({ - revocation_sig: { $null: false }, - revoked: false, - wasMember: true - }); - - this.getPendingIdentities = () => that.sqlFind({ - revocation_sig: { $null: false }, - revoked: false - }); - - this.searchThoseMatching = (search) => that.sqlFindLikeAny({ - pubkey: "%" + search + "%", - uid: "%" + search + "%" - }); - - this.trimExpiredIdentities = (medianTime) => this.exec('DELETE FROM ' + this.table + ' WHERE (expires_on IS NULL AND revocation_sig IS NULL) OR expires_on < ' + medianTime); - - /************************** - * SANDBOX STUFF - */ - - this.getSandboxIdentities = () => that.query('SELECT * FROM sandbox_idty LIMIT ' + (that.sandbox.maxSize), []); - - this.sandbox = new SandBox(constants.SANDBOX_SIZE_IDENTITIES, this.getSandboxIdentities.bind(this), (compared, reference) => { - if (compared.certsCount < reference.certsCount) { - return -1; - } - else if (compared.certsCount > reference.certsCount) { - return 1; - } - else if (compared.ref_block < reference.ref_block) { - return -1; - } - else if (compared.ref_block > reference.ref_block) { - return 1; - } - else { - return 0; - } - }); - - this.getSandboxRoom = () => this.sandbox.getSandboxRoom(); - this.setSandboxSize = (maxSize) => this.sandbox.maxSize = maxSize; -} diff --git a/app/lib/dal/sqliteDAL/IdentityDAL.ts b/app/lib/dal/sqliteDAL/IdentityDAL.ts new file mode 100644 index 000000000..173d737f2 --- /dev/null +++ b/app/lib/dal/sqliteDAL/IdentityDAL.ts @@ -0,0 +1,192 @@ +import {AbstractSQLite} from "./AbstractSQLite"; +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {SandBox} from "./SandBox"; +const constants = require('../../constants'); + +export interface DBIdentity { + revoked: boolean + currentMSN: null + currentINN: null + buid: string + member: boolean + kick: boolean + leaving: boolean | null + wasMember: boolean + pubkey: string + uid: string + sig: string + revocation_sig: string | null + hash: string + written: boolean + wotb_id: number | null + expires_on: number, + certsCount: number, + ref_block: number +} + +export class IdentityDAL extends AbstractSQLite<DBIdentity> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'idty', + // PK fields + ['pubkey', 'uid', 'hash'], + // Fields + [ + 'revoked', + 'revoked_on', + 'revocation_sig', + 'currentMSN', + 'currentINN', + 'buid', + 'member', + 'kick', + 'leaving', + 'wasMember', + 'pubkey', + 'uid', + 'sig', + 'hash', + 'written', + 'wotb_id', + 'expired', + 'expires_on', + 'removed' + ], + // Arrays + [], + // Booleans + ['revoked', 'member', 'kick', 'leaving', 'wasMember', 'written', 'removed'], + // BigIntegers + [], + // Transient + ['certsCount', 'ref_block'] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'revoked BOOLEAN NOT NULL,' + + 'currentMSN INTEGER NULL,' + + 'currentINN INTEGER NULL,' + + 'buid VARCHAR(100) NOT NULL,' + + 'member BOOLEAN NOT NULL,' + + 'kick BOOLEAN NOT NULL,' + + 'leaving BOOLEAN NULL,' + + 'wasMember BOOLEAN NOT NULL,' + + 'pubkey VARCHAR(50) NOT NULL,' + + 'uid VARCHAR(255) NOT NULL,' + + 'sig VARCHAR(100) NOT NULL,' + + 'revocation_sig VARCHAR(100) NULL,' + + 'hash VARCHAR(64) NOT NULL,' + + 'written BOOLEAN NULL,' + + 'wotb_id INTEGER NULL,' + + 'expires_on INTEGER NULL,' + + 'PRIMARY KEY (pubkey,uid,hash)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_idty_pubkey ON idty (pubkey);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_uid ON idty (uid);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_kick ON idty (kick);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_member ON idty (member);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_wasMember ON idty (wasMember);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_hash ON idty (hash);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_written ON idty (written);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_currentMSN ON idty (currentMSN);' + + 'CREATE INDEX IF NOT EXISTS idx_idty_currentINN ON idty (currentINN);' + + 'COMMIT;') + } + + revokeIdentity(pubkey:string) { + return this.exec('DELETE FROM ' + this.table + ' WHERE pubkey = \'' + pubkey + '\'') + } + + removeUnWrittenWithPubkey(pubkey:string) { + return this.sqlRemoveWhere({ + pubkey: pubkey, + written: false + }) + } + + removeUnWrittenWithUID(uid:string) { + return this.sqlRemoveWhere({ + uid: uid, + written: false + }) + } + + getByHash(hash:string) { + return this.sqlFindOne({ + hash: hash + }) + } + + saveIdentity(idty:DBIdentity) { + return this.saveEntity(idty) + } + + async deleteByHash(hash:string) { + await this.exec('UPDATE ' + this.table + ' SET removed = 1 where hash = \'' + hash + '\'') + } + + getToRevoke() { + return this.sqlFind({ + revocation_sig: { $null: false }, + revoked: false, + wasMember: true + }) + } + + getPendingIdentities() { + return this.sqlFind({ + revocation_sig: { $null: false }, + revoked: false + }) + } + + searchThoseMatching(search:string) { + return this.sqlFindLikeAny({ + pubkey: "%" + search + "%", + uid: "%" + search + "%" + }) + } + + async trimExpiredIdentities(medianTime:number) { + await this.exec('DELETE FROM ' + this.table + ' WHERE (expires_on IS NULL AND revocation_sig IS NULL) OR expires_on < ' + medianTime) + } + + /************************** + * SANDBOX STUFF + */ + + getSandboxIdentities() { + 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) => { + if (compared.certsCount < reference.certsCount) { + return -1; + } + else if (compared.certsCount > reference.certsCount) { + return 1; + } + else if (compared.ref_block < reference.ref_block) { + return -1; + } + else if (compared.ref_block > reference.ref_block) { + return 1; + } + else { + return 0; + } + }); + + getSandboxRoom() { + return this.sandbox.getSandboxRoom() + } + + setSandboxSize(maxSize:number) { + this.sandbox.maxSize = maxSize + } +} diff --git a/app/lib/dal/sqliteDAL/IndexDAL.js b/app/lib/dal/sqliteDAL/IndexDAL.js deleted file mode 100644 index 55f089ddf..000000000 --- a/app/lib/dal/sqliteDAL/IndexDAL.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; - -const AbstractSQLite = require('./AbstractSQLite') -const AbstractIndex = require('./AbstractIndex') - -module.exports = function IndexDAL(driver) { - - AbstractSQLite.call(this, driver); - AbstractIndex.call(this); -} diff --git a/app/lib/dal/sqliteDAL/MembershipDAL.js b/app/lib/dal/sqliteDAL/MembershipDAL.js deleted file mode 100644 index e3f5564a7..000000000 --- a/app/lib/dal/sqliteDAL/MembershipDAL.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const _ = require('underscore'); -const AbstractSQLite = require('./AbstractSQLite'); -const constants = require('../../constants'); -const SandBox = require('./SandBox'); - -module.exports = MembershipDAL; - -function MembershipDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - const that = this; - - this.table = 'membership'; - this.fields = [ - 'membership', - 'issuer', - 'number', - 'blockNumber', - 'blockHash', - 'userid', - 'certts', - 'block', - 'fpr', - 'idtyHash', - 'written', - 'written_number', - 'expires_on', - 'signature', - 'expired' - ]; - this.arrays = []; - this.booleans = ['written']; - this.pkFields = ['issuer','signature']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS membership (' + - 'membership CHAR(2) NOT NULL,' + - 'issuer VARCHAR(50) NOT NULL,' + - 'number INTEGER NOT NULL,' + - 'blockNumber INTEGER,' + - 'blockHash VARCHAR(64) NOT NULL,' + - 'userid VARCHAR(255) NOT NULL,' + - 'certts VARCHAR(100) NOT NULL,' + - 'block INTEGER,' + - 'fpr VARCHAR(64),' + - 'idtyHash VARCHAR(64),' + - 'written BOOLEAN NOT NULL,' + - 'written_number INTEGER,' + - 'expires_on INTEGER NULL,' + - 'signature VARCHAR(50),' + - 'PRIMARY KEY (issuer,signature)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_mmembership_idtyHash ON membership (idtyHash);' + - 'CREATE INDEX IF NOT EXISTS idx_mmembership_membership ON membership (membership);' + - 'CREATE INDEX IF NOT EXISTS idx_mmembership_written ON membership (written);' + - 'COMMIT;', []); - }); - - this.getMembershipsOfIssuer = (issuer) => this.sqlFind({ - issuer: issuer - }); - - this.getPendingINOfTarget = (hash) => this.sqlFind({ - idtyHash: hash, - membership: 'IN' - }); - - this.getPendingIN = () => this.sqlFind({ - membership: 'IN' - }); - - this.getPendingOUT = () => this.sqlFind({ - membership: 'OUT' - }); - - this.savePendingMembership = (ms) => { - ms.membership = ms.membership.toUpperCase(); - ms.written = false; - return this.saveEntity(_.pick(ms, 'membership', 'issuer', 'number', 'blockNumber', 'blockHash', 'userid', 'certts', 'block', 'fpr', 'idtyHash', 'expires_on', 'written', 'written_number', 'signature')); - }; - - this.deleteMS = (ms) => this.deleteEntity(ms); - - this.trimExpiredMemberships = (medianTime) => this.exec('DELETE FROM ' + this.table + ' WHERE expires_on IS NULL OR expires_on < ' + medianTime); - - /************************** - * SANDBOX STUFF - */ - - this.getSandboxMemberships = () => that.query('SELECT * FROM sandbox_memberships LIMIT ' + (that.sandbox.maxSize), []); - - this.sandbox = new SandBox(constants.SANDBOX_SIZE_MEMBERSHIPS, this.getSandboxMemberships.bind(this), (compared, reference) => { - if (compared.block_number < reference.block_number) { - return -1; - } - else if (compared.block_number > reference.block_number) { - return 1; - } - else { - return 0; - } - }); - - this.getSandboxRoom = () => this.sandbox.getSandboxRoom(); - this.setSandboxSize = (maxSize) => this.sandbox.maxSize = maxSize; -} diff --git a/app/lib/dal/sqliteDAL/MembershipDAL.ts b/app/lib/dal/sqliteDAL/MembershipDAL.ts new file mode 100644 index 000000000..61ce346e9 --- /dev/null +++ b/app/lib/dal/sqliteDAL/MembershipDAL.ts @@ -0,0 +1,154 @@ +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {AbstractSQLite} from "./AbstractSQLite"; +import {SandBox} from "./SandBox"; +const _ = require('underscore'); +const constants = require('../../constants'); + +export interface DBMembership { + membership: string + issuer: string + number: number + blockNumber: number + blockHash: string + userid: string + certts: string + block: string + fpr: string + idtyHash: string + written: boolean + written_number: number | null + expires_on: number + signature: string + expired: boolean | null, + block_number: number +} + +export class MembershipDAL extends AbstractSQLite<DBMembership> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'membership', + // PK fields + ['issuer','signature'], + // Fields + [ + 'membership', + 'issuer', + 'number', + 'blockNumber', + 'blockHash', + 'userid', + 'certts', + 'block', + 'fpr', + 'idtyHash', + 'written', + 'written_number', + 'expires_on', + 'signature', + 'expired' + ], + // Arrays + [], + // Booleans + ['written'], + // BigIntegers + [], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS membership (' + + 'membership CHAR(2) NOT NULL,' + + 'issuer VARCHAR(50) NOT NULL,' + + 'number INTEGER NOT NULL,' + + 'blockNumber INTEGER,' + + 'blockHash VARCHAR(64) NOT NULL,' + + 'userid VARCHAR(255) NOT NULL,' + + 'certts VARCHAR(100) NOT NULL,' + + 'block INTEGER,' + + 'fpr VARCHAR(64),' + + 'idtyHash VARCHAR(64),' + + 'written BOOLEAN NOT NULL,' + + 'written_number INTEGER,' + + 'expires_on INTEGER NULL,' + + 'signature VARCHAR(50),' + + 'PRIMARY KEY (issuer,signature)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_mmembership_idtyHash ON membership (idtyHash);' + + 'CREATE INDEX IF NOT EXISTS idx_mmembership_membership ON membership (membership);' + + 'CREATE INDEX IF NOT EXISTS idx_mmembership_written ON membership (written);' + + 'COMMIT;') + } + + getMembershipsOfIssuer(issuer:string) { + return this.sqlFind({ + issuer: issuer + }) + } + + getPendingINOfTarget(hash:string) { + return this.sqlFind({ + idtyHash: hash, + membership: 'IN' + }) + } + + getPendingIN() { + return this.sqlFind({ + membership: 'IN' + }) + } + + getPendingOUT() { + return this.sqlFind({ + membership: 'OUT' + }) + } + + savePendingMembership(ms:DBMembership) { + ms.membership = ms.membership.toUpperCase(); + ms.written = false; + return this.saveEntity(_.pick(ms, 'membership', 'issuer', 'number', 'blockNumber', 'blockHash', 'userid', 'certts', 'block', 'fpr', 'idtyHash', 'expires_on', 'written', 'written_number', 'signature')) + } + + async deleteMS(ms:DBMembership) { + await this.deleteEntity(ms) + } + + async trimExpiredMemberships(medianTime:number) { + await this.exec('DELETE FROM ' + this.table + ' WHERE expires_on IS NULL OR expires_on < ' + medianTime) + } + + /************************** + * SANDBOX STUFF + */ + + getSandboxMemberships() { + return this.query('SELECT * FROM sandbox_memberships LIMIT ' + (this.sandbox.maxSize), []) + } + + sandbox = new SandBox(constants.SANDBOX_SIZE_MEMBERSHIPS, this.getSandboxMemberships.bind(this), (compared:DBMembership, reference:DBMembership) => { + if (compared.block_number < reference.block_number) { + return -1; + } + else if (compared.block_number > reference.block_number) { + return 1; + } + else { + return 0; + } + }); + + getSandboxRoom() { + return this.sandbox.getSandboxRoom() + } + + setSandboxSize(maxSize:number) { + this.sandbox.maxSize = maxSize + } +} diff --git a/app/lib/dal/sqliteDAL/MetaDAL.js b/app/lib/dal/sqliteDAL/MetaDAL.ts similarity index 70% rename from app/lib/dal/sqliteDAL/MetaDAL.js rename to app/lib/dal/sqliteDAL/MetaDAL.ts index 122e71c47..f19f58acf 100644 --- a/app/lib/dal/sqliteDAL/MetaDAL.js +++ b/app/lib/dal/sqliteDAL/MetaDAL.ts @@ -1,36 +1,54 @@ -"use strict"; - -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); +import {AbstractSQLite} from "./AbstractSQLite"; +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {ConfDTO} from "../../dto/ConfDTO"; +import {SindexEntry} from "../../indexer"; +import {hashf} from "../../common"; +import {TransactionDTO} from "../../dto/TransactionDTO"; +import {BlockDAL} from "./BlockDAL"; +import {IdentityDAL} from "./IdentityDAL"; +import {SIndexDAL} from "./index/SIndexDAL"; +import {WalletDAL} from "./WalletDAL"; +import {MIndexDAL} from "./index/MIndexDAL"; + +const _ = require('underscore') const logger = require('../../logger')('metaDAL'); -const AbstractSQLite = require('./AbstractSQLite'); const common = require('duniter-common'); -const hashf = require('duniter-common').hashf; const rawer = require('duniter-common').rawer; const constants = require('./../../constants'); -module.exports = MetaDAL; - -function MetaDAL(driver) { - - AbstractSQLite.call(this, driver); - - const that = this; +export interface DBMeta { + id: number, + version: number +} - this.table = 'meta'; - this.fields = [ - 'id', - 'version' - ]; - this.arrays = []; - this.booleans = []; - this.pkFields = ['version']; - this.translated = {}; +export class MetaDAL extends AbstractSQLite<DBMeta> { + + driverCopy:SQLiteDriver + + constructor(driver:SQLiteDriver) { + super( + driver, + 'meta', + // PK fields + ['version'], + // Fields + [ + 'id', + 'version' + ], + // Arrays + [], + // Booleans + [], + // BigIntegers + [], + // Transient + [] + ) + this.driverCopy = driver + } - const migrations = { + private migrations:any = { // Test 0: 'BEGIN; COMMIT;', @@ -50,16 +68,13 @@ function MetaDAL(driver) { 2: 'BEGIN; ALTER TABLE txs ADD COLUMN received INTEGER NULL; COMMIT;', // Update wrong recipients field (was not filled in) - 3: () => co(function*() { - }), + 3: async () => {}, // Migrates wrong unitbases - 4: () => co(function*() { - }), + 4: async () => {}, // Migrates wrong monetary masses - 5: () => co(function*() { - }), + 5: async () => {}, 6: 'BEGIN; ALTER TABLE idty ADD COLUMN expired INTEGER NULL; COMMIT;', 7: 'BEGIN; ALTER TABLE cert ADD COLUMN expired INTEGER NULL; COMMIT;', @@ -74,11 +89,11 @@ function MetaDAL(driver) { 'ALTER TABLE block ADD COLUMN issuersFrameVar INTEGER NULL;' + 'ALTER TABLE block ADD COLUMN issuersCount INTEGER NULL;' + 'COMMIT;', - 12: () => co(function *() { - let blockDAL = new (require('./BlockDAL'))(driver); - yield blockDAL.exec('ALTER TABLE block ADD COLUMN len INTEGER NULL;'); - yield blockDAL.exec('ALTER TABLE txs ADD COLUMN len INTEGER NULL;'); - }), + 12: async () => { + let blockDAL = new BlockDAL(this.driverCopy) + await blockDAL.exec('ALTER TABLE block ADD COLUMN len INTEGER NULL;'); + await blockDAL.exec('ALTER TABLE txs ADD COLUMN len INTEGER NULL;'); + }, 13: 'BEGIN; ALTER TABLE txs ADD COLUMN blockstampTime INTEGER NULL; COMMIT;', 14: 'BEGIN; ' + @@ -109,25 +124,24 @@ function MetaDAL(driver) { 'ORDER BY block_number DESC;' + 'COMMIT;', - 15: () => co(function *() { - let idtyDAL = new (require('./IdentityDAL'))(driver); - yield idtyDAL.exec('ALTER TABLE idty ADD COLUMN revoked_on INTEGER NULL'); - }), + 15: async () => { + let idtyDAL = new IdentityDAL(this.driverCopy) + await idtyDAL.exec('ALTER TABLE idty ADD COLUMN revoked_on INTEGER NULL'); + }, - 16: () => co(function *() { - }), + 16: async () => {}, - 17: () => co(function *() { - let blockDAL = new (require('./BlockDAL'))(driver); - let sindexDAL = new (require('./index/SIndexDAL'))(driver); - const blocks = yield blockDAL.query('SELECT * FROM block WHERE NOT fork'); + 17: async () => { + let blockDAL = new BlockDAL(this.driverCopy) + let sindexDAL = new SIndexDAL(this.driverCopy) + const blocks = await blockDAL.query('SELECT * FROM block WHERE NOT fork'); const Block = require('../../../lib/entity/block'); const Identity = require('../../../lib/entity/identity'); - const amountsPerKey = {}; + const amountsPerKey:any = {}; const members = []; for (const block of blocks) { const b = new Block(block); - const amountsInForBlockPerKey = {}; + const amountsInForBlockPerKey:any = {}; for (const idty of b.identities) { members.push(Identity.statics.fromInline(idty).pubkey); } @@ -141,7 +155,7 @@ function MetaDAL(driver) { const txs = b.getTransactions(); for (let i = 0; i < txs.length; i++) { const tx = txs[i]; - tx.hash = hashf(rawer.getTransaction(b.transactions[i])).toUpperCase(); + tx.hash = hashf(rawer.getTransaction(b.transactions[i])) for (const input of tx.inputs) { input.tx = tx.hash; input.block = b; @@ -169,10 +183,10 @@ function MetaDAL(driver) { } } const keysToSee = Object.keys(amountsPerKey); - const sourcesMovements = []; + const sourcesMovements: SindexEntry[] = []; for (const key of keysToSee) { - const allCreates = {}; - const allUpdates = {}; + const allCreates: any = {}; + const allUpdates: any = {}; const amounts = amountsPerKey[key]; let balance = 0; for (let j = 0; j < amounts.length; j++) { @@ -210,17 +224,23 @@ function MetaDAL(driver) { } } let amountMissing = 0; - yield Object.values(allCreates).map((src) => co(function*() { - const exist = yield sindexDAL.getSource(src.identifier, src.pos); + await Promise.all(_.values(allCreates).map(async (src:any) => { + const exist = await sindexDAL.getSource(src.identifier, src.pos); if (!exist || exist.consumed) { amountMissing += src.amount; const block = src.block; sourcesMovements.push({ + index: common.constants.I_INDEX, op: common.constants.IDX_CREATE, tx: src.tx, identifier: src.identifier, pos: src.pos, + unlock: null, + age: 0, + txObj: TransactionDTO.mock(), + created_on: null, written_on: [block.number, block.hash].join('-'), + writtenOn: block.number, written_time: block.medianTime, locktime: src.locktime, amount: src.amount, @@ -229,17 +249,17 @@ function MetaDAL(driver) { consumed: false }); } - })); + })) let amountNotDestroyed = 0; - yield Object.values(allUpdates).map((src) => co(function*() { - const exist = yield sindexDAL.getSource(src.identifier, src.pos); + await _.values(allUpdates).map(async (src:any) => { + const exist = await sindexDAL.getSource(src.identifier, src.pos); if (exist && !exist.consumed) { amountNotDestroyed += src.amount; } - })); + }) } - yield sindexDAL.insertBatch(sourcesMovements); - }), + await sindexDAL.insertBatch(sourcesMovements); + }, 18: 'BEGIN;' + // Add a `massReeval` column @@ -254,39 +274,39 @@ function MetaDAL(driver) { /** * Feeds the table of wallets with balances */ - 20: () => co(function*() { - let walletDAL = new (require('./WalletDAL'))(driver); - let sindexDAL = new (require('./index/SIndexDAL'))(driver); - const conditions = yield sindexDAL.query('SELECT DISTINCT(conditions) FROM s_index') + 20: async () => { + let walletDAL = new WalletDAL(this.driverCopy) + let sindexDAL = new SIndexDAL(this.driverCopy) + const conditions = await sindexDAL.query('SELECT DISTINCT(conditions) FROM s_index') for (const row of conditions) { const wallet = { conditions: row.conditions, balance: 0 } - const amountsRemaining = yield sindexDAL.getAvailableForConditions(row.conditions) - wallet.balance = amountsRemaining.reduce((sum, src) => sum + src.amount * Math.pow(10, src.base), 0) - yield walletDAL.saveWallet(wallet) + const amountsRemaining = await sindexDAL.getAvailableForConditions(row.conditions) + wallet.balance = amountsRemaining.reduce((sum:number, src:SindexEntry) => sum + src.amount * Math.pow(10, src.base), 0) + await walletDAL.saveWallet(wallet) } - }), + }, /** * Feeds the m_index.chainable_on */ - 21: (conf) => co(function*() { - let blockDAL = new (require('./BlockDAL'))(driver); - let mindexDAL = new (require('./index/MIndexDAL'))(driver); - yield mindexDAL.exec('ALTER TABLE m_index ADD COLUMN chainable_on INTEGER NULL;') - const memberships = yield mindexDAL.query('SELECT * FROM m_index WHERE op = ?', [common.constants.IDX_CREATE]) + 21: async (conf:ConfDTO) => { + let blockDAL = new BlockDAL(this.driverCopy) + let mindexDAL = new MIndexDAL(this.driverCopy) + await mindexDAL.exec('ALTER TABLE m_index ADD COLUMN chainable_on INTEGER NULL;') + const memberships = await mindexDAL.query('SELECT * FROM m_index WHERE op = ?', [common.constants.IDX_CREATE]) for (const ms of memberships) { - const reference = yield blockDAL.getBlock(parseInt(ms.written_on.split('-')[0])) + const reference = await blockDAL.getBlock(parseInt(ms.written_on.split('-')[0])) const updateQuery = 'UPDATE m_index SET chainable_on = ' + (reference.medianTime + conf.msPeriod) + ' WHERE pub = \'' + ms.pub + '\' AND op = \'CREATE\'' - yield mindexDAL.exec(updateQuery) + await mindexDAL.exec(updateQuery) } - }), + }, // Replay the wallet table feeding, because of a potential bug - 22: function resetWallets() { - return migrations[20]() + 22: () => { + return this.migrations[20]() }, 23: 'BEGIN;' + @@ -306,64 +326,60 @@ function MetaDAL(driver) { 'COMMIT;' }; - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + 'id INTEGER NOT NULL,' + 'version INTEGER NOT NULL,' + 'PRIMARY KEY (id)' + ');' + - 'COMMIT;', []); - }); + 'COMMIT;') + } - function executeMigration(migration, conf) { - return co(function *() { - try { - if (typeof migration == "string") { + private async executeMigration(migration: any[], conf:ConfDTO) { + try { + if (typeof migration == "string") { - // Simple SQL script to pass - yield that.exec(migration); + // Simple SQL script to pass + await this.exec(migration); - } else if (typeof migration == "function") { + } else if (typeof migration == "function") { - // JS function to execute - yield migration(conf); + // JS function to execute + await migration(conf); - } - } catch (e) { - logger.warn('An error occured during DB migration, continue.', e); } - }); + } catch (e) { + logger.warn('An error occured during DB migration, continue.', e); + } } - this.upgradeDatabase = (conf) => co(function *() { - let version = yield that.getVersion(); - while(migrations[version]) { - yield executeMigration(migrations[version], conf); + async upgradeDatabase(conf:ConfDTO) { + let version = await this.getVersion(); + while(this.migrations[version]) { + await this.executeMigration(this.migrations[version], conf); // Automated increment - yield that.exec('UPDATE meta SET version = version + 1'); + await this.exec('UPDATE meta SET version = version + 1'); version++; } - }); - - this.upgradeDatabaseVersions = (versions) => co(function *() { - for (const version of versions) { - logger.debug("Upgrading from to v%s...", version, version + 1); - yield executeMigration(migrations[version]); - } - }); + } - this.getRow = () => that.sqlFindOne({ id: 1 }); + getRow() { + return this.sqlFindOne({ id: 1 }) + } - this.getVersion = () => co(function *() { + async getVersion() { try { - const row = yield that.getRow(); + const row = await this.getRow() return row.version; } catch(e) { - yield that.exec('INSERT INTO ' + that.table + ' VALUES (1,0);'); + await this.exec('INSERT INTO ' + this.table + ' VALUES (1,0);') return 0; } - }); + } - this.cleanData = null; // Never clean data of this table + cleanData() { + // Never clean data of this table + return Promise.resolve() + } } diff --git a/app/lib/dal/sqliteDAL/PeerDAL.js b/app/lib/dal/sqliteDAL/PeerDAL.js deleted file mode 100644 index f65ec06cd..000000000 --- a/app/lib/dal/sqliteDAL/PeerDAL.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const AbstractSQLite = require('./AbstractSQLite'); - -module.exports = PeerDAL; - -function PeerDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - const that = this; - - this.table = 'peer'; - this.fields = [ - 'version', - 'currency', - 'status', - 'statusTS', - 'hash', - 'first_down', - 'last_try', - 'pubkey', - 'block', - 'signature', - 'endpoints', - 'raw' - ]; - this.arrays = ['endpoints']; - this.booleans = []; - this.pkFields = ['pubkey']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'version INTEGER NOT NULL,' + - 'currency VARCHAR(50) NOT NULL,' + - 'status VARCHAR(10),' + - 'statusTS INTEGER NOT NULL,' + - 'hash CHAR(64),' + - 'first_down INTEGER,' + - 'last_try INTEGER,' + - 'pubkey VARCHAR(50) NOT NULL,' + - 'block VARCHAR(60) NOT NULL,' + - 'signature VARCHAR(100),' + - 'endpoints TEXT NOT NULL,' + - 'raw TEXT,' + - 'PRIMARY KEY (pubkey)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_link_source ON peer (pubkey);' + - 'COMMIT;', []); - }); - - this.listAll = () => this.sqlListAll(); - - this.getPeer = (pubkey) => this.sqlFindOne({ pubkey: pubkey }); - - this.savePeer = (peer) => this.saveEntity(peer); - - this.removeAll = () => this.sqlDeleteAll(); -} diff --git a/app/lib/dal/sqliteDAL/PeerDAL.ts b/app/lib/dal/sqliteDAL/PeerDAL.ts new file mode 100644 index 000000000..299ec9b6e --- /dev/null +++ b/app/lib/dal/sqliteDAL/PeerDAL.ts @@ -0,0 +1,89 @@ +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {AbstractSQLite} from "./AbstractSQLite"; + +export interface DBPeer { + version: number + currency: string + status: string + statusTS: number + hash: string + first_down: number | null + last_try: number | null + pubkey: string + block: string + signature: string + endpoints: string + raw: string +} + +export class PeerDAL extends AbstractSQLite<DBPeer> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'peer', + // PK fields + ['pubkey'], + // Fields + [ + 'version', + 'currency', + 'status', + 'statusTS', + 'hash', + 'first_down', + 'last_try', + 'pubkey', + 'block', + 'signature', + 'endpoints', + 'raw' + ], + // Arrays + ['endpoints'], + // Booleans + [], + // BigIntegers + [], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'version INTEGER NOT NULL,' + + 'currency VARCHAR(50) NOT NULL,' + + 'status VARCHAR(10),' + + 'statusTS INTEGER NOT NULL,' + + 'hash CHAR(64),' + + 'first_down INTEGER,' + + 'last_try INTEGER,' + + 'pubkey VARCHAR(50) NOT NULL,' + + 'block VARCHAR(60) NOT NULL,' + + 'signature VARCHAR(100),' + + 'endpoints TEXT NOT NULL,' + + 'raw TEXT,' + + 'PRIMARY KEY (pubkey)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_link_source ON peer (pubkey);' + + 'COMMIT;') + } + + listAll() { + return this.sqlListAll() + } + + getPeer(pubkey:string) { + return this.sqlFindOne({ pubkey: pubkey }) + } + + savePeer(peer:DBPeer) { + return this.saveEntity(peer) + } + + async removeAll() { + await this.sqlDeleteAll() + } +} diff --git a/app/lib/dal/sqliteDAL/SandBox.js b/app/lib/dal/sqliteDAL/SandBox.js deleted file mode 100644 index 4e7d836ee..000000000 --- a/app/lib/dal/sqliteDAL/SandBox.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; - -const co = require('co'); - -module.exports = SandBox; - -function SandBox(maxSize, findElements, compareElements) { - - const that = this; - this.maxSize = maxSize || 10; - - this.acceptNewSandBoxEntry = (element, pubkey) => co(function *() { - if (element.pubkey === pubkey) { - return true; - } - const elements = yield findElements(); - if (elements.length < that.maxSize) { - return true; - } - const lowestElement = elements[elements.length - 1]; - const comparison = compareElements(element, lowestElement); - return comparison > 0; - }); - - this.getSandboxRoom = () => co(function *() { - const elems = yield findElements(); - return that.maxSize - elems.length; - }); -} diff --git a/app/lib/dal/sqliteDAL/SandBox.ts b/app/lib/dal/sqliteDAL/SandBox.ts new file mode 100644 index 000000000..c84f9a87f --- /dev/null +++ b/app/lib/dal/sqliteDAL/SandBox.ts @@ -0,0 +1,30 @@ +export class SandBox<T> { + + maxSize:number + + constructor( + maxSize:number, + public findElements:() => Promise<T[]>, + public compareElements:(t1:T, t2:T) => number + ) { + this.maxSize = maxSize || 10 + } + + async acceptNewSandBoxEntry(element:any, pubkey:string) { + if (element.pubkey === pubkey) { + return true; + } + const elements = await this.findElements() + if (elements.length < this.maxSize) { + return true; + } + const lowestElement:T = elements[elements.length - 1]; + const comparison = this.compareElements(element, lowestElement) + return comparison > 0; + } + + async getSandboxRoom() { + const elems = await this.findElements() + return this.maxSize - elems.length; + } +} diff --git a/app/lib/dal/sqliteDAL/TxsDAL.js b/app/lib/dal/sqliteDAL/TxsDAL.js deleted file mode 100644 index 2a2f907a1..000000000 --- a/app/lib/dal/sqliteDAL/TxsDAL.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const _ = require('underscore'); -const Q = require('q'); -const co = require('co'); -const moment = require('moment'); -const constants = require('../../constants'); -const Transaction = require('../../entity/transaction'); -const AbstractSQLite = require('./AbstractSQLite'); -const SandBox = require('./SandBox'); - -module.exports = TxsDAL; - -function TxsDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - const that = this; - - this.table = 'txs'; - this.fields = [ - 'hash', - 'block_number', - 'version', - 'currency', - 'comment', - 'blockstamp', - 'blockstampTime', - 'locktime', - 'received', - 'time', - 'written', - 'removed', - 'inputs', - 'unlocks', - 'outputs', - 'issuers', - 'signatures', - 'recipients', - 'output_base', - 'output_amount' - ]; - this.arrays = ['inputs','unlocks','outputs','issuers','signatures','recipients']; - this.booleans = ['written','removed']; - this.pkFields = ['hash']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'hash CHAR(64) NOT NULL,' + - 'block_number INTEGER,' + - 'locktime INTEGER NOT NULL,' + - 'version INTEGER NOT NULL,' + - 'currency VARCHAR(50) NOT NULL,' + - 'comment VARCHAR(255) NOT NULL,' + - 'time DATETIME,' + - 'inputs TEXT NOT NULL,' + - 'unlocks TEXT NOT NULL,' + - 'outputs TEXT NOT NULL,' + - 'issuers TEXT NOT NULL,' + - 'signatures TEXT NOT NULL,' + - 'recipients TEXT NOT NULL,' + - 'written BOOLEAN NOT NULL,' + - 'removed BOOLEAN NOT NULL,' + - 'PRIMARY KEY (hash)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_txs_issuers ON txs (issuers);' + - 'CREATE INDEX IF NOT EXISTS idx_txs_written ON txs (written);' + - 'CREATE INDEX IF NOT EXISTS idx_txs_removed ON txs (removed);' + - 'CREATE INDEX IF NOT EXISTS idx_txs_hash ON txs (hash);' + - 'COMMIT;', []); - }); - - this.getAllPending = (versionMin) => this.sqlFind({ - written: false, - removed: false, - version: { $gte: versionMin } - }); - - this.getTX = (hash) => this.sqlFindOne({ - hash: hash - }); - - this.removeTX = (hash) => co(function *() { - const tx = yield that.sqlFindOne({ - hash: hash - }); - if (tx) { - tx.removed = true; - return that.saveEntity(tx); - } - return Q(tx); - }); - - this.addLinked = (tx) => { - tx.written = true; - tx.removed = false; - tx.hash = tx.getHash(true); - tx.recipients = Transaction.statics.outputs2recipients(tx); - return that.saveEntity(tx); - }; - - this.addPending = (tx) => { - tx.received = moment().unix(); - tx.written = false; - tx.removed = false; - tx.hash = tx.getHash(true); - tx.recipients = Transaction.statics.outputs2recipients(tx); - return this.saveEntity(tx); - }; - - this.getLinkedWithIssuer = (pubkey) => this.sqlFind({ - issuers: { $contains: pubkey }, - written: true - }); - - this.getLinkedWithRecipient = (pubkey) => co(function*() { - const rows = yield that.sqlFind({ - recipients: { $contains: pubkey }, - written: true - }); - // Which does not contains the key as issuer - return _.filter(rows, (row) => row.issuers.indexOf(pubkey) === -1); - }); - - this.getPendingWithIssuer = (pubkey) => this.sqlFind({ - issuers: { $contains: pubkey }, - written: false, - removed: false - }); - - this.getPendingWithRecipient = (pubkey) => this.sqlFind({ - recipients: { $contains: pubkey }, - written: false, - removed: false - }); - - this.insertBatchOfTxs = (txs) => co(function *() { - // // Be sure the recipients field are correctly updated - Transaction.statics.setRecipients(txs); - const queries = []; - const insert = that.getInsertHead(); - const values = txs.map((cert) => that.getInsertValue(cert)); - if (txs.length) { - queries.push(insert + '\n' + values.join(',\n') + ';'); - } - if (queries.length) { - return that.exec(queries.join('\n')); - } - }); - - this.trimExpiredNonWrittenTxs = (limitTime) => that.exec("DELETE FROM txs WHERE NOT written AND blockstampTime <= " + limitTime) - - this.getTransactionByExtendedHash = (hash) => that.query("SELECT * FROM txs WHERE hash = ? OR v4_hash = ? OR v5_hash = ?", [hash, hash, hash]); - - /************************** - * SANDBOX STUFF - */ - - this.getSandboxTxs = () => that.query('SELECT * FROM sandbox_txs LIMIT ' + (that.sandbox.maxSize), []); - - this.sandbox = new SandBox(constants.SANDBOX_SIZE_TRANSACTIONS, this.getSandboxTxs.bind(this), (compared, reference) => { - if (compared.output_base < reference.output_base) { - return -1; - } - else if (compared.output_base > reference.output_base) { - return 1; - } - else if (compared.output_amount > reference.output_amount) { - return -1; - } - else if (compared.output_amount < reference.output_amount) { - return 1; - } - else { - return 0; - } - }); - - this.getSandboxRoom = () => this.sandbox.getSandboxRoom(); - this.setSandboxSize = (maxSize) => this.sandbox.maxSize = maxSize; -} diff --git a/app/lib/dal/sqliteDAL/TxsDAL.ts b/app/lib/dal/sqliteDAL/TxsDAL.ts new file mode 100644 index 000000000..1d2a542b0 --- /dev/null +++ b/app/lib/dal/sqliteDAL/TxsDAL.ts @@ -0,0 +1,257 @@ +import {AbstractSQLite} from "./AbstractSQLite"; +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {TransactionDTO} from "../../dto/TransactionDTO"; +import {SandBox} from "./SandBox"; + +const _ = require('underscore'); +const moment = require('moment'); +const constants = require('../../constants'); +const Transaction = require('../../entity/transaction'); + +export class DBTx { + hash: string + block_number: number | null + locktime: number + version: number + currency: string + comment: string + blockstamp: string + blockstampTime: number | null + time: number | null + inputs: string[] + unlocks: string[] + outputs: string[] + issuers: string[] + signatures: string[] + recipients: string[] + written: boolean + removed: boolean + received: boolean + output_base: number + output_amount: number + + static fromTransactionDTO(tx:TransactionDTO) { + const dbTx = new DBTx() + dbTx.hash = tx.hash + dbTx.locktime = tx.locktime + dbTx.version = tx.version + dbTx.currency = tx.currency + dbTx.blockstamp = tx.blockstamp + dbTx.blockstampTime = tx.blockstampTime + dbTx.comment = tx.comment || "" + dbTx.inputs = tx.inputs + dbTx.unlocks = tx.unlocks + dbTx.outputs = tx.outputs + dbTx.issuers = tx.issuers + dbTx.signatures = tx.signatures + dbTx.recipients = tx.outputsAsRecipients() + dbTx.written = false + dbTx.removed = false + dbTx.output_base = tx.outputs.reduce((sum, output) => sum + parseInt(output.split(':')[0]), 0) + dbTx.output_amount = tx.outputs.reduce((maxBase, output) => Math.max(maxBase, parseInt(output.split(':')[1])), 0) + return dbTx + } +} + +export class TxsDAL extends AbstractSQLite<DBTx> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'txs', + // PK fields + ['hash'], + // Fields + [ + 'hash', + 'block_number', + 'version', + 'currency', + 'comment', + 'blockstamp', + 'blockstampTime', + 'locktime', + 'received', + 'time', + 'written', + 'removed', + 'inputs', + 'unlocks', + 'outputs', + 'issuers', + 'signatures', + 'recipients', + 'output_base', + 'output_amount' + ], + // Arrays + ['inputs','unlocks','outputs','issuers','signatures','recipients'], + // Booleans + ['written','removed'], + // BigIntegers + [], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'hash CHAR(64) NOT NULL,' + + 'block_number INTEGER,' + + 'locktime INTEGER NOT NULL,' + + 'version INTEGER NOT NULL,' + + 'currency VARCHAR(50) NOT NULL,' + + 'comment VARCHAR(255) NOT NULL,' + + 'time DATETIME,' + + 'inputs TEXT NOT NULL,' + + 'unlocks TEXT NOT NULL,' + + 'outputs TEXT NOT NULL,' + + 'issuers TEXT NOT NULL,' + + 'signatures TEXT NOT NULL,' + + 'recipients TEXT NOT NULL,' + + 'written BOOLEAN NOT NULL,' + + 'removed BOOLEAN NOT NULL,' + + 'PRIMARY KEY (hash)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_txs_issuers ON txs (issuers);' + + 'CREATE INDEX IF NOT EXISTS idx_txs_written ON txs (written);' + + 'CREATE INDEX IF NOT EXISTS idx_txs_removed ON txs (removed);' + + 'CREATE INDEX IF NOT EXISTS idx_txs_hash ON txs (hash);' + + 'COMMIT;') + } + + getAllPending(versionMin:number): Promise<DBTx[]> { + return this.sqlFind({ + written: false, + removed: false, + version: { $gte: versionMin } + }) + } + + getTX(hash:string): Promise<DBTx> { + return this.sqlFindOne({ + hash: hash + }) + } + + async removeTX(hash:string) { + const tx = await this.sqlFindOne({ + hash: hash + }); + if (tx) { + tx.removed = true; + return this.saveEntity(tx); + } + return tx + } + + addLinked(tx:TransactionDTO, block_number:number, time:number) { + const dbTx = DBTx.fromTransactionDTO(tx) + dbTx.block_number = block_number + dbTx.time = time + dbTx.received = moment().unix() + dbTx.written = true + dbTx.removed = false + dbTx.hash = tx.getHash() + return this.saveEntity(dbTx) + } + + addPending(tx:TransactionDTO) { + const dbTx = DBTx.fromTransactionDTO(tx) + dbTx.received = moment().unix() + dbTx.written = false + dbTx.removed = false + dbTx.hash = tx.getHash() + return this.saveEntity(dbTx) + } + + getLinkedWithIssuer(pubkey:string) { + return this.sqlFind({ + issuers: { $contains: pubkey }, + written: true + }) + } + + async getLinkedWithRecipient(pubkey:string) { + const rows = await this.sqlFind({ + recipients: { $contains: pubkey }, + written: true + }) + // Which does not contains the key as issuer + return _.filter(rows, (row:DBTx) => row.issuers.indexOf(pubkey) === -1); + } + + getPendingWithIssuer(pubkey:string) { + return this.sqlFind({ + issuers: { $contains: pubkey }, + written: false, + removed: false + }) + } + + getPendingWithRecipient(pubkey:string) { + return this.sqlFind({ + recipients: { $contains: pubkey }, + written: false, + removed: false + }) + } + + insertBatchOfTxs(txs:DBTx[]) { + // // Be sure the recipients field are correctly updated + Transaction.statics.setRecipients(txs); + const queries = []; + const insert = this.getInsertHead(); + const values = txs.map((cert) => this.getInsertValue(cert)); + if (txs.length) { + queries.push(insert + '\n' + values.join(',\n') + ';'); + } + if (queries.length) { + this.exec(queries.join('\n')); + } + } + + trimExpiredNonWrittenTxs(limitTime:number) { + return this.exec("DELETE FROM txs WHERE NOT written AND blockstampTime <= " + limitTime) + } + + getTransactionByExtendedHash(hash:string) { + return this.query("SELECT * FROM txs WHERE hash = ? OR v4_hash = ? OR v5_hash = ?", [hash, hash, hash]) + } + + /************************** + * SANDBOX STUFF + */ + + getSandboxTxs() { + return this.query('SELECT * FROM sandbox_txs LIMIT ' + (this.sandbox.maxSize), []) + } + + sandbox = new SandBox(constants.SANDBOX_SIZE_TRANSACTIONS, this.getSandboxTxs.bind(this), (compared:DBTx, reference:DBTx) => { + if (compared.output_base < reference.output_base) { + return -1; + } + else if (compared.output_base > reference.output_base) { + return 1; + } + else if (compared.output_amount > reference.output_amount) { + return -1; + } + else if (compared.output_amount < reference.output_amount) { + return 1; + } + else { + return 0; + } + }) + + getSandboxRoom() { + return this.sandbox.getSandboxRoom() + } + + setSandboxSize(maxSize:number) { + this.sandbox.maxSize = maxSize + } +} diff --git a/app/lib/dal/sqliteDAL/WalletDAL.js b/app/lib/dal/sqliteDAL/WalletDAL.js deleted file mode 100644 index 88c5eb215..000000000 --- a/app/lib/dal/sqliteDAL/WalletDAL.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const AbstractSQLite = require('./AbstractSQLite'); - -module.exports = WalletDAL; - -/** - * Facility table saving the current state of a wallet. - * @param driver SQL driver for making SQL requests. - * @constructor - */ -function WalletDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - const that = this; - - this.table = 'wallet'; - this.fields = [ - 'conditions', - 'balance' - ]; - this.arrays = []; - this.booleans = []; - this.pkFields = ['conditions']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'conditions TEXT NOT NULL,' + - 'balance INTEGER NOT NULL,' + - 'PRIMARY KEY (conditions)' + - ');' + - 'CREATE INDEX IF NOT EXISTS wallet_balance ON wallet(balance);' + - 'COMMIT;', []); - }); - - this.getWallet = (conditions) => this.sqlFindOne({ conditions }); - - this.saveWallet = (wallet) => this.saveEntity(wallet); -} diff --git a/app/lib/dal/sqliteDAL/WalletDAL.ts b/app/lib/dal/sqliteDAL/WalletDAL.ts new file mode 100644 index 000000000..86c9f31b4 --- /dev/null +++ b/app/lib/dal/sqliteDAL/WalletDAL.ts @@ -0,0 +1,56 @@ +import {SQLiteDriver} from "../drivers/SQLiteDriver"; +import {AbstractSQLite} from "./AbstractSQLite"; + +export interface DBWallet { + conditions: string + balance: number +} + +/** + * Facility table saving the current state of a wallet. + * @param driver SQL driver for making SQL requests. + * @constructor + */ +export class WalletDAL extends AbstractSQLite<DBWallet> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'wallet', + // PK fields + ['conditions'], + // Fields + [ + 'conditions', + 'balance' + ], + // Arrays + [], + // Booleans + [], + // BigIntegers + ['monetaryMass'], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'conditions TEXT NOT NULL,' + + 'balance INTEGER NOT NULL,' + + 'PRIMARY KEY (conditions)' + + ');' + + 'CREATE INDEX IF NOT EXISTS wallet_balance ON wallet(balance);' + + 'COMMIT;') + } + + getWallet(conditions:string) { + return this.sqlFindOne({ conditions }) + } + + saveWallet(wallet:DBWallet) { + return this.saveEntity(wallet) + } +} diff --git a/app/lib/dal/sqliteDAL/index/BIndexDAL.js b/app/lib/dal/sqliteDAL/index/BIndexDAL.js deleted file mode 100644 index bee032011..000000000 --- a/app/lib/dal/sqliteDAL/index/BIndexDAL.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const AbstractSQLite = require('./../AbstractSQLite'); - -module.exports = BIndexDAL; - -function BIndexDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - - const that = this; - - this.table = 'b_index'; - this.fields = [ - 'version', - 'bsize', - 'hash', - 'issuer', - 'time', - 'number', - 'membersCount', - 'issuersCount', - 'issuersFrame', - 'issuersFrameVar', - 'issuerDiff', - 'avgBlockSize', - 'medianTime', - 'dividend', - 'mass', - 'massReeval', - 'unitBase', - 'powMin', - 'udTime', - 'udReevalTime', - 'diffNumber', - 'speed' - ]; - this.arrays = []; - this.bigintegers = ['mass', 'massReeval']; - this.booleans = ['leaving']; - this.pkFields = ['number']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'version INTEGER NOT NULL,' + - 'bsize INTEGER NOT NULL,' + - 'hash VARCHAR(64) NOT NULL,' + - 'issuer VARCHAR(50) NOT NULL,' + - 'time INTEGER NOT NULL,' + - 'number INTEGER NOT NULL,' + - 'membersCount INTEGER NOT NULL,' + - 'issuersCount INTEGER NOT NULL,' + - 'issuersFrame INTEGER NOT NULL,' + - 'issuersFrameVar INTEGER NOT NULL,' + - 'issuerDiff INTEGER NULL,' + - 'avgBlockSize INTEGER NOT NULL,' + - 'medianTime INTEGER NOT NULL,' + - 'dividend INTEGER NOT NULL,' + - 'mass VARCHAR(100) NOT NULL,' + - 'unitBase INTEGER NOT NULL,' + - 'powMin INTEGER NOT NULL,' + - 'udTime INTEGER NOT NULL,' + - 'udReevalTime INTEGER NOT NULL,' + - 'diffNumber INTEGER NOT NULL,' + - 'speed FLOAT NOT NULL,' + - 'PRIMARY KEY (number)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_bindex_number ON b_index (number);' + - 'CREATE INDEX IF NOT EXISTS idx_bindex_issuer ON b_index (issuer);' + - 'COMMIT;', []); - }); - - /** - * Get HEAD~n - * @param n Position - */ - this.head = (n) => co(function*() { - if (!n) { - throw "Cannot read HEAD~0, which is the incoming block" - } - const headRecords = yield that.query('SELECT * FROM ' + that.table + ' ORDER BY number DESC LIMIT 1 OFFSET ?', [n - 1]); - return headRecords[0]; - }); - - /** - * Get the last record available in bindex - */ - this.tail = () => co(function*() { - const tailRecords = yield that.query('SELECT * FROM ' + that.table + ' ORDER BY number ASC LIMIT 1', []); - return tailRecords[0]; - }); - - /** - * Get HEAD~n..m - * @param n - * @param m - */ - this.range = (n, m) => co(function*() { - const count = m - n + 1; - return that.query('SELECT * FROM ' + that.table + ' ORDER BY number DESC LIMIT ? OFFSET ?', [count, n - 1]); - }); - - this.removeBlock = (number) => that.exec('DELETE FROM ' + that.table + ' WHERE number = ' + number); - - this.trimBlocks = (maxnumber) => that.exec('DELETE FROM ' + that.table + ' WHERE number < ' + maxnumber); -} diff --git a/app/lib/dal/sqliteDAL/index/BIndexDAL.ts b/app/lib/dal/sqliteDAL/index/BIndexDAL.ts new file mode 100644 index 000000000..24de1d84d --- /dev/null +++ b/app/lib/dal/sqliteDAL/index/BIndexDAL.ts @@ -0,0 +1,117 @@ +import {AbstractSQLite} from "../AbstractSQLite"; +import {DBHead} from "../../../db/DBHead"; +import {SQLiteDriver} from "../../drivers/SQLiteDriver"; + +export class BIndexDAL extends AbstractSQLite<DBHead> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'b_index', + // PK fields + ['number'], + // Fields + [ + 'version', + 'bsize', + 'hash', + 'issuer', + 'time', + 'number', + 'membersCount', + 'issuersCount', + 'issuersFrame', + 'issuersFrameVar', + 'issuerDiff', + 'avgBlockSize', + 'medianTime', + 'dividend', + 'mass', + 'massReeval', + 'unitBase', + 'powMin', + 'udTime', + 'udReevalTime', + 'diffNumber', + 'speed' + ], + // Arrays + [], + // Booleans + ['leaving'], + // BigIntegers + ['mass', 'massReeval'], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'version INTEGER NOT NULL,' + + 'bsize INTEGER NOT NULL,' + + 'hash VARCHAR(64) NOT NULL,' + + 'issuer VARCHAR(50) NOT NULL,' + + 'time INTEGER NOT NULL,' + + 'number INTEGER NOT NULL,' + + 'membersCount INTEGER NOT NULL,' + + 'issuersCount INTEGER NOT NULL,' + + 'issuersFrame INTEGER NOT NULL,' + + 'issuersFrameVar INTEGER NOT NULL,' + + 'issuerDiff INTEGER NULL,' + + 'avgBlockSize INTEGER NOT NULL,' + + 'medianTime INTEGER NOT NULL,' + + 'dividend INTEGER NOT NULL,' + + 'mass VARCHAR(100) NOT NULL,' + + 'unitBase INTEGER NOT NULL,' + + 'powMin INTEGER NOT NULL,' + + 'udTime INTEGER NOT NULL,' + + 'udReevalTime INTEGER NOT NULL,' + + 'diffNumber INTEGER NOT NULL,' + + 'speed FLOAT NOT NULL,' + + 'PRIMARY KEY (number)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_bindex_number ON b_index (number);' + + 'CREATE INDEX IF NOT EXISTS idx_bindex_issuer ON b_index (issuer);' + + 'COMMIT;') + } + + /** + * Get HEAD~n + * @param n Position + */ + async head(n:number) { + if (!n) { + throw "Cannot read HEAD~0, which is the incoming block" + } + const headRecords = await this.query('SELECT * FROM ' + this.table + ' ORDER BY number DESC LIMIT 1 OFFSET ?', [n - 1]); + return headRecords[0]; + } + + /** + * Get the last record available in bindex + */ + async tail() { + const tailRecords = await this.query('SELECT * FROM ' + this.table + ' ORDER BY number ASC LIMIT 1', []); + return tailRecords[0]; + } + + /** + * Get HEAD~n..m + * @param n + * @param m + */ + range(n:number, m:number) { + const count = m - n + 1; + return this.query('SELECT * FROM ' + this.table + ' ORDER BY number DESC LIMIT ? OFFSET ?', [count, n - 1]); + } + + removeBlock(number:number) { + return this.exec('DELETE FROM ' + this.table + ' WHERE number = ' + number) + } + + trimBlocks(maxnumber:number) { + return this.exec('DELETE FROM ' + this.table + ' WHERE number < ' + maxnumber) + } +} diff --git a/app/lib/dal/sqliteDAL/index/CIndexDAL.js b/app/lib/dal/sqliteDAL/index/CIndexDAL.js deleted file mode 100644 index dde0e9394..000000000 --- a/app/lib/dal/sqliteDAL/index/CIndexDAL.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const constants = require('./../../../constants'); -const common = require('duniter-common'); -const indexer = require('../../../indexer').Indexer -const AbstractSQLite = require('./../AbstractSQLite'); -const AbstractIndex = require('./../AbstractIndex'); - -module.exports = CIndexDAL; - -function CIndexDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - AbstractIndex.call(this, driver); - - const that = this; - - this.table = 'c_index'; - this.fields = [ - 'op', - 'issuer', - 'receiver', - 'created_on', - 'written_on', - 'writtenOn', - 'sig', - 'expires_on', - 'expired_on', - 'chainable_on', - 'from_wid', - 'to_wid' - ]; - this.arrays = []; - this.bigintegers = []; - this.booleans = []; - this.pkFields = ['op', 'issuer', 'receiver', 'written_on']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'issuer VARCHAR(50) NOT NULL,' + - 'receiver VARCHAR(50) NOT NULL,' + - 'created_on VARCHAR(80) NOT NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'sig VARCHAR(100) NULL,' + - 'expires_on INTEGER NULL,' + - 'expired_on INTEGER NULL,' + - 'chainable_on INTEGER NULL,' + - 'from_wid INTEGER NULL,' + - 'to_wid INTEGER NULL,' + - 'PRIMARY KEY (op,issuer,receiver,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_cindex_issuer ON c_index (issuer);' + - 'CREATE INDEX IF NOT EXISTS idx_cindex_receiver ON c_index (receiver);' + - 'CREATE INDEX IF NOT EXISTS idx_cindex_chainable_on ON c_index (chainable_on);' + - 'COMMIT;', []); - }); - - this.reducablesFrom = (from) => co(function*() { - const reducables = yield that.query('SELECT * FROM ' + that.table + ' WHERE issuer = ? ORDER BY CAST(written_on as integer) ASC', [from]); - return indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver', 'created_on']); - }); - - this.trimExpiredCerts = (belowNumber) => co(function*() { - const toDelete = yield that.query('SELECT * FROM ' + that.table + ' WHERE expired_on > ? AND CAST(written_on as int) < ?', [0, belowNumber]); - for (const row of toDelete) { - yield that.exec("DELETE FROM " + that.table + " " + - "WHERE issuer like '" + row.issuer + "' " + - "AND receiver = '" + row.receiver + "' " + - "AND created_on like '" + row.created_on + "'"); - } - }); - - this.getWrittenOn = (blockstamp) => that.sqlFind({ written_on: blockstamp }); - - this.findExpired = (medianTime) => that.query('SELECT * FROM ' + that.table + ' c1 WHERE expires_on <= ? ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [medianTime, common.constants.IDX_UPDATE]); - - this.getValidLinksTo = (receiver) => that.query('SELECT * FROM ' + that.table + ' c1 ' + - 'WHERE c1.receiver = ? ' + - 'AND c1.expired_on = 0 ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [receiver, common.constants.IDX_UPDATE]); - - this.getValidLinksFrom = (issuer) => that.query('SELECT * FROM ' + that.table + ' c1 ' + - 'WHERE c1.issuer = ? ' + - 'AND c1.expired_on = 0 ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [issuer, common.constants.IDX_UPDATE]); - - this.existsNonReplayableLink = (issuer, receiver) => co(function*() { - const results = yield that.query('SELECT * FROM ' + that.table + ' c1 ' + - 'WHERE c1.issuer = ? ' + - 'AND c1.receiver = ? ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [issuer, receiver, common.constants.IDX_UPDATE]); - return results.length > 0; - }); - - this.removeBlock = (blockstamp) => that.exec('DELETE FROM ' + that.table + ' WHERE written_on = \'' + blockstamp + '\''); -} diff --git a/app/lib/dal/sqliteDAL/index/CIndexDAL.ts b/app/lib/dal/sqliteDAL/index/CIndexDAL.ts new file mode 100644 index 000000000..60c0bf938 --- /dev/null +++ b/app/lib/dal/sqliteDAL/index/CIndexDAL.ts @@ -0,0 +1,138 @@ +import {AbstractIndex} from "../AbstractIndex"; +import {SQLiteDriver} from "../../drivers/SQLiteDriver"; +import {CindexEntry} from "../../../indexer"; + +const constants = require('./../../../constants'); +const common = require('duniter-common'); +const indexer = require('../../../indexer').Indexer + +export class CIndexDAL extends AbstractIndex<CindexEntry> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'c_index', + // PK fields + ['op', 'issuer', 'receiver', 'written_on'], + // Fields + [ + 'op', + 'issuer', + 'receiver', + 'created_on', + 'written_on', + 'writtenOn', + 'sig', + 'expires_on', + 'expired_on', + 'chainable_on', + 'from_wid', + 'to_wid' + ], + // Arrays + [], + // Booleans + [], + // BigIntegers + [], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'op VARCHAR(10) NOT NULL,' + + 'issuer VARCHAR(50) NOT NULL,' + + 'receiver VARCHAR(50) NOT NULL,' + + 'created_on VARCHAR(80) NOT NULL,' + + 'written_on VARCHAR(80) NOT NULL,' + + 'sig VARCHAR(100) NULL,' + + 'expires_on INTEGER NULL,' + + 'expired_on INTEGER NULL,' + + 'chainable_on INTEGER NULL,' + + 'from_wid INTEGER NULL,' + + 'to_wid INTEGER NULL,' + + 'PRIMARY KEY (op,issuer,receiver,written_on)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_cindex_issuer ON c_index (issuer);' + + 'CREATE INDEX IF NOT EXISTS idx_cindex_receiver ON c_index (receiver);' + + 'CREATE INDEX IF NOT EXISTS idx_cindex_chainable_on ON c_index (chainable_on);' + + 'COMMIT;') + } + + async reducablesFrom(from:string) { + const reducables = await this.query('SELECT * FROM ' + this.table + ' WHERE issuer = ? ORDER BY CAST(written_on as integer) ASC', [from]); + return indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver', 'created_on']); + } + + async trimExpiredCerts(belowNumber:number) { + const toDelete = await this.query('SELECT * FROM ' + this.table + ' WHERE expired_on > ? AND CAST(written_on as int) < ?', [0, belowNumber]) + for (const row of toDelete) { + await this.exec("DELETE FROM " + this.table + " " + + "WHERE issuer like '" + row.issuer + "' " + + "AND receiver = '" + row.receiver + "' " + + "AND created_on like '" + row.created_on + "'"); + } + } + + getWrittenOn(blockstamp:string) { + return this.sqlFind({ written_on: blockstamp }) + } + + findExpired(medianTime:number) { + return this.query('SELECT * FROM ' + this.table + ' c1 WHERE expires_on <= ? ' + + 'AND NOT EXISTS (' + + ' SELECT * FROM c_index c2' + + ' WHERE c1.issuer = c2.issuer' + + ' AND c1.receiver = c2.receiver' + + ' AND c1.created_on = c2.created_on' + + ' AND c2.op = ?' + + ')', [medianTime, common.constants.IDX_UPDATE]) + } + + getValidLinksTo(receiver:string) { + return this.query('SELECT * FROM ' + this.table + ' c1 ' + + 'WHERE c1.receiver = ? ' + + 'AND c1.expired_on = 0 ' + + 'AND NOT EXISTS (' + + ' SELECT * FROM c_index c2' + + ' WHERE c1.issuer = c2.issuer' + + ' AND c1.receiver = c2.receiver' + + ' AND c1.created_on = c2.created_on' + + ' AND c2.op = ?' + + ')', [receiver, common.constants.IDX_UPDATE]) + } + + getValidLinksFrom(issuer:string) { + return this.query('SELECT * FROM ' + this.table + ' c1 ' + + 'WHERE c1.issuer = ? ' + + 'AND c1.expired_on = 0 ' + + 'AND NOT EXISTS (' + + ' SELECT * FROM c_index c2' + + ' WHERE c1.issuer = c2.issuer' + + ' AND c1.receiver = c2.receiver' + + ' AND c1.created_on = c2.created_on' + + ' AND c2.op = ?' + + ')', [issuer, common.constants.IDX_UPDATE]) + } + + async existsNonReplayableLink(issuer:string, receiver:string) { + const results = await this.query('SELECT * FROM ' + this.table + ' c1 ' + + 'WHERE c1.issuer = ? ' + + 'AND c1.receiver = ? ' + + 'AND NOT EXISTS (' + + ' SELECT * FROM c_index c2' + + ' WHERE c1.issuer = c2.issuer' + + ' AND c1.receiver = c2.receiver' + + ' AND c1.created_on = c2.created_on' + + ' AND c2.op = ?' + + ')', [issuer, receiver, common.constants.IDX_UPDATE]); + return results.length > 0; + } + + removeBlock(blockstamp:string) { + return this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') + } +} diff --git a/app/lib/dal/sqliteDAL/index/IIndexDAL.js b/app/lib/dal/sqliteDAL/index/IIndexDAL.js deleted file mode 100644 index 4030b5905..000000000 --- a/app/lib/dal/sqliteDAL/index/IIndexDAL.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const _ = require('underscore'); -const indexer = require('../../../indexer').Indexer -const AbstractSQLite = require('./../AbstractSQLite'); -const AbstractIndex = require('./../AbstractIndex'); - -module.exports = IIndexDAL; - -function IIndexDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - AbstractIndex.call(this); - - const that = this; - - this.table = 'i_index'; - this.fields = [ - 'op', - 'uid', - 'pub', - 'hash', - 'sig', - 'created_on', - 'written_on', - 'writtenOn', - 'member', - 'wasMember', - 'kick', - 'wotb_id' - ]; - this.arrays = []; - this.bigintegers = []; - this.booleans = ['member', 'wasMember', 'kick']; - this.pkFields = ['op', 'pub', 'created_on', 'written_on']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'uid VARCHAR(100) NULL,' + - 'pub VARCHAR(50) NOT NULL,' + - 'hash VARCHAR(80) NULL,' + - 'sig VARCHAR(80) NULL,' + - 'created_on VARCHAR(80) NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'member BOOLEAN NULL,' + - 'wasMember BOOLEAN NULL,' + - 'kick BOOLEAN NULL,' + - 'wotb_id INTEGER NULL,' + - 'PRIMARY KEY (op,pub,created_on,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_iindex_pub ON i_index (pub);' + - 'COMMIT;', []); - }); - - this.getMembers = () => co(function*() { - // All those who has been subject to, or who are currently subject to kicking. Make one result per pubkey. - const pubkeys = yield that.query('SELECT DISTINCT(pub) FROM ' + that.table); - // We get the full representation for each member - const reduced = yield pubkeys.map((entry) => co(function*() { - const reducable = yield that.reducable(entry.pub); - return indexer.DUP_HELPERS.reduce(reducable); - })); - // Filter on those to be kicked, return their pubkey - const filtered = _.filter(reduced, (entry) => entry.member); - return filtered.map(toCorrectEntity); - }); - - this.getMembersPubkeys = () => this.query('SELECT i1.pub ' + - 'FROM i_index i1 ' + - 'WHERE i1.member ' + - 'AND CAST(i1.written_on as int) = (' + - ' SELECT MAX(CAST(i2.written_on as int)) ' + - ' FROM i_index i2 ' + - ' WHERE i1.pub = i2.pub ' + - ' AND i2.member IS NOT NULL' + - ')') - - this.getLatestMember = () => co(function*() { - const max_wotb_id = (yield that.query('SELECT MAX(wotb_id) as max_wotb_id FROM ' + that.table))[0].max_wotb_id; - return entityOrNull('wotb_id', max_wotb_id); - }); - - this.getToBeKickedPubkeys = () => co(function*() { - // All those who has been subject to, or who are currently subject to kicking. Make one result per pubkey. - const reducables = indexer.DUP_HELPERS.reduceBy(yield that.sqlFind({ kick: true }), ['pub']); - // We get the full representation for each member - const reduced = yield reducables.map((entry) => co(function*() { - const reducable = yield that.reducable(entry.pub); - return indexer.DUP_HELPERS.reduce(reducable); - })); - // Filter on those to be kicked, return their pubkey - return _.filter(reduced, (entry) => entry.kick).map((entry) => entry.pub); - }); - - this.searchThoseMatching = (search) => co(function*() { - const reducables = indexer.DUP_HELPERS.reduceBy(yield that.sqlFindLikeAny({ - pub: "%" + search + "%", - uid: "%" + search + "%" - }), ['pub']); - // We get the full representation for each member - return yield reducables.map((entry) => co(function*() { - return toCorrectEntity(indexer.DUP_HELPERS.reduce(yield that.reducable(entry.pub))); - })); - }); - - this.getFromPubkey = (pubkey) => entityOrNull('pub', pubkey); - - this.getFromUID = (uid) => entityOrNull('uid', uid); - - this.getFromHash = (hash) => entityOrNull('hash', hash, 'pub'); - - this.reducable = (pub) => this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as integer) ASC', [pub]); - - this.removeBlock = (blockstamp) => that.exec('DELETE FROM ' + that.table + ' WHERE written_on = \'' + blockstamp + '\''); - - function entityOrNull(field, value, retrieveOnField) { - return co(function*() { - let reducable = yield that.query('SELECT * FROM ' + that.table + ' WHERE ' + field + ' = ?', [value]); - if (reducable.length) { - if (retrieveOnField) { - // Force full retrieval on `pub` field - reducable = yield that.query('SELECT * FROM ' + that.table + ' WHERE pub = ? ORDER BY CAST(written_on as int) ASC', [reducable[0].pub]); - } - return toCorrectEntity(indexer.DUP_HELPERS.reduce(reducable)); - } - return null; - }); - } - - function toCorrectEntity(row) { - // Old field - row.pubkey = row.pub; - row.buid = row.created_on; - row.revocation_sig = null; - return row; - } -} diff --git a/app/lib/dal/sqliteDAL/index/IIndexDAL.ts b/app/lib/dal/sqliteDAL/index/IIndexDAL.ts new file mode 100644 index 000000000..813e34f67 --- /dev/null +++ b/app/lib/dal/sqliteDAL/index/IIndexDAL.ts @@ -0,0 +1,175 @@ +import {SQLiteDriver} from "../../drivers/SQLiteDriver"; +import {AbstractIndex} from "../AbstractIndex"; +import {IindexEntry, Indexer} from "../../../indexer"; + +const _ = require('underscore'); + +export interface OldIindexEntry extends IindexEntry { + pubkey: string + buid: string | null + revocation_sig:string | null +} + +export class IIndexDAL extends AbstractIndex<IindexEntry> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'i_index', + // PK fields + ['op', 'pub', 'created_on', 'written_on'], + // Fields + [ + 'op', + 'uid', + 'pub', + 'hash', + 'sig', + 'created_on', + 'written_on', + 'writtenOn', + 'member', + 'wasMember', + 'kick', + 'wotb_id' + ], + // Arrays + [], + // Booleans + ['member', 'wasMember', 'kick'], + // BigIntegers + [], + // Transient + [] + ) + } + + init() { + return this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'op VARCHAR(10) NOT NULL,' + + 'uid VARCHAR(100) NULL,' + + 'pub VARCHAR(50) NOT NULL,' + + 'hash VARCHAR(80) NULL,' + + 'sig VARCHAR(80) NULL,' + + 'created_on VARCHAR(80) NULL,' + + 'written_on VARCHAR(80) NOT NULL,' + + 'member BOOLEAN NULL,' + + 'wasMember BOOLEAN NULL,' + + 'kick BOOLEAN NULL,' + + 'wotb_id INTEGER NULL,' + + 'PRIMARY KEY (op,pub,created_on,written_on)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_iindex_pub ON i_index (pub);' + + 'COMMIT;') + } + + async getMembers() { + // All those who has been subject to, or who are currently subject to kicking. Make one result per pubkey. + const pubkeys = await this.query('SELECT DISTINCT(pub) FROM ' + this.table); + // We get the full representation for each member + const reduced = await Promise.all(pubkeys.map(async (entry) => { + const reducable = await this.reducable(entry.pub); + return Indexer.DUP_HELPERS.reduce(reducable); + })); + // Filter on those to be kicked, return their pubkey + const filtered = _.filter(reduced, (entry:IindexEntry) => entry.member); + return filtered.map((t:IindexEntry) => this.toCorrectEntity(t)) + } + + getMembersPubkeys() { + return this.query('SELECT i1.pub ' + + 'FROM i_index i1 ' + + 'WHERE i1.member ' + + 'AND CAST(i1.written_on as int) = (' + + ' SELECT MAX(CAST(i2.written_on as int)) ' + + ' FROM i_index i2 ' + + ' WHERE i1.pub = i2.pub ' + + ' AND i2.member IS NOT NULL' + + ')') + } + + async getLatestMember() { + const res:any = (await this.query('SELECT MAX(wotb_id) as max_wotb_id FROM ' + this.table))[0] + const max_wotb_id = res.max_wotb_id + return this.entityOrNull('wotb_id', max_wotb_id) + } + + async getToBeKickedPubkeys() { + // All those who has been subject to, or who are currently subject to kicking. Make one result per pubkey. + const reducables = Indexer.DUP_HELPERS.reduceBy(await this.sqlFind({ kick: true }), ['pub']); + // We get the full representation for each member + const reduced = await Promise.all(reducables.map(async (entry) => { + const reducable = await this.reducable(entry.pub); + return Indexer.DUP_HELPERS.reduce(reducable); + })) + // Filter on those to be kicked, return their pubkey + return _.filter(reduced, (entry:IindexEntry) => entry.kick).map((entry:IindexEntry) => entry.pub); + } + + async searchThoseMatching(search:string) { + const reducables = Indexer.DUP_HELPERS.reduceBy(await this.sqlFindLikeAny({ + pub: "%" + search + "%", + uid: "%" + search + "%" + }), ['pub']); + // We get the full representation for each member + return await Promise.all(reducables.map(async (entry) => { + return this.toCorrectEntity(Indexer.DUP_HELPERS.reduce(await this.reducable(entry.pub))) + })) + } + + getFromPubkey(pubkey:string) { + return this.entityOrNull('pub', pubkey) + } + + getFromUID(uid:string) { + return this.entityOrNull('uid', uid) + } + + getFromHash(hash:string) { + return this.entityOrNull('hash', hash, true) + } + + reducable(pub:string) { + return this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as integer) ASC', [pub]) + } + + removeBlock(blockstamp:string) { + return this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') + } + + private async entityOrNull(field:string, value:any, retrieveOnField:boolean = false) { + let reducable = await this.query('SELECT * FROM ' + this.table + ' WHERE ' + field + ' = ?', [value]); + if (reducable.length) { + if (retrieveOnField) { + // Force full retrieval on `pub` field + reducable = await this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as int) ASC', [reducable[0].pub]); + } + return this.toCorrectEntity(Indexer.DUP_HELPERS.reduce(reducable)); + } + return null; + } + + private toCorrectEntity(row:IindexEntry): OldIindexEntry { + // Old field + return { + pubkey: row.pub, + pub: row.pub, + buid: row.created_on, + revocation_sig: null, + uid: row.uid, + hash: row.hash, + sig: row.sig, + created_on: row.created_on, + member: row.member, + wasMember: row.wasMember, + kick: row.kick, + wotb_id: row.wotb_id, + age: row.age, + index: row.index, + op: row.op, + writtenOn: row.writtenOn, + written_on: row.written_on + } + } +} diff --git a/app/lib/dal/sqliteDAL/index/MIndexDAL.js b/app/lib/dal/sqliteDAL/index/MIndexDAL.js deleted file mode 100644 index 77faa633d..000000000 --- a/app/lib/dal/sqliteDAL/index/MIndexDAL.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const co = require('co'); -const indexer = require('../../../indexer').Indexer -const AbstractSQLite = require('./../AbstractSQLite'); -const AbstractIndex = require('./../AbstractIndex'); - -module.exports = MIndexDAL; - -function MIndexDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - AbstractIndex.call(this); - - const that = this; - - this.table = 'm_index'; - this.fields = [ - 'op', - 'pub', - 'created_on', - 'written_on', - 'writtenOn', - 'expires_on', - 'expired_on', - 'revokes_on', - 'revoked_on', - 'chainable_on', - 'leaving', - 'revocation' - ]; - this.arrays = []; - this.bigintegers = []; - this.booleans = ['leaving']; - this.pkFields = ['op', 'pub', 'created_on', 'written_on']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'pub VARCHAR(50) NOT NULL,' + - 'created_on VARCHAR(80) NOT NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'expires_on INTEGER NULL,' + - 'expired_on INTEGER NULL,' + - 'revokes_on INTEGER NULL,' + - 'revoked_on INTEGER NULL,' + - 'leaving BOOLEAN NULL,' + - 'revocation VARCHAR(80) NULL,' + - 'PRIMARY KEY (op,pub,created_on,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_mindex_pub ON m_index (pub);' + - 'COMMIT;', []); - }); - - this.getReducedMS = (pub) => co(function*() { - const reducables = yield that.reducable(pub); - if (reducables.length) { - return indexer.DUP_HELPERS.reduce(reducables); - } - return null; - }); - - this.reducable = (pub) => this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as integer) ASC', [pub]); - - this.removeBlock = (blockstamp) => that.exec('DELETE FROM ' + that.table + ' WHERE written_on = \'' + blockstamp + '\''); -} diff --git a/app/lib/dal/sqliteDAL/index/MIndexDAL.ts b/app/lib/dal/sqliteDAL/index/MIndexDAL.ts new file mode 100644 index 000000000..7f3e151a9 --- /dev/null +++ b/app/lib/dal/sqliteDAL/index/MIndexDAL.ts @@ -0,0 +1,73 @@ +import {SQLiteDriver} from "../../drivers/SQLiteDriver"; +import {AbstractIndex} from "../AbstractIndex"; +import {Indexer, MindexEntry} from "../../../indexer"; + +export class MIndexDAL extends AbstractIndex<MindexEntry> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 'm_index', + // PK fields + ['op', 'pub', 'created_on', 'written_on'], + // Fields + [ + 'op', + 'pub', + 'created_on', + 'written_on', + 'writtenOn', + 'expires_on', + 'expired_on', + 'revokes_on', + 'revoked_on', + 'chainable_on', + 'leaving', + 'revocation' + ], + // Arrays + [], + // Booleans + ['leaving'], + // BigIntegers + [], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'op VARCHAR(10) NOT NULL,' + + 'pub VARCHAR(50) NOT NULL,' + + 'created_on VARCHAR(80) NOT NULL,' + + 'written_on VARCHAR(80) NOT NULL,' + + 'expires_on INTEGER NULL,' + + 'expired_on INTEGER NULL,' + + 'revokes_on INTEGER NULL,' + + 'revoked_on INTEGER NULL,' + + 'leaving BOOLEAN NULL,' + + 'revocation VARCHAR(80) NULL,' + + 'PRIMARY KEY (op,pub,created_on,written_on)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_mindex_pub ON m_index (pub);' + + 'COMMIT;') + } + + async getReducedMS(pub:string) { + const reducables = await this.reducable(pub); + if (reducables.length) { + return Indexer.DUP_HELPERS.reduce(reducables); + } + return null; + } + + reducable(pub:string) { + return this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as integer) ASC', [pub]) +} + + async removeBlock(blockstamp:string) { + return this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') + } +} diff --git a/app/lib/dal/sqliteDAL/index/SIndexDAL.js b/app/lib/dal/sqliteDAL/index/SIndexDAL.js deleted file mode 100644 index f5e5e8413..000000000 --- a/app/lib/dal/sqliteDAL/index/SIndexDAL.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Created by cgeek on 22/08/15. - */ - -const _ = require('underscore'); -const co = require('co'); -const common = require('duniter-common'); -const indexer = require('../../../indexer').Indexer -const constants = require('../../../constants'); -const AbstractSQLite = require('./../AbstractSQLite'); -const AbstractIndex = require('./../AbstractIndex'); - -module.exports = SIndexDAL; - -function SIndexDAL(driver) { - - "use strict"; - - AbstractSQLite.call(this, driver); - AbstractIndex.call(this, driver); - - const that = this; - - this.table = 's_index'; - this.fields = [ - 'op', - 'tx', - 'identifier', - 'pos', - 'created_on', - 'written_on', - 'writtenOn', - 'written_time', - 'amount', - 'base', - 'locktime', - 'consumed', - 'conditions' - ]; - this.arrays = []; - this.bigintegers = []; - this.booleans = ['consumed']; - this.pkFields = ['op', 'identifier', 'pos', 'written_on']; - this.translated = {}; - - this.init = () => co(function *() { - return that.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + that.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'tx VARCHAR(80) NULL,' + - 'identifier VARCHAR(64) NOT NULL,' + - 'pos INTEGER NOT NULL,' + - 'created_on VARCHAR(80) NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'written_time INTEGER NOT NULL,' + - 'amount INTEGER NULL,' + - 'base INTEGER NULL,' + - 'locktime INTEGER NULL,' + - 'consumed BOOLEAN NOT NULL,' + - 'conditions TEXT,' + - 'PRIMARY KEY (op,identifier,pos,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_sindex_identifier ON s_index (identifier);' + - 'CREATE INDEX IF NOT EXISTS idx_sindex_pos ON s_index (pos);' + - 'COMMIT;', []); - }); - - this.removeBlock = (blockstamp) => that.exec('DELETE FROM ' + that.table + ' WHERE written_on = \'' + blockstamp + '\''); - - this.getSource = (identifier, pos) => co(function*() { - const reducable = yield that.query('SELECT * FROM ' + that.table + ' s1 ' + - 'WHERE s1.identifier = ? ' + - 'AND s1.pos = ? ' + - 'ORDER BY op ASC', [identifier, pos]); - if (reducable.length == 0) { - return null; - } else { - const src = indexer.DUP_HELPERS.reduce(reducable); - src.type = src.tx ? 'T' : 'D'; - return src; - } - }); - - this.getUDSources = (pubkey) => co(function*() { - const reducables = yield that.query('SELECT * FROM ' + that.table + ' s1 ' + - 'WHERE conditions = ? ' + - 'AND s1.tx IS NULL ' + - 'ORDER BY op ASC', ['SIG(' + pubkey + ')']); - const reduced = indexer.DUP_HELPERS.reduceBy(reducables, ['identifier', 'pos']).map((src) => { - src.type = src.tx ? 'T' : 'D'; - return src; - }); - return _.sortBy(reduced, (row) => row.type == 'D' ? 0 : 1); - }); - - this.getAvailableForPubkey = (pubkey) => this.getAvailableForConditions('%SIG(' + pubkey + ')%'); - - this.getAvailableForConditions = (conditionsStr) => co(function*() { - const potentials = yield that.query('SELECT * FROM ' + that.table + ' s1 ' + - 'WHERE s1.op = ? ' + - 'AND conditions LIKE ? ' + - 'AND NOT EXISTS (' + - ' SELECT * ' + - ' FROM s_index s2 ' + - ' WHERE s2.identifier = s1.identifier ' + - ' AND s2.pos = s1.pos ' + - ' AND s2.op = ?' + - ') ' + - 'ORDER BY CAST(SUBSTR(written_on, 0, INSTR(written_on, "-")) as number)', [common.constants.IDX_CREATE, conditionsStr, common.constants.IDX_UPDATE]); - const sources = potentials.map((src) => { - src.type = src.tx ? 'T' : 'D'; - return src; - }); - return _.sortBy(sources, (row) => row.type == 'D' ? 0 : 1); - }); - - this.trimConsumedSource = (belowNumber) => co(function*() { - const toDelete = yield that.query('SELECT * FROM ' + that.table + ' WHERE consumed AND CAST(written_on as int) < ?', [belowNumber]); - const queries = []; - for (const row of toDelete) { - const sql = "DELETE FROM " + that.table + " " + - "WHERE identifier like '" + row.identifier + "' " + - "AND pos = " + row.pos; - queries.push(sql); - } - yield that.exec(queries.join(';\n')); - }); -} diff --git a/app/lib/dal/sqliteDAL/index/SIndexDAL.ts b/app/lib/dal/sqliteDAL/index/SIndexDAL.ts new file mode 100644 index 000000000..13b0c04e3 --- /dev/null +++ b/app/lib/dal/sqliteDAL/index/SIndexDAL.ts @@ -0,0 +1,129 @@ +import {Indexer, SindexEntry} from "../../../indexer"; +import {SQLiteDriver} from "../../drivers/SQLiteDriver"; +import {AbstractIndex} from "../AbstractIndex"; +const _ = require('underscore'); +const common = require('duniter-common'); +const constants = require('../../../constants'); + +export class SIndexDAL extends AbstractIndex<SindexEntry> { + + constructor(driver:SQLiteDriver) { + super( + driver, + 's_index', + // PK fields + ['op', 'identifier', 'pos', 'written_on'], + // Fields + [ + 'op', + 'tx', + 'identifier', + 'pos', + 'created_on', + 'written_on', + 'writtenOn', + 'written_time', + 'amount', + 'base', + 'locktime', + 'consumed', + 'conditions' + ], + // Arrays + [], + // Booleans + ['consumed'], + // BigIntegers + [], + // Transient + [] + ) + } + + async init() { + await this.exec('BEGIN;' + + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + + 'op VARCHAR(10) NOT NULL,' + + 'tx VARCHAR(80) NULL,' + + 'identifier VARCHAR(64) NOT NULL,' + + 'pos INTEGER NOT NULL,' + + 'created_on VARCHAR(80) NULL,' + + 'written_on VARCHAR(80) NOT NULL,' + + 'written_time INTEGER NOT NULL,' + + 'amount INTEGER NULL,' + + 'base INTEGER NULL,' + + 'locktime INTEGER NULL,' + + 'consumed BOOLEAN NOT NULL,' + + 'conditions TEXT,' + + 'PRIMARY KEY (op,identifier,pos,written_on)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_sindex_identifier ON s_index (identifier);' + + 'CREATE INDEX IF NOT EXISTS idx_sindex_pos ON s_index (pos);' + + 'COMMIT;') + } + + async removeBlock(blockstamp:string) { + await this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') + } + + async getSource(identifier:string, pos:number) { + const reducable = await this.query('SELECT * FROM ' + this.table + ' s1 ' + + 'WHERE s1.identifier = ? ' + + 'AND s1.pos = ? ' + + 'ORDER BY op ASC', [identifier, pos]); + if (reducable.length == 0) { + return null; + } else { + const src = Indexer.DUP_HELPERS.reduce(reducable); + src.type = src.tx ? 'T' : 'D'; + return src; + } + } + + async getUDSources(pubkey:string) { + const reducables = await this.query('SELECT * FROM ' + this.table + ' s1 ' + + 'WHERE conditions = ? ' + + 'AND s1.tx IS NULL ' + + 'ORDER BY op ASC', ['SIG(' + pubkey + ')']); + const reduced = Indexer.DUP_HELPERS.reduceBy(reducables, ['identifier', 'pos']).map((src) => { + src.type = src.tx ? 'T' : 'D'; + return src; + }); + return _.sortBy(reduced, (row:SindexEntry) => row.type == 'D' ? 0 : 1); + } + + getAvailableForPubkey(pubkey:string) { + return this.getAvailableForConditions('%SIG(' + pubkey + ')%') + } + + async getAvailableForConditions(conditionsStr:string) { + const potentials = await this.query('SELECT * FROM ' + this.table + ' s1 ' + + 'WHERE s1.op = ? ' + + 'AND conditions LIKE ? ' + + 'AND NOT EXISTS (' + + ' SELECT * ' + + ' FROM s_index s2 ' + + ' WHERE s2.identifier = s1.identifier ' + + ' AND s2.pos = s1.pos ' + + ' AND s2.op = ?' + + ') ' + + 'ORDER BY CAST(SUBSTR(written_on, 0, INSTR(written_on, "-")) as number)', [common.constants.IDX_CREATE, conditionsStr, common.constants.IDX_UPDATE]); + const sources = potentials.map((src) => { + src.type = src.tx ? 'T' : 'D'; + return src; + }); + return _.sortBy(sources, (row:SindexEntry) => row.type == 'D' ? 0 : 1); + } + + async trimConsumedSource(belowNumber:number) { + const toDelete = await this.query('SELECT * FROM ' + this.table + ' WHERE consumed AND CAST(written_on as int) < ?', [belowNumber]); + const queries = []; + for (const row of toDelete) { + const sql = "DELETE FROM " + this.table + " " + + "WHERE identifier like '" + row.identifier + "' " + + "AND pos = " + row.pos; + queries.push(sql); + } + await this.exec(queries.join(';\n')); + } +} diff --git a/app/lib/db/DBTransaction.ts b/app/lib/db/DBTransaction.ts index 1a012e98d..9e0a7c82f 100644 --- a/app/lib/db/DBTransaction.ts +++ b/app/lib/db/DBTransaction.ts @@ -26,6 +26,7 @@ export class DBTransaction extends TransactionDTO { locktime, hash, blockstamp, + blockstampTime, issuers, inputs, outputs, diff --git a/app/lib/dto/ConfDTO.ts b/app/lib/dto/ConfDTO.ts index 66cfca0d7..6b135ca6a 100644 --- a/app/lib/dto/ConfDTO.ts +++ b/app/lib/dto/ConfDTO.ts @@ -32,4 +32,8 @@ export class ConfDTO { public msWindow: number, public sigWindow: number, ) {} + + 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) + } } \ No newline at end of file diff --git a/app/lib/dto/TransactionDTO.ts b/app/lib/dto/TransactionDTO.ts index 2bd4ea0d2..70ea23cba 100644 --- a/app/lib/dto/TransactionDTO.ts +++ b/app/lib/dto/TransactionDTO.ts @@ -28,12 +28,13 @@ export class TransactionDTO { public locktime: number, public hash: string, public blockstamp: string, + public blockstampTime: number, public issuers: string[], public inputs: string[], public outputs: string[], public unlocks: string[], public signatures: string[], - public comment?: string + public comment: string ) { // Compute the hash if not given if (!hash) { @@ -76,6 +77,13 @@ export class TransactionDTO { }) } + outputsAsRecipients(): string[] { + return this.outputs.map((out) => { + const recipent = out.match('SIG\\((.*)\\)'); + return (recipent && recipent[1]) || 'UNKNOWN'; + }) + } + getCompactVersion() { let issuers = this.issuers; let raw = ["TX", this.version, issuers.length, this.inputs.length, this.unlocks.length, this.outputs.length, this.comment ? 1 : 0, this.locktime || 0].join(':') + '\n'; @@ -102,17 +110,18 @@ export class TransactionDTO { static fromJSONObject(obj:any) { return new TransactionDTO( - obj.version, - obj.currency, - obj.locktime, - obj.hash, - obj.blockstamp, - obj.issuers, - obj.inputs, - obj.outputs, - obj.unlocks, - obj.signatures, - obj.comment + obj.version || 10, + obj.currency || "", + obj.locktime || 0, + obj.hash || "", + obj.blockstamp || "", + obj.blockstampTime || 0, + obj.issuers || [], + obj.inputs || [], + obj.outputs || [], + obj.unlocks || [], + obj.signatures || [], + obj.comment || "" ) } @@ -147,4 +156,8 @@ export class TransactionDTO { } return raw } + + static mock() { + return new TransactionDTO(1, "", 0, "", "", 0, [], [], [], [], [], "") + } } \ No newline at end of file diff --git a/app/lib/indexer.ts b/app/lib/indexer.ts index 7806e7308..785ef39a4 100644 --- a/app/lib/indexer.ts +++ b/app/lib/indexer.ts @@ -60,14 +60,13 @@ export interface IindexEntry extends IndexEntry { member: boolean, wasMember: boolean | null, kick: boolean | null, - wid: number | null, + wotb_id: number | null, age: number, pubUnique?: boolean, excludedIsMember?: boolean, isBeingKicked?: boolean, uidUnique?: boolean, hasToBeExcluded?: boolean, - wotb_id?: number, } export interface CindexEntry extends IndexEntry { @@ -105,6 +104,7 @@ export interface SindexEntry extends IndexEntry { consumed: boolean, txObj: TransactionDTO, age: number, + type?: string, available?: boolean, isLocked?: boolean, isTimeLocked?: boolean, @@ -168,7 +168,7 @@ export class Indexer { member: true, wasMember: true, kick: false, - wid: null // wotb id + wotb_id: null }) } @@ -233,7 +233,7 @@ export class Indexer { member: true, wasMember: null, kick: null, - wid: null + wotb_id: null }) } } @@ -320,7 +320,7 @@ export class Indexer { member: false, wasMember: null, kick: false, - wid: null + wotb_id: null }); } @@ -1830,7 +1830,7 @@ function reduce(records: any[]) { }, {}); } -function reduceBy(reducables: SindexEntry[], properties: string[]): any[] { +function reduceBy(reducables: IndexEntry[], properties: string[]): any[] { const reduced = reducables.reduce((map: any, entry: any) => { const id = properties.map((prop) => entry[prop]).join('-'); map[id] = map[id] || []; diff --git a/test/blockchain/basic-blockchain.ts b/test/blockchain/basic-blockchain.ts index 9869d3ee9..45b42adf5 100644 --- a/test/blockchain/basic-blockchain.ts +++ b/test/blockchain/basic-blockchain.ts @@ -2,10 +2,11 @@ import {BasicBlockchain} from "../../app/lib/blockchain/BasicBlockchain" import {ArrayBlockchain} from "./lib/ArrayBlockchain" import {SQLBlockchain} from "../../app/lib/blockchain/SqlBlockchain" import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver" +import {BIndexDAL} from "../../app/lib/dal/sqliteDAL/index/BIndexDAL"; +import {MetaDAL} from "../../app/lib/dal/sqliteDAL/MetaDAL"; +import {ConfDTO} from "../../app/lib/dto/ConfDTO"; const assert = require('assert') -const BIndexDAL = require('../../app/lib/dal/sqliteDAL/index/BIndexDAL') -const MetaDAL = require('../../app/lib/dal/sqliteDAL/MetaDAL') let blockchain:BasicBlockchain, emptyBlockchain:BasicBlockchain @@ -83,7 +84,7 @@ describe('Basic SQL Blockchain', () => { await metaDAL.exec('CREATE TABLE cert (id INTEGER null);') await metaDAL.exec('CREATE TABLE membership (id INTEGER null);') await metaDAL.exec('CREATE TABLE block (fork INTEGER null);') - await metaDAL.upgradeDatabase({}); + await metaDAL.upgradeDatabase(ConfDTO.mock()); const dal = { bindexDAL } @@ -102,7 +103,7 @@ describe('Basic SQL Blockchain', () => { await metaDAL.exec('CREATE TABLE cert (id INTEGER null);') await metaDAL.exec('CREATE TABLE membership (id INTEGER null);') await metaDAL.exec('CREATE TABLE block (fork INTEGER null);') - await metaDAL.upgradeDatabase({}); + await metaDAL.upgradeDatabase(ConfDTO.mock()); const dal = { bindexDAL } diff --git a/test/blockchain/misc-sql-blockchain.ts b/test/blockchain/misc-sql-blockchain.ts index 1b2988ff2..3f80bf5b9 100644 --- a/test/blockchain/misc-sql-blockchain.ts +++ b/test/blockchain/misc-sql-blockchain.ts @@ -2,13 +2,14 @@ import {MiscIndexedBlockchain} from "../../app/lib/blockchain/MiscIndexedBlockchain" import {ArrayBlockchain} from "./lib/ArrayBlockchain" import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver" +import {MIndexDAL} from "../../app/lib/dal/sqliteDAL/index/MIndexDAL"; +import {IIndexDAL} from "../../app/lib/dal/sqliteDAL/index/IIndexDAL"; +import {SIndexDAL} from "../../app/lib/dal/sqliteDAL/index/SIndexDAL"; +import {CIndexDAL} from "../../app/lib/dal/sqliteDAL/index/CIndexDAL"; +import {MetaDAL} from "../../app/lib/dal/sqliteDAL/MetaDAL"; +import {ConfDTO} from "../../app/lib/dto/ConfDTO"; const assert = require('assert') -const MIndexDAL = require('../../app/lib/dal/sqliteDAL/index/MIndexDAL') -const IIndexDAL = require('../../app/lib/dal/sqliteDAL/index/IIndexDAL') -const SIndexDAL = require('../../app/lib/dal/sqliteDAL/index/SIndexDAL') -const CIndexDAL = require('../../app/lib/dal/sqliteDAL/index/CIndexDAL') -const MetaDAL = require('../../app/lib/dal/sqliteDAL/MetaDAL') describe('MISC SQL Blockchain', () => { @@ -24,8 +25,8 @@ describe('MISC SQL Blockchain', () => { const cindexDAL = new CIndexDAL(db) const metaDAL = new MetaDAL(db) - await mindexDAL.init() await iindexDAL.init() + await mindexDAL.init() await sindexDAL.init() await cindexDAL.init() await metaDAL.init() @@ -36,7 +37,7 @@ describe('MISC SQL Blockchain', () => { await metaDAL.exec('CREATE TABLE membership (id INTEGER null);') await metaDAL.exec('CREATE TABLE block (fork INTEGER null);') await metaDAL.exec('CREATE TABLE b_index (id INTEGER null);') - await metaDAL.upgradeDatabase({}); + await metaDAL.upgradeDatabase(ConfDTO.mock()); blockchain = new MiscIndexedBlockchain(new ArrayBlockchain(), mindexDAL, iindexDAL, sindexDAL, cindexDAL) }) -- GitLab