var mongoose = require('mongoose'), lib = require('../lib/explorer'), db = require('../lib/database'), Tx = require('../models/tx'), Address = require('../models/address'), AddressTx = require('../models/addresstx'), Richlist = require('../models/richlist'), Stats = require('../models/stats'), settings = require('../lib/settings'); var mode = 'update'; var database = 'index'; var block_start = 1; // displays usage and exits function usage() { console.log('Usage: scripts/sync.sh /path/to/node [mode]'); console.log(''); console.log('Mode: (required)'); console.log('update Updates index from last sync to current block'); console.log('check Checks index for (and adds) any missing transactions/addresses'); console.log(' Optional parameter: block number to start checking from'); console.log('reindex Clears index then resyncs from genesis to current block'); console.log('reindex-rich Clears and recreates the richlist data'); console.log('reindex-txcount Rescan and flatten the tx count value for faster access'); console.log('reindex-last Rescan and flatten the last blockindex value for faster access'); console.log('market Updates market summaries, orderbooks, trade history + charts'); console.log('peers Updates peer info based on local wallet connections'); console.log('masternodes Updates the list of active masternodes on the network'); console.log(''); console.log('Notes:'); console.log('- \'current block\' is the latest created block when script is executed.'); console.log('- The market + peers databases only support (& defaults to) reindex mode.'); console.log('- If check mode finds missing data (other than new data since last sync),'); console.log(' this likely means that sync.update_timeout in settings.json is set too low.'); console.log(''); process.exit(0); } // check options if (process.argv[2] == 'index') { if (process.argv.length < 3) usage(); else { switch (process.argv[3]) { case 'update': mode = 'update'; break; case 'check': mode = 'check'; // check if the block start value was passed in and is an integer if (!isNaN(process.argv[4]) && Number.isInteger(parseFloat(process.argv[4]))) { // Check if the block start value is less than 1 if (parseInt(process.argv[4]) < 1) block_start = 1; else block_start = parseInt(process.argv[4]); } break; case 'reindex': mode = 'reindex'; break; case 'reindex-rich': mode = 'reindex-rich'; break; case 'reindex-txcount': mode = 'reindex-txcount'; break; case 'reindex-last': mode = 'reindex-last'; break; default: usage(); } } } else if (process.argv[2] == 'market') database = 'market'; else if (process.argv[2] == 'peers') database = 'peers'; else if (process.argv[2] == 'masternodes') database = 'masternodes'; else usage(); function create_lock(cb) { if (database == 'index') { var fname = './tmp/' + database + '.pid'; db.fs.appendFile(fname, process.pid.toString(), function (err) { if (err) { console.log("Error: unable to create %s", fname); process.exit(1); } else return cb(); }); } else return cb(); } function remove_lock(cb) { if (database == 'index') { var fname = './tmp/' + database + '.pid'; db.fs.unlink(fname, function (err) { if (err) { console.log("unable to remove lock: %s", fname); process.exit(1); } else return cb(); }); } else return cb(); } function is_locked(cb) { if (database == 'index') { var fname = './tmp/' + database + '.pid'; db.fs.exists(fname, function (exists) { if (exists) return cb(true); else return cb(false); }); } else return cb(); } function exit() { remove_lock(function() { mongoose.disconnect(); process.exit(0); }); } var dbString = 'mongodb://' + settings.dbsettings.user; dbString = dbString + ':' + settings.dbsettings.password; dbString = dbString + '@' + settings.dbsettings.address; dbString = dbString + ':' + settings.dbsettings.port; dbString = dbString + '/' + settings.dbsettings.database; if (database == 'peers') { var rateLimitLib = require('../lib/ratelimit'); console.log('syncing peers.. please wait..'); // syncing peers does not require a lock mongoose.connect(dbString, function(err) { if (err) { console.log('Unable to connect to database: %s', dbString); console.log('Aborting'); exit(); } else { lib.get_peerinfo(function (body) { if (body != null) { lib.syncLoop(body.length, function (loop) { var i = loop.iteration(); var address = body[i].addr; var port = null; if (occurrences(address, ':') == 1 || occurrences(address, ']:') == 1) { // Separate the port # from the IP address address = address.substring(0, address.lastIndexOf(":")).replace("[", "").replace("]", ""); port = body[i].addr.substring(body[i].addr.lastIndexOf(":") + 1); } if (address.indexOf("]") > -1) { // Remove [] characters from IPv6 addresses address = address.replace("[", "").replace("]", ""); } db.find_peer(address, function(peer) { if (peer) { if ((peer['port'] != null && (isNaN(peer['port']) || peer['port'].length < 2)) || peer['country'].length < 1 || peer['country_code'].length < 1) { db.drop_peers(function() { console.log('Saved peers missing ports or country, dropping peers. Re-run this script afterwards.'); exit(); }); } // peer already exists console.log('Updated peer %s [%s/%s]', address, (i + 1).toString(), body.length.toString()); loop.next(); } else { var rateLimit = new rateLimitLib.RateLimit(1, 2000, false); rateLimit.schedule(function() { lib.get_geo_location(address, function (error, geo) { // check if an error was returned if (error) { console.log(error); exit(); } else { // add peer to collection db.create_peer({ address: address, port: port, protocol: body[i].version, version: body[i].subver.replace('/', '').replace('/', ''), country: geo.country_name, country_code: geo.country_code }, function() { console.log('Added new peer %s [%s/%s]', address, (i + 1).toString(), body.length.toString()); loop.next(); }); } }); }); } }); }, function() { // update network_last_updated value db.update_last_updated_stats(settings.coin.name, { network_last_updated: Math.floor(new Date() / 1000) }, function (cb) { console.log('peer sync complete'); exit(); }); }); } else { console.log('no peers found'); exit(); } }); } }); } else if (database == 'masternodes') { console.log('syncing masternodes.. please wait..'); // syncing masternodes does not require a lock mongoose.connect(dbString, function(err) { if (err) { console.log('Unable to connect to database: %s', dbString); console.log('Aborting'); exit(); } else { lib.get_masternodelist(function (body) { if (body != null) { var isObject = false; var objectKeys = null; // Check if the masternode data is an array or an object if (body.length == null) { // Process data as an object objectKeys = Object.keys(body); isObject = true; } lib.syncLoop((isObject ? objectKeys : body).length, function (loop) { var i = loop.iteration(); db.save_masternode((isObject ? body[objectKeys[i]] : body[i]), function (success) { if (success) loop.next(); else { console.log('error: cannot save masternode %s.', (isObject ? (body[objectKeys[i]].payee ? body[objectKeys[i]].payee : 'UNKNOWN') : (body[i].addr ? body[i].addr : 'UNKNOWN'))); exit(); } }); }, function () { db.remove_old_masternodes(function (cb) { db.update_last_updated_stats(settings.coin.name, { masternodes_last_updated: Math.floor(new Date() / 1000) }, function (cb) { console.log('masternode sync complete'); exit(); }); }); }); } else { console.log('no masternodes found'); exit(); } }); } }); } else { // index and market sync requires locking is_locked(function (exists) { if (exists) { console.log("Script already running.."); process.exit(0); } else { create_lock(function () { console.log("script launched with pid: " + process.pid); mongoose.connect(dbString, function(err) { if (err) { console.log('Unable to connect to database: %s', dbString); console.log('Aborting'); exit(); } else if (database == 'index') { db.check_stats(settings.coin.name, function(exists) { if (exists == false) { console.log('Run \'npm start\' to create database structures before running this script.'); exit(); } else { db.update_db(settings.coin.name, function(stats) { // check if stats returned properly if (stats !== false) { // determine which index mode to run if (mode == 'reindex') { console.log('deleting transactions.. please wait..'); Tx.deleteMany({}, function(err) { console.log('transactions deleted successfully'); console.log('deleting addresses.. please wait..'); Address.deleteMany({}, function(err2) { console.log('addresses deleted successfully'); console.log('deleting address transactions.. please wait..'); AddressTx.deleteMany({}, function(err3) { console.log('address transactions deleted successfully'); console.log('deleting top 100 data.. please wait..'); Richlist.updateOne({coin: settings.coin.name}, { received: [], balance: [] }, function(err3) { console.log('top 100 data deleted successfully'); console.log('deleting block index.. please wait..'); Stats.updateOne({coin: settings.coin.name}, { last: 0, count: 0, supply: 0, txes: 0, blockchain_last_updated: 0, richlist_last_updated: 0 }, function() { console.log('block index deleted successfully'); // Check if the sync msg should be shown check_show_sync_message(stats.count); console.log('starting resync of blockchain data.. please wait..'); db.update_tx_db(settings.coin.name, block_start, stats.count, stats.txes, settings.sync.update_timeout, false, function() { // update blockchain_last_updated value db.update_last_updated_stats(settings.coin.name, { blockchain_last_updated: Math.floor(new Date() / 1000) }, function (cb) { db.update_richlist('received', function() { db.update_richlist('balance', function() { // update richlist_last_updated value db.update_last_updated_stats(settings.coin.name, { richlist_last_updated: Math.floor(new Date() / 1000) }, function (cb) { db.get_stats(settings.coin.name, function(nstats) { // check for and update heavycoin data if applicable update_heavy(settings.coin.name, stats.count, 20, settings.blockchain_specific.heavycoin.enabled, function(heavy) { // always check for and remove the sync msg if exists remove_sync_message(); console.log('reindex complete (block: %s)', nstats.last); exit(); }); }); }); }); }); }); }); }); }); }); }); }); } else if (mode == 'check') { console.log('starting check.. please wait..'); db.update_tx_db(settings.coin.name, block_start, stats.count, stats.txes, settings.sync.check_timeout, true, function() { db.get_stats(settings.coin.name, function(nstats) { console.log('check complete (block: %s)', nstats.last); exit(); }); }); } else if (mode == 'update') { // Get the last synced block index value var last = (stats.last ? stats.last : 0); // Get the total number of blocks var count = (stats.count ? stats.count : 0); // Check if the sync msg should be shown check_show_sync_message(count - last); db.update_tx_db(settings.coin.name, last, count, stats.txes, settings.sync.update_timeout, false, function() { db.update_richlist('received', function() { db.update_richlist('balance', function() { db.get_stats(settings.coin.name, function(nstats) { // check for and update heavycoin data if applicable update_heavy(settings.coin.name, stats.count, 20, settings.blockchain_specific.heavycoin.enabled, function(heavy) { // always check for and remove the sync msg if exists remove_sync_message(); // update richlist_last_updated value db.update_last_updated_stats(settings.coin.name, { richlist_last_updated: Math.floor(new Date() / 1000) }, function (cb) { // update blockchain_last_updated value db.update_last_updated_stats(settings.coin.name, { blockchain_last_updated: Math.floor(new Date() / 1000) }, function (cb) { console.log('update complete (block: %s)', nstats.last); exit(); }); }); }); }); }); }); }); } else if (mode == 'reindex-rich') { console.log('check richlist'); db.check_richlist(settings.coin.name, function(exists) { if (exists) console.log('richlist entry found, deleting now..'); db.delete_richlist(settings.coin.name, function(deleted) { if (deleted) console.log('richlist entry deleted'); db.create_richlist(settings.coin.name, function() { console.log('richlist created.'); db.update_richlist('received', function() { console.log('richlist updated received.'); db.update_richlist('balance', function() { // update richlist_last_updated value db.update_last_updated_stats(settings.coin.name, { richlist_last_updated: Math.floor(new Date() / 1000) }, function (cb) { console.log('richlist update complete'); exit(); }); }); }); }); }); }); } else if (mode == 'reindex-txcount') { console.log('calculating tx count.. please wait..'); // Resetting the transaction counter requires a single lookup on the txes collection to find all txes that have a positive or zero total and 1 or more vout Tx.find({'total': {$gte: 0}, 'vout': { $gte: { $size: 1 }}}).countDocuments(function(err, count) { console.log('found tx count: ' + count.toString()); Stats.updateOne({coin: settings.coin.name}, { txes: count }, function() { console.log('tx count update complete'); exit(); }); }); } else if (mode == 'reindex-last') { console.log('finding last blockindex.. please wait..'); // Resetting the last blockindex counter requires a single lookup on the txes collection to find the last indexed blockindex Tx.find({}, {blockindex:1, _id:0}).sort({blockindex: -1}).limit(1).exec(function(err, tx) { // check if any blocks exists if (err != null || tx == null || tx.length == 0) { console.log('no blocks found. setting last blockindex to 0.'); Stats.updateOne({coin: settings.coin.name}, { last: 0 }, function() { console.log('last blockindex update complete'); exit(); }); } else { console.log('found last blockindex: ' + tx[0].blockindex.toString()); Stats.updateOne({coin: settings.coin.name}, { last: tx[0].blockindex }, function() { console.log('last blockindex update complete'); exit(); }); } }); } } else { // update_db threw an error so exit exit(); } }); } }); } else { // check if market feature is enabled if (settings.markets_page.enabled == true) { var complete = 0; var total_pairs = 0; var exchanges = Object.keys(settings.markets_page.exchanges); // loop through all exchanges to determine how many trading pairs must be updated exchanges.forEach(function (key, index, map) { // check if market is enabled via settings if (settings.markets_page.exchanges[key].enabled == true) { // check if market is installed/supported if (db.fs.existsSync('./lib/markets/' + key + '.js')) { // add trading pairs to total total_pairs += settings.markets_page.exchanges[key].trading_pairs.length; // loop through all trading pairs for this market for (var i = 0; i < settings.markets_page.exchanges[key].trading_pairs.length; i++) { // ensure trading pair setting is always uppercase settings.markets_page.exchanges[key].trading_pairs[i] = settings.markets_page.exchanges[key].trading_pairs[i].toUpperCase(); } } } }); // check if there are any trading pairs to update if (total_pairs > 0) { // initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis var rateLimitLib = require('../lib/ratelimit'); var rateLimit = new rateLimitLib.RateLimit(1, 2000, false); // loop through and test all exchanges defined in the settings.json file exchanges.forEach(function (key, index, map) { // check if market is enabled via settings if (settings.markets_page.exchanges[key].enabled == true) { // check if market is installed/supported if (db.fs.existsSync('./lib/markets/' + key + '.js')) { // loop through all trading pairs settings.markets_page.exchanges[key].trading_pairs.forEach(function (pair_key, pair_index, pair_map) { // split the pair data var split_pair = pair_key.split('/'); // check if this is a valid trading pair if (split_pair.length == 2) { // lookup the exchange in the market collection db.check_market(key, split_pair[0], split_pair[1], function(mkt, exists) { // check if exchange trading pair exists in the market collection if (exists) { // automatically pause for 2 seconds in between requests rateLimit.schedule(function() { // update market data db.update_markets_db(key, split_pair[0], split_pair[1], function(err) { if (!err) { console.log('%s[%s]: market data updated successfully.', key, pair_key); complete++; if (complete == total_pairs) get_last_usd_price(); } else { console.log('%s[%s] error: %s', key, pair_key, err); complete++; if (complete == total_pairs) get_last_usd_price(); } }); }); } else { console.log('error: entry for %s[%s] does not exist in markets database.', key, pair_key); complete++; if (complete == total_pairs) get_last_usd_price(); } }); } }); } else { // market not installed console.log('%s market not installed', key); complete++; if (complete == total_pairs) get_last_usd_price(); } } }); } else { // no market trading pairs are enabled console.log('error: no market trading pairs are enabled in settings'); exit(); } } else { // market page is not enabled console.log('error: market feature is disabled in settings'); exit(); } } }); }); } }); } function update_heavy(coin, height, count, heavycoin_enabled, cb) { if (heavycoin_enabled == true) { db.update_heavy(coin, height, count, function() { return cb(true); }); } else return cb(false); } function check_show_sync_message(blocks_to_sync) { var retVal = false; var filePath = './tmp/show_sync_message.tmp'; // Check if the sync msg should be shown if (blocks_to_sync > settings.sync.show_sync_msg_when_syncing_more_than_blocks) { // Check if the show sync stub file already exists if (!db.fs.existsSync(filePath)) { // File doesn't exist, so create it now db.fs.writeFileSync(filePath, ''); } retVal = true; } return retVal; } function remove_sync_message() { var filePath = './tmp/show_sync_message.tmp'; // Check if the show sync stub file exists if (db.fs.existsSync(filePath)) { // File exists, so delete it now try { db.fs.unlinkSync(filePath); } catch (err) { console.log(err); } } } function get_last_usd_price() { // get the last usd price for coinstats db.get_last_usd_price(function(err) { // check for errors if (err == null) { // update markets_last_updated value db.update_last_updated_stats(settings.coin.name, { markets_last_updated: Math.floor(new Date() / 1000) }, function (cb) { console.log('market sync complete'); exit(); }); } else { // display error msg console.log('error: %s', err); exit(); } }); } /** Function that count occurrences of a substring in a string; * @param {String} string The string * @param {String} subString The sub string to search for * @param {Boolean} [allowOverlapping] Optional. (Default:false) * * @author Vitim.us https://gist.github.com/victornpb/7736865 * @see Unit Test https://jsfiddle.net/Victornpb/5axuh96u/ * @see http://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string/7924240#7924240 */ function occurrences(string, subString, allowOverlapping) { string += ""; subString += ""; if (subString.length <= 0) return (string.length + 1); var n = 0, pos = 0, step = allowOverlapping ? 1 : subString.length; while (true) { pos = string.indexOf(subString, pos); if (pos >= 0) { ++n; pos += step; } else break; } return n; }