diff --git a/app/lib/common-libs/moment.ts b/app/lib/common-libs/moment.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca05144233a3364fc44985f8cee8947870af7521 --- /dev/null +++ b/app/lib/common-libs/moment.ts @@ -0,0 +1,16 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Äž1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +const _moment_ = require("moment") + +export const moment = _moment_ \ No newline at end of file diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts index 256d93cbff504c377b2a15aae3d77bde615fdafb..12a07818d173ba48de018c999a192eee6b13d421 100644 --- a/app/lib/dal/fileDAL.ts +++ b/app/lib/dal/fileDAL.ts @@ -326,6 +326,11 @@ export class FileDAL { return (await this.blockDAL.getAbsoluteBlock(number, hash)) || (await this.blockchainArchiveDAL.getBlock(number, hash)) } + async getAbsoluteBlockByBlockstamp(blockstamp: string): Promise<DBBlock|null> { + const sp = blockstamp.split('-') + return this.getAbsoluteBlockByNumberAndHash(parseInt(sp[0]), sp[1]) + } + async existsNonChainableLink(from:string, vHEAD_1:DBHead, sigStock:number) { // Cert period rule const medianTime = vHEAD_1 ? vHEAD_1.medianTime : 0; diff --git a/app/lib/dal/indexDAL/abstract/GenericDAO.ts b/app/lib/dal/indexDAL/abstract/GenericDAO.ts index 500899136bffbaf86ff136b4b8c2c3dc342d3704..12ada2d2819ac5f280283e17b0f5a9f5b874d5c2 100644 --- a/app/lib/dal/indexDAL/abstract/GenericDAO.ts +++ b/app/lib/dal/indexDAL/abstract/GenericDAO.ts @@ -20,7 +20,7 @@ export interface GenericDAO<T> extends Initiable { * @param sort A LokiJS's compunded sort object. * @returns {Promise<any>} A set of records. */ - findRawWithOrder(criterion: any, sort:((string|((string|boolean)[]))[])): Promise<any> + findRawWithOrder(criterion: any, sort:((string|((string|boolean)[]))[])): Promise<T[]> /** * Make a single insert. diff --git a/app/modules/dump.ts b/app/modules/dump.ts index 3ebba35e3057b43b2834c7d11db43810f96a5641..5d3aae6907b2ea300c844246b5587bda46fc285e 100644 --- a/app/modules/dump.ts +++ b/app/modules/dump.ts @@ -13,13 +13,15 @@ import {ConfDTO} from "../lib/dto/ConfDTO" import {Server} from "../../server" +import {moment} from "../lib/common-libs/moment" +import {DBBlock} from "../lib/db/DBBlock" -const Table = require('cli-table'); +const Table = require('cli-table') module.exports = { duniter: { cli: [{ - name: 'dump [what] [name]', + name: 'dump [what] [name] [cond]', desc: 'Dumps data of the blockchain.', logs: false, preventIfRunning: true, @@ -27,31 +29,15 @@ module.exports = { onDatabaseExecute: async (server:Server, conf:ConfDTO, program:any, params:any) => { const what: string = params[0] || '' const name: string = params[1] || '' + const cond: string = params[2] || '' switch (what) { case 'table': - let rows: any[] - switch (name) { - case 'i_index': - rows = await server.dal.iindexDAL.findRawWithOrder({}, [['writtenOn', false], ['wotb_id', false]]) - dump(rows, (program.dbgOmitCols || "").split(',').concat('index', 'meta', '$loki', 'writtenOn', 'age')) - break - case 'm_index': - rows = await server.dal.mindexDAL.findRawWithOrder({}, [['writtenOn', false], ['pub', false]]) - dump(rows, ['op','pub','created_on','written_on','expires_on','expired_on','revokes_on','revoked_on','leaving','revocation','chainable_on']) - break - case 'c_index': - rows = await server.dal.cindexDAL.findRawWithOrder({}, [['writtenOn', false], ['issuer', false], ['receiver', false]]) - dump(rows, ['op','issuer','receiver','created_on','written_on','sig','expires_on','expired_on','chainable_on','from_wid','to_wid']) - break - case 's_index': - rows = await server.dal.sindexDAL.findRawWithOrder({}, [['writtenOn', false], ['identifier', false], ['pos', false]]) - dump(rows, ['op','tx','identifier','pos','created_on','written_on','written_time','amount','base','locktime','consumed','conditions']) - break - default: - console.error(`Unknown dump table ${name}`) - break - } + await dumpTable(server, name, cond) + break + + case 'history': + await dumpHistory(server, name) break default: @@ -65,6 +51,46 @@ module.exports = { } } +async function dumpTable(server: Server, name: string, condition?: string) { + const criterion: any = {} + const filters = condition && condition.split(',') || [] + for (const f of filters) { + const k = f.split('=')[0] + const v = f.split('=')[1] + if (v === 'true' || v === 'false') { + criterion[k] = v === 'true' ? true : 0 + } else if (v === 'NULL') { + criterion[k] = null + } else if (v.match(/^\d+$/)) { + criterion[k] = parseInt(v) + } else { + criterion[k] = v + } + } + let rows: any[] + switch (name) { + case 'i_index': + rows = await server.dal.iindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['wotb_id', false]]) + dump(rows, ['op','uid','pub','hash','sig','created_on','written_on','member','wasMember','kick','wotb_id']) + break + case 'm_index': + rows = await server.dal.mindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['pub', false]]) + dump(rows, ['op','pub','created_on','written_on','expires_on','expired_on','revokes_on','revoked_on','leaving','revocation','chainable_on']) + break + case 'c_index': + rows = await server.dal.cindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['issuer', false], ['receiver', false]]) + dump(rows, ['op','issuer','receiver','created_on','written_on','sig','expires_on','expired_on','chainable_on','from_wid','to_wid']) + break + case 's_index': + rows = await server.dal.sindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['identifier', false], ['pos', false]]) + dump(rows, ['op','tx','identifier','pos','created_on','written_on','written_time','amount','base','locktime','consumed','conditions']) + break + default: + console.error(`Unknown dump table ${name}`) + break + } +} + function dump(rows: any[], columns: string[]) { // Table columns const t = new Table({ @@ -91,3 +117,45 @@ function dump(rows: any[], columns: string[]) { console.error(e) } } + +async function dumpHistory(server: Server, pub: string) { + const irows = await server.dal.iindexDAL.findRawWithOrder({ pub }, [['writtenOn', false]]) + const mrows = await server.dal.mindexDAL.findRawWithOrder({ pub }, [['writtenOn', false]]) + console.log('----- IDENTITY -----') + for (const e of irows) { + const date = await getDateFor(server, e.written_on) + if (e.uid) { + console.log('%s: new identity %s (created on %s)', date, e.uid, await getDateFor(server, e.created_on as string)) + } else if (e.member) { + console.log('%s: comeback', date) + } else if (e.kick) { + // console.log('%s: being kicked... (either)', date) + } else if (e.member === false) { + console.log('%s: excluded', date) + } else { + console.log('Non displayable IINDEX entry') + } + } + console.log('----- MEMBERSHIP -----') + for (const e of mrows) { + const date = await getDateFor(server, e.written_on) + if (e.chainable_on) { + console.log('%s: join/renew', date) + } else if (e.expired_on) { + console.log('%s: expired', date) + } else { + console.log('Non displayable MINDEX entry') + } + } +} + +async function getDateFor(server: Server, blockstamp: string) { + const b = (await server.dal.getAbsoluteBlockByBlockstamp(blockstamp)) as DBBlock + const s = " " + b.number + const bnumberPadded = s.substr(s.length - 6) + return formatTimestamp(b.medianTime) + ' (#' + bnumberPadded + ')' +} + +function formatTimestamp(ts: number) { + return moment(ts * 1000).format('YYYY-MM-DD hh:mm:ss') +} \ No newline at end of file