UNPKG

@owstack/wallet-service

Version:

A service for multisignature HD wallets

330 lines (299 loc) 9.34 kB
#!/usr/bin/env node const owsCommon = require('@owstack/ows-common'); const async = require('async'); const EventEmitter = require('events').EventEmitter; const log = require('npmlog'); const mongodb = require('mongodb'); const moment = require('moment'); const Storage = require('./storage'); const lodash = owsCommon.deps.lodash; log.debug = log.verbose; log.disableColor(); const INITIAL_DATE = '2019-01-01'; class Stats extends EventEmitter { constructor(context, config, opts) { super(); // Context defines the coin network and is set by the implementing service in // order to instance this base service; e.g., btc-service. context.inject(this); // Set some frequently used contant values based on context. this.LIVENET = this.ctx.Networks.livenet; this.config = config || {}; opts = opts || {}; this.networkName = opts.networkName || this.LIVENET.name; this.from = moment(opts.from || INITIAL_DATE); this.to = moment(opts.to); this.fromTs = this.from.startOf('day').valueOf(); this.toTs = this.to.endOf('day').valueOf(); } } Stats.prototype.run = function (cb) { const self = this; const uri = self.config.storageOpts.mongoDb.uri; mongodb.MongoClient.connect(uri, function (err, db) { if (err) { log.error('Unable to connect to the mongoDB', err); return cb(err, null); } log.info(`Connection established to ${ uri}`); self.db = db; self._getStats(function (err, stats) { if (err) { return cb(err); } return cb(null, stats); }); }); }; Stats.prototype._getStats = function (cb) { const self = this; const result = {}; async.parallel([ function (next) { self._getNewWallets(next); }, function (next) { self._getTxProposals(next); }, ], function (err, results) { if (err) { return cb(err); } result.newWallets = results[0]; result.txProposals = results[1]; return cb(null, result); }); }; Stats.prototype._getNewWallets = function (cb) { const self = this; function getLastDate(cb) { self.db.collection('stats_wallets') .find({}) .sort({ '_id.day': -1 }) .limit(1) .toArray(function (err, lastRecord) { if (lodash.isEmpty(lastRecord)) { return cb(null, moment(INITIAL_DATE)); } return cb(null, moment(lastRecord[0]._id.day)); }); } function updateStats(from, cb) { const to = moment().subtract(1, 'day').endOf('day'); const map = function () { const day = new Date(this.createdOn * 1000); day.setHours(0); day.setMinutes(0); day.setSeconds(0); const key = { day: +day, networkName: this.networkName }; const value = { count: 1 }; emit(key, value); // eslint-disable-line no-undef }; const reduce = function (k, v) { let count = 0; for (let i = 0; i < v.length; i++) { count += v[i].count; } return { count: count, }; }; const opts = { query: { createdOn: { $gt: from.unix(), $lte: to.unix(), }, }, out: { merge: 'stats_wallets', } }; self.db.collection(Storage.collections.WALLETS) .mapReduce(map, reduce, opts, function (err, collection, stats) { return cb(err); }); } function queryStats(cb) { self.db.collection('stats_wallets') .find({ '_id.networkName': self.networkName, '_id.day': { $gte: self.fromTs, $lte: self.toTs }, }) .sort({ '_id.day': 1 }) .toArray(function (err, results) { if (err) { return cb(err); } const stats = {}; stats.byDay = lodash.map(results, function (record) { const day = moment(record._id.day).format('YYYYMMDD'); return { day: day, count: record.value.count }; }); return cb(null, stats); }); } async.series([ function (next) { getLastDate(function (err, lastDate) { if (err) { return next(err); } lastDate = lastDate.startOf('day'); const yesterday = moment().subtract(1, 'day').startOf('day'); if (lastDate.isBefore(yesterday)) { // Needs update return updateStats(lastDate, next); } next(); }); }, function (next) { queryStats(next); }, ], function (err, res) { if (err) { log.error(err); } return cb(err, res[1]); }); }; Stats.prototype._getTxProposals = function (cb) { const self = this; function getLastDate(cb) { self.db.collection('stats_txps') .find({}) .sort({ '_id.day': -1 }) .limit(1) .toArray(function (err, lastRecord) { if (lodash.isEmpty(lastRecord)) { return cb(null, moment(INITIAL_DATE)); } return cb(null, moment(lastRecord[0]._id.day)); }); } function updateStats(from, cb) { const to = moment().subtract(1, 'day').endOf('day'); const map = function () { const day = new Date(this.broadcastedOn * 1000); day.setHours(0); day.setMinutes(0); day.setSeconds(0); const key = { day: +day, networkName: this.networkName, }; const value = { count: 1, amount: this.amount }; emit(key, value); // eslint-disable-line no-undef }; const reduce = function (k, v) { let count = 0; let amount = 0; for (let i = 0; i < v.length; i++) { count += v[i].count; amount += v[i].amount; } return { count: count, amount: amount, }; }; const opts = { query: { status: 'broadcasted', broadcastedOn: { $gt: from.unix(), $lte: to.unix(), }, }, out: { merge: 'stats_txps', } }; self.db.collection(Storage.collections.TXS) .mapReduce(map, reduce, opts, function (err, collection, stats) { return cb(err); }); } function queryStats(cb) { self.db.collection('stats_txps') .find({ '_id.networkName': self.networkName, '_id.day': { $gte: self.fromTs, $lte: self.toTs, }, }) .sort({ '_id.day': 1 }) .toArray(function (err, results) { if (err) { return cb(err); } const stats = { nbByDay: [], amountByDay: [] }; lodash.each(results, function (record) { const day = moment(record._id.day).format('YYYYMMDD'); stats.nbByDay.push({ day: day, count: record.value.count, }); stats.amountByDay.push({ day: day, amount: record.value.amount, }); }); return cb(null, stats); }); } async.series([ function (next) { getLastDate(function (err, lastDate) { if (err) { return next(err); } lastDate = lastDate.startOf('day'); const yesterday = moment().subtract(1, 'day').startOf('day'); if (lastDate.isBefore(yesterday)) { // Needs update return updateStats(lastDate, next); } next(); }); }, function (next) { queryStats(next); }, ], function (err, res) { if (err) { log.error(err); } return cb(err, res[1]); }); }; module.exports = Stats;