From d7c18a48f5fb869bb467c9cb2c976d872856cff1 Mon Sep 17 00:00:00 2001 From: Joe Uhren Date: Sat, 30 Apr 2022 20:53:10 -0600 Subject: [PATCH] Improved (sync/backup) scripts + misc updates -Added locks to all sync processes (blocks, markets, peers, masternodes) as well as "create backup", "restore backup" and "delete database" functions. This helps prevent problems with syncing data while a backup is in progress for example -The code to initialize certain database collections on startup was moved into database.js and is now called from restore_backup.js and delete_database.js. This effectively allows the database to be deleted or restored to a completely different backup while the explorer is still running -Lock functions (create_lock, remove_lock, is_locked) were moved into explorer.js for better reusability and rewriten to be synchronous -is_locked function now accepts an array of lock files to be able to check for multiple locks in a single call -remove_sync_message() function was moved into database.js so that restore_backup.js and delete_database.js can also check for and remove the sync msg if it exists -Useful Scripts section updated in the README to make it clear that the explorer no longer needs to be stopped for these scripts to be run -Most if not all log messages now start with a capitlal letter --- README.md | 6 +- bin/instance | 82 +-- lib/database.js | 243 +++++++-- lib/explorer.js | 88 +++ scripts/benchmark.js | 11 +- scripts/create_backup.js | 90 ++- scripts/delete_database.js | 165 ++++-- scripts/restore_backup.js | 191 ++++--- scripts/sync.js | 1063 ++++++++++++++++-------------------- 9 files changed, 1085 insertions(+), 854 deletions(-) diff --git a/README.md b/README.md index 435bf0d..594d116 100644 --- a/README.md +++ b/README.md @@ -719,7 +719,7 @@ jQuery(document).ready(function($) { #### Backup Database Script -Make a complete backup of an eIquidus mongo database collection and save to compressed file. Please note that you must ensure that the explorer is NOT running at the time of backup to prevent corrupting the backup data. The following backup scenarios are supported: +Make a complete backup of an eIquidus mongo database and save to compressed file. A built-in locking mechanism prevents data from being updated or changed while a backup is in process. Backups can be safely created while the explorer is actively running and/or while the explorer is turned off. The following backup scenarios are supported: **Backup Database (No filename specified)** @@ -743,7 +743,7 @@ Make a complete backup of an eIquidus mongo database collection and save to comp #### Restore Database Script -Restore a previously saved eIquidus mongo database collection backup. :warning: **WARNING:** This will completely overwrite your existing eIquidus mongo database, so be sure to make a full backup before proceeding. Please note that the explorer should NOT be running at the time of restore to prevent problems restoring the database. +Restore a previously saved eIquidus mongo database backup. :warning: **WARNING:** This will completely overwrite your existing eIquidus mongo database, so be sure to make a full backup before proceeding. A built-in locking mechanism prevents data from being updated or changed while a backup is being restored. Backups can be safely restored while the explorer is actively running and/or while the explorer is turned off. **NOTE:** Older v1.x eIquidus database backups were compressed into tar.gz files. These older tar.gz backups can still be restored, but you must specifically add the .tar.gz suffix. Example: `npm run restore-backup /path/to/old_backup.tar.gz` @@ -767,7 +767,7 @@ The following restore scenarios are supported: #### Delete Database Script -Completely wipe the eIquidus mongo database collection clean to start again from scratch. :warning: **WARNING:** This will completely destroy all data in your existing eIquidus mongo database, so be sure to make a full backup before proceeding. Please note that the explorer should NOT be running at the time of database deletion to prevent database related problems. Delete the mongo database with the following command: +Wipe the eIquidus mongo database clean to start again from scratch. :warning: **WARNING:** This will completely destroy all data in your existing eIquidus mongo database, so be sure to make a full backup before proceeding. A built-in locking mechanism prevents data from being updated or changed while the database is being deleted. The process to delete the database can be executed while the explorer is actively running and/or while the explorer is turned off. Delete the mongo database with the following command: **Delete Database** diff --git a/bin/instance b/bin/instance index 42f9389..2a7fb73 100644 --- a/bin/instance +++ b/bin/instance @@ -13,80 +13,20 @@ dbString = dbString + ':' + settings.dbsettings.port; dbString = dbString + '/' + settings.dbsettings.database; db.connect(dbString, function() { - db.check_stats(settings.coin.name, function(exists) { - if (exists == false) { - console.log('no stats entry found, creating now..'); - db.create_stats(settings.coin.name, function() {}); - } else { - db.get_stats(settings.coin.name, function (stats) { - app.locals.stats = stats; - }); - } - }); - - // check markets/exchanges - if (settings.markets_page.enabled == true) { - // loop through and test all exchanges defined in the settings.json file - Object.keys(settings.markets_page.exchanges).forEach(function (key, index, map) { - // check if market is enabled via settings - if (settings.markets_page.exchanges[key].enabled == true) { - // check if exchange 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(market, exists) { - // check if exchange trading pair exists in the market collection - if (!exists) { - // exchange doesn't exist in the market collection so add a default definition now - console.log('no %s: %s entry found, creating now..', market, pair_key); - db.create_market(split_pair[0], split_pair[1], market, function() {}); - } - }); - } - }); - } - } + // initialize the database + db.initialize_data_startup(function() { + var server = app.listen(app.get('port'), '::', function() { + debug('Express server listening on port ' + server.address().port); }); - } - // Add new field(s) to tx collection if missing - db.check_txes(function(exists) {}); + process.on('SIGINT', () => { + server.close(() => { + var mongoose = require('mongoose'); - // Add new field(s) to masternode collection if missing - db.check_masternodes(function(exists) {}); - - db.check_richlist(settings.coin.name, function(exists) { - if (exists == false) { - console.log('no richlist entry found, creating now..'); - db.create_richlist(settings.coin.name, function() {}); - } - }); - - if (settings.blockchain_specific.heavycoin.enabled == true) { - db.check_heavy(settings.coin.name, function(exists) { - if (exists == false) { - console.log('no heavycoin entry found, creating now..'); - db.create_heavy(settings.coin.name, function() {}); - } - }); - } - - var server = app.listen(app.get('port'), '::', function() { - debug('Express server listening on port ' + server.address().port); - }); - - process.on('SIGINT', () => { - server.close(() => { - var mongoose = require('mongoose'); - - mongoose.connection.close(false, () => { - // close the main process now that all http and database connections have closed - process.exit(0); + mongoose.connection.close(false, () => { + // close the main process now that all http and database connections have closed + process.exit(0); + }); }); }); }); diff --git a/lib/database.js b/lib/database.js index 09e5bba..3af786a 100644 --- a/lib/database.js +++ b/lib/database.js @@ -212,13 +212,102 @@ function hex_to_ascii(hex) { return str; } +function init_markets(cb) { + // check if markets/exchanges feature is enabled + if (settings.markets_page.enabled == true) { + var marketCounter = 0; + + // loop through and test all exchanges defined in the settings.json file + Object.keys(settings.markets_page.exchanges).forEach(function (key, index, map) { + // check if market is enabled via settings + if (settings.markets_page.exchanges[key].enabled == true) { + // check if exchange is installed/supported + if (module.exports.fs.existsSync('./lib/markets/' + key + '.js')) { + var pairCounter = 0; + + // 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 + module.exports.check_market(key, split_pair[0], split_pair[1], function(market, exists) { + // check if exchange trading pair exists in the market collection + if (!exists) { + // exchange doesn't exist in the market collection so add a default definition now + console.log('No %s: %s entry found. Creating new entry now..', market, pair_key); + module.exports.create_market(split_pair[0], split_pair[1], market, function() { + pairCounter++; + + // check if all pairs have been tested + if (pairCounter == settings.markets_page.exchanges[key].trading_pairs.length) + marketCounter++; + + // check if all exchanges have been tested + if (marketCounter == Object.keys(settings.markets_page.exchanges).length) { + // finished initializing markets + return cb(); + } + }); + } else { + pairCounter++; + + // check if all pairs have been tested + if (pairCounter == settings.markets_page.exchanges[key].trading_pairs.length) + marketCounter++; + } + + // check if all exchanges have been tested + if (marketCounter == Object.keys(settings.markets_page.exchanges).length) { + // finished initializing markets + return cb(); + } + }); + } else { + pairCounter++; + + // check if all pairs have been tested + if (pairCounter == settings.markets_page.exchanges[key].trading_pairs.length) + marketCounter++; + } + }); + } else + marketCounter++; + } else + marketCounter++; + }); + + // check if all exchanges have been tested + if (marketCounter == Object.keys(settings.markets_page.exchanges).length) { + // finished initializing markets + return cb(); + } + } else + return cb(); +} + +function init_heavy(cb) { + if (settings.blockchain_specific.heavycoin.enabled == true) { + module.exports.check_heavy(settings.coin.name, function(exists) { + if (exists == false) { + console.log('No heavycoin entry found. Creating new entry now..'); + module.exports.create_heavy(settings.coin.name, function() { + return cb(); + }); + } else + return cb(); + }); + } else + return cb(); +} + module.exports = { // initialize DB connect: function(database, cb) { mongoose.connect(database, function(err) { if (err) { - console.log('Unable to connect to database: %s', database); - console.log('Aborting'); + console.log('Error: Unable to connect to database: %s', database); process.exit(999); } @@ -377,21 +466,25 @@ module.exports = { }); }, - create_stats: function(coin, cb) { - var newStats = new Stats({ - coin: coin, - last: 0 - }); + create_stats: function(coin, skip, cb) { + // check if stats need to be created + if (!skip) { + var newStats = new Stats({ + coin: coin, + last: 0 + }); - newStats.save(function(err) { - if (err) { - console.log(err); - return cb(); - } else { - console.log("initial stats entry created for %s", coin); - return cb(); - } - }); + newStats.save(function(err) { + if (err) { + console.log(err); + return cb(); + } else { + console.log("Initial stats entry created for %s", coin); + return cb(); + } + }); + } else + return cb(); }, get_address: function(hash, caseSensitive, cb) { @@ -630,7 +723,7 @@ module.exports = { console.log(err); return cb(); } else { - console.log("initial market entry created for %s: %s", market, coin_symbol +'/' + pair_symbol); + console.log("Initial market entry created for %s: %s", market, coin_symbol +'/' + pair_symbol); return cb(); } }); @@ -653,21 +746,25 @@ module.exports = { }); }, - // creates initial richlist entry in database; called on first launch of explorer - create_richlist: function(coin, cb) { - var newRichlist = new Richlist({ - coin: coin - }); + // creates initial richlist entry in database; called on first launch of explorer + after restore or delete database + create_richlist: function(coin, skip, cb) { + // check if stats need to be created + if (!skip) { + var newRichlist = new Richlist({ + coin: coin + }); - newRichlist.save(function(err) { - if (err) { - console.log(err); - return cb(); - } else { - console.log("initial richlist entry created for %s", coin); - return cb(); - } - }); + newRichlist.save(function(err) { + if (err) { + console.log(err); + return cb(); + } else { + console.log("Initial richlist entry created for %s", coin); + return cb(); + } + }); + } else + return cb(); }, // drops richlist data for given coin @@ -700,7 +797,7 @@ module.exports = { console.log(err); return cb(); } else { - console.log("initial heavycoin entry created for %s", coin); + console.log("Initial heavycoin entry created for %s", coin); return cb(); } }); @@ -812,7 +909,7 @@ module.exports = { }, function() { // update reward_last_updated value module.exports.update_last_updated_stats(settings.coin.name, { reward_last_updated: Math.floor(new Date() / 1000) }, function (new_cb) { - console.log('heavycoin update complete'); + console.log('Heavycoin update complete'); return cb(); }); }); @@ -849,7 +946,7 @@ module.exports = { newNetworkHistory.save(function(err) { // check for errors if (err) { - console.log('error updating network history: ' + err); + console.log('Error updating network history: ' + err); return cb(); } else { // get the count of network history records @@ -866,12 +963,12 @@ module.exports = { // delete old network history records NetworkHistory.deleteMany({blockindex: {$in: ids}}, function(err) { - console.log('network history update complete'); + console.log('Network history update complete'); return cb(); }); }); } else { - console.log('network history update complete'); + console.log('Network history update complete'); return cb(); } }); @@ -977,7 +1074,7 @@ module.exports = { lib.get_blockcount( function (count) { // check to ensure count is a positive number if (!count || (count != null && typeof count === 'number' && count < 0)) { - console.log('Unable to connect to explorer API'); + console.log('Error: Unable to connect to explorer API'); return cb(false); } @@ -993,7 +1090,7 @@ module.exports = { connections: (connections ? connections : 0) }, function(err) { if (err) - console.log("Error during Stats Update: ", err); + console.log("Error during stats update: %s", err); return cb({ coin: coin, @@ -1005,7 +1102,7 @@ module.exports = { }); }); } else { - console.log("Error during Stats Update: ", (err ? err : 'cannot find stats collection')); + console.log("Error during stats update: %s", (err ? err : 'Cannot find stats collection')); return cb(false); } }); @@ -1038,7 +1135,7 @@ module.exports = { txes: txes }, function() {}); } else if (check_only) { - console.log('checking block ' + block_height + '...'); + console.log('Checking block ' + block_height + '...'); } lib.get_blockhash(block_height, function(blockhash) { @@ -1077,7 +1174,7 @@ module.exports = { }, timeout); }); } else { - console.log('block not found: %s', blockhash); + console.log('Block not found: %s', blockhash); setTimeout( function() { next_block(); @@ -1203,7 +1300,7 @@ module.exports = { // add or update a single masternode add_update_masternode(masternode, add, cb) { if (masternode.proTxHash == null && masternode.txhash == null) { - console.log('Masternode Update - TXid is missing'); + console.log('Masternode update error: TXid is missing'); return cb(false); } else { @@ -1444,5 +1541,67 @@ module.exports = { }); }, + initialize_data_startup: function(cb) { + console.log('Initializing database.. Please wait...'); + + // check if stats collection is initialized + module.exports.check_stats(settings.coin.name, function(stats_exists) { + var skip = true; + + // determine if stats collection already exists + if (stats_exists == false) { + console.log('No stats entry found. Creating new entry now..'); + skip = false; + } + + // initialize the stats collection + module.exports.create_stats(settings.coin.name, skip, function() { + // check and initialize the markets collection + init_markets(function() { + // add new field(s) to tx collection if missing + module.exports.check_txes(function(txes_exists) { + // add new field(s) to masternode collection if missing + module.exports.check_masternodes(function(masternodes_exists) { + // check if richlist collection is initialized + module.exports.check_richlist(settings.coin.name, function(richlist_exists) { + skip = true; + + // determine if richlist collection already exists + if (richlist_exists == false) { + console.log('No richlist entry found. Creating new entry now..'); + skip = false; + } + + // initialize the richlist collection + module.exports.create_richlist(settings.coin.name, skip, function() { + // check and initialize the heavycoin collection + init_heavy(function() { + // finished initializing startup data + console.log('Database initialization complete'); + return cb(); + }); + }); + }); + }); + }); + }); + }); + }); + }, + + remove_sync_message: function() { + var filePath = './tmp/show_sync_message.tmp'; + + // Check if the show sync stub file exists + if (fs.existsSync(filePath)) { + // File exists, so delete it now + try { + fs.unlinkSync(filePath); + } catch (err) { + console.log(err); + } + } + }, + fs: fs }; \ No newline at end of file diff --git a/lib/explorer.js b/lib/explorer.js index 182d58b..fbec3a0 100644 --- a/lib/explorer.js +++ b/lib/explorer.js @@ -1315,5 +1315,93 @@ module.exports = { }, function() { return cb(arr_vin, tx_type); }); + }, + + create_lock: function(lock) { + const fs = require('fs'); + var fname = './tmp/' + lock + '.pid'; + + try { + fs.appendFileSync(fname, process.pid.toString()); + return true; + } catch(err) { + console.log("Error: Unable to remove lock: %s", fname); + return false; + } + }, + + remove_lock: function(lock) { + const fs = require('fs'); + var fname = './tmp/' + lock + '.pid'; + + try { + fs.unlinkSync(fname); + return true; + } catch(err) { + console.log("Error: Unable to remove lock: %s", fname); + return false; + } + }, + + is_locked: function(lock_array) { + const fs = require('fs'); + const path = require('path'); + var retVal = false; + + // loop through all lock files that need to be checked + for (var i = 0; i < lock_array.length; i++) { + var pidFile = path.join(path.dirname(__dirname), 'tmp', `${lock_array[i]}.pid`); + + // check if the script is already running (tmp/file.pid file already exists) + if (fs.existsSync(pidFile)) { + const { execSync } = require('child_process'); + var deactivateLock = false; + + // the pid file exists + // determine the operating system + switch (process.platform) { + case 'win32': + // windows + // run a cmd that will determine if the lock should still be active + var cmdResult = execSync(`tasklist /FI "PID eq ${fs.readFileSync(pidFile).toString()}"`); + + // check if the process that created the lock is actually still running (crude check by testing for # of carriage returns or node.exe process running, but should work universally across different systems and languages) + if (cmdResult.toString().split('\n').length < 4 || cmdResult.toString().toLowerCase().indexOf('\nnode.exe') == -1) { + // lock should be deactivated + deactivateLock = true; + } + + break; + default: + // linux or other + // run a cmd that will determine if the lock should still be active + + try { + var cmdResult = execSync('ps -p `cat ' + pidFile + '` > /dev/null'); + } catch (err) { + // if an error occurs, the process is NOT running and therefore the lock should be deactivated + deactivateLock = true; + } + } + + // check if the lock should be deactivated + if (deactivateLock) { + // script is not actually running so the lock file can be deleted + try { + fs.rmSync(pidFile); + } catch(err) { + console.log(`Failed to delete lock file ${pidFile}: ${err}`); + } + } else { + // script is running + console.log(`${lock_array[i]} script is running..`); + retVal = true; + + break; + } + } + } + + return retVal; } }; \ No newline at end of file diff --git a/scripts/benchmark.js b/scripts/benchmark.js index 340ef89..f92b641 100644 --- a/scripts/benchmark.js +++ b/scripts/benchmark.js @@ -6,9 +6,9 @@ var mongoose = require('mongoose'), var COUNT = 5000; // number of blocks to index -function exit() { +function exit(exitCode) { mongoose.disconnect(); - process.exit(0); + process.exit(exitCode); } var dbString = 'mongodb://' + settings.dbsettings.user; @@ -19,9 +19,8 @@ dbString = dbString + "/IQUIDUS-BENCHMARK"; mongoose.connect(dbString, function(err) { if (err) { - console.log('Unable to connect to database: %s', dbString); - console.log('Aborting'); - exit(); + console.log('Error: Unable to connect to database: %s', dbString); + exit(999); } Tx.deleteMany({}, function(err) { @@ -40,7 +39,7 @@ mongoose.connect(dbString, function(err) { }; console.log(stats); - exit(); + exit(0); }); }); }); diff --git a/scripts/create_backup.js b/scripts/create_backup.js index d6b370d..6bf7f54 100644 --- a/scripts/create_backup.js +++ b/scripts/create_backup.js @@ -1,8 +1,23 @@ const fs = require('fs'); const path = require('path'); +const lib = require('../lib/explorer'); const archiveSuffix = '.bak'; +const backupLockName = 'backup'; var backupPath = path.join(path.dirname(__dirname), 'backups'); var backupFilename; +var lockCreated = false; + +// exit function used to cleanup lock before finishing script +function exit(exitCode) { + // only remove backup lock if it was created in this session + if (!lockCreated || lib.remove_lock(backupLockName) == true) { + // clean exit with previous exit code + process.exit(exitCode); + } else { + // error removing lock + process.exit(1); + } +} // check if a backup filename was passed into the script if (process.argv[2] != null && process.argv[2] != '') { @@ -38,39 +53,60 @@ if (!fs.existsSync(backupPath)) { // check if backup file already exists if (!fs.existsSync(path.join(backupPath, `${backupFilename}${archiveSuffix}`))) { - const { exec } = require('child_process'); - const settings = require('../lib/settings'); - const randomDirectoryName = Math.random().toString(36).substring(2, 15) + Math.random().toString(23).substring(2, 5); + // check if the "create backup" process is already running + if (lib.is_locked([backupLockName]) == false) { + // create a new backup lock before checking the rest of the locks to minimize problems with running scripts at the same time + lib.create_lock(backupLockName); + // ensure the lock will be deleted on exit + lockCreated = true; + // check all other possible locks since backups should not run at the same time that data is being changed + if (lib.is_locked(['restore', 'delete', 'index', 'markets', 'peers', 'masternodes']) == false) { + // all tests passed. OK to run backup + console.log("Script launched with pid: " + process.pid); - // execute backup - const backupProcess = exec(`mongodump --host="${settings.dbsettings.address}" --port="${settings.dbsettings.port}" --username="${settings.dbsettings.user}" --password="${settings.dbsettings.password}" --db="${settings.dbsettings.database}" --archive="${path.join(backupPath, backupFilename + archiveSuffix)}" --gzip`); + const { exec } = require('child_process'); + const settings = require('../lib/settings'); + const randomDirectoryName = Math.random().toString(36).substring(2, 15) + Math.random().toString(23).substring(2, 5); - backupProcess.stdout.on('data', (data) => { - console.log(data); - }); + // execute backup + const backupProcess = exec(`mongodump --host="${settings.dbsettings.address}" --port="${settings.dbsettings.port}" --username="${settings.dbsettings.user}" --password="${settings.dbsettings.password}" --db="${settings.dbsettings.database}" --archive="${path.join(backupPath, backupFilename + archiveSuffix)}" --gzip`); - backupProcess.stderr.on('data', (data) => { - console.log(Buffer.from(data).toString()); - }); + backupProcess.stdout.on('data', (data) => { + console.log(data); + }); - backupProcess.on('error', (error) => { - console.log(error); - }); + backupProcess.stderr.on('data', (data) => { + console.log(Buffer.from(data).toString()); + }); - backupProcess.on('exit', (code, signal) => { - if (code) { - console.log(`Process exit with code: ${code}`); - process.exit(1); - } else if (signal) { - console.log(`Process killed with signal: ${signal}`); - process.exit(1); - } else { - console.log(`Backup saved successfully to ${path.join(backupPath, backupFilename + archiveSuffix)}`); - process.exit(0); - } - }); + backupProcess.on('error', (error) => { + console.log(error); + }); + + backupProcess.on('exit', (code, signal) => { + if (code) { + console.log(`Process exit with code: ${code}`); + exit(code); + } else if (signal) { + console.log(`Process killed with signal: ${signal}`); + exit(1); + } else { + console.log(`Backup saved successfully to ${path.join(backupPath, backupFilename + archiveSuffix)}`); + exit(0); + } + }); + } else { + // another script process is currently running + console.log("Backup aborted"); + exit(2); + } + } else { + // backup process is already running + console.log("Backup aborted"); + exit(2); + } } else { // backup already exists console.log(`A backup named ${backupFilename}${archiveSuffix} already exists`); - process.exit(1); + exit(2); } \ No newline at end of file diff --git a/scripts/delete_database.js b/scripts/delete_database.js index 4e8fc20..c96c7e4 100644 --- a/scripts/delete_database.js +++ b/scripts/delete_database.js @@ -1,31 +1,66 @@ +const lib = require('../lib/explorer'); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); +const deleteLockName = 'delete'; +var lockCreated = false; + +// exit function used to cleanup lock before finishing script +function exit(mongoose, exitCode) { + const db = require('../lib/database'); + + // check if mongo was connected + if (mongoose != null) { + // check if this is a clean exit + if (exitCode == 0) { + // initialize the database + db.initialize_data_startup(function() { + // disconnect mongo connection + mongoose.disconnect(); + + // finish exit cleanup + finishExit(db, exitCode); + }); + } else { + // disconnect mongo connection + mongoose.disconnect(); + + // finish exit cleanup + finishExit(db, exitCode); + } + } else { + // finish exit cleanup + finishExit(db, exitCode); + } +} + +function finishExit(db, exitCode) { + // always check for and remove the sync msg if exists + db.remove_sync_message(); + + // only remove delete lock if it was created in this session + if (!lockCreated || lib.remove_lock(deleteLockName) == true) { + // clean exit with previous exit code + process.exit(exitCode); + } else { + // error removing lock + process.exit(1); + } +} function drop_collection(mongoose, colName, cb) { // attempt to delete the collection mongoose.connection.db.dropCollection(colName, function(err, result) { if (err || !result) { - console.log(`Unable to delete the ${colName} collection`); - console.log('Aborting'); - process.exit(1); + console.log(`Error: Unable to delete the ${colName} collection`); + exit(mongoose, 1); } else return cb(true); }); } -function finished_deleting(mongoose) { - console.log('Finished deleting database'); - - // disconnect from mongo database - mongoose.disconnect(); - - // delete database complete - process.exit(0); -} - console.log('You are about to delete the entire eIquidus database.'); // prompt for deleting explorer database @@ -40,62 +75,82 @@ rl.question('Are you sure you want to do this? [y/n]: ', function (deleteAnswer) case 'yes': case 'YES': case 'Yes': - const settings = require('../lib/settings'); - const mongoose = require('mongoose'); - const dbString = `mongodb://${settings.dbsettings.user}:${settings.dbsettings.password}@${settings.dbsettings.address}:${settings.dbsettings.port}/${settings.dbsettings.database}`; + // check if the "delete database" process is already running + if (lib.is_locked([deleteLockName]) == false) { + // create a new delete lock before checking the rest of the locks to minimize problems with running scripts at the same time + lib.create_lock(deleteLockName); + // ensure the lock will be deleted on exit + lockCreated = true; + // check all other possible locks since database deletion should not run at the same time that data is being changed + if (lib.is_locked(['backup', 'restore', 'index', 'markets', 'peers', 'masternodes']) == false) { + // all tests passed. OK to run delete + console.log("Script launched with pid: " + process.pid); - console.log('Connecting to database..'); + const settings = require('../lib/settings'); + const mongoose = require('mongoose'); + const dbString = `mongodb://${settings.dbsettings.user}:${settings.dbsettings.password}@${settings.dbsettings.address}:${settings.dbsettings.port}/${settings.dbsettings.database}`; - // connect to mongo database - mongoose.connect(dbString, function(err) { - if (err) { - console.log('Unable to connect to database: %s', dbString); - console.log('Aborting'); - process.exit(1); - } else { - // get the list of collections - mongoose.connection.db.listCollections().toArray(function (err, collections) { + console.log('Connecting to database..'); + + // connect to mongo database + mongoose.connect(dbString, function(err) { if (err) { - console.log('Unable to list collections in database: %s', err); - console.log('Aborting'); - process.exit(1); + console.log('Error: Unable to connect to database: %s', dbString); + exit(mongoose, 999); } else { - // check if there are any collections - if (collections.length > 0) { - var counter = 0; + // get the list of collections + mongoose.connection.db.listCollections().toArray(function (err, collections) { + if (err) { + console.log('Error: Unable to list collections in database: %s', err); + exit(mongoose, 1); + } else { + // check if there are any collections + if (collections.length > 0) { + var counter = 0; - // loop through all collections - collections.forEach((collection) => { - console.log(`Deleting ${collection.name}..`); + // loop through all collections + collections.forEach((collection) => { + console.log(`Deleting ${collection.name}..`); - // delete this collection - drop_collection(mongoose, collection.name, function(retVal) { - // check if the collection was successfully deleted - if (retVal) - counter++; + // delete this collection + drop_collection(mongoose, collection.name, function(retVal) { + // check if the collection was successfully deleted + if (retVal) + counter++; - // check if the last collection was deleted - if (counter == collections.length) { - // finish the delete process - finished_deleting(mongoose); - } - }); - }); - } else { - // nothing to delete - console.log('Nothing to delete, the database is already empty..'); + // check if the last collection was deleted + if (counter == collections.length) { + // finish the delete process + console.log('Finished deleting database'); + exit(mongoose, 0); + } + }); + }); + } else { + // nothing to delete + console.log('Nothing to delete, the database is already empty..'); - // finish the delete process - finished_deleting(mongoose); - } + // finish the delete process + exit(mongoose, 0); + } + } + }); } }); + } else { + // another script process is currently running + console.log("Delete aborted"); + exit(null, 2); } - }); + } else { + // delete process is already running + console.log("Delete aborted"); + exit(null, 2); + } break; default: console.log('Process aborted. Nothing was deleted.'); - process.exit(1); + exit(null, 2); } }); \ No newline at end of file diff --git a/scripts/restore_backup.js b/scripts/restore_backup.js index 671c0bd..03d4091 100644 --- a/scripts/restore_backup.js +++ b/scripts/restore_backup.js @@ -1,8 +1,54 @@ const fs = require('fs'); const path = require('path'); +const lib = require('../lib/explorer'); const archiveSuffix = '.bak'; const oldArchiveSuffix = '.tar.gz'; +const restoreLockName = 'restore'; const defaultBackupPath = path.join(path.dirname(__dirname), 'backups'); +var lockCreated = false; + +// exit function used to cleanup lock before finishing script +function exit(mongoose, exitCode) { + const db = require('../lib/database'); + + // check if mongo was connected + if (mongoose != null) { + // check if this is a clean exit + if (exitCode == 0) { + // initialize the database + db.initialize_data_startup(function() { + // disconnect mongo connection + mongoose.disconnect(); + + // finish exit cleanup + finishExit(db, exitCode); + }); + } else { + // disconnect mongo connection + mongoose.disconnect(); + + // finish exit cleanup + finishExit(db, exitCode); + } + } else { + // finish exit cleanup + finishExit(db, exitCode); + } +} + +function finishExit(db, exitCode) { + // always check for and remove the sync msg if exists + db.remove_sync_message(); + + // only remove restore lock if it was created in this session + if (!lockCreated || lib.remove_lock(restoreLockName) == true) { + // clean exit with previous exit code + process.exit(exitCode); + } else { + // error removing lock + process.exit(1); + } +} function check_module_directory_exists(dirName, cb) { // check if module directory exists @@ -24,16 +70,14 @@ function drop_collection(mongoose, colName, cb) { // attempt to delete the collection mongoose.connection.db.dropCollection(colName, function(err, result) { if (err || !result) { - console.log(`Unable to delete the ${colName} collection`); - console.log('Aborting'); - process.exit(1); + console.log(`Error: Unable to delete the ${colName} collection`); + exit(mongoose, 1); } else return cb(true); }); } -function delete_database(settings, cb) { - const mongoose = require('mongoose'); +function delete_database(mongoose, settings, cb) { const dbString = `mongodb://${settings.dbsettings.user}:${settings.dbsettings.password}@${settings.dbsettings.address}:${settings.dbsettings.port}/${settings.dbsettings.database}`; console.log('Connecting to database..'); @@ -41,16 +85,14 @@ function delete_database(settings, cb) { // connect to mongo database mongoose.connect(dbString, function(err) { if (err) { - console.log('Unable to connect to database: %s', dbString); - console.log('Aborting'); - process.exit(1); + console.log('Error: Unable to connect to database: %s', dbString); + exit(mongoose, 999); } else { // get the list of collections mongoose.connection.db.listCollections().toArray(function (err, collections) { if (err) { - console.log('Unable to list collections in database: %s', err); - console.log('Aborting'); - process.exit(1); + console.log('Error: Unable to list collections in database: %s', err); + exit(mongoose, 1); } else { // check if there are any collections if (collections.length > 0) { @@ -68,9 +110,6 @@ function delete_database(settings, cb) { // check if the last collection was deleted if (counter == collections.length) { - // disconnect from mongo database - mongoose.disconnect(); - // finished the delete process return cb(true); } @@ -78,9 +117,6 @@ function delete_database(settings, cb) { }); } else { // nothing to delete - // disconnect from mongo database - mongoose.disconnect(); - return cb(true); } } @@ -89,7 +125,7 @@ function delete_database(settings, cb) { }); } -function restore_backup(settings, backupPath, extractedPath, gZip) { +function restore_backup(mongoose, settings, backupPath, extractedPath, gZip) { const { exec } = require('child_process'); console.log('Restoring backup.. Please wait..'); @@ -112,10 +148,10 @@ function restore_backup(settings, backupPath, extractedPath, gZip) { restoreProcess.on('exit', (code, signal) => { if (code) { console.log(`Process exit with code: ${code}`); - process.exit(1); + exit(mongoose, code); } else if (signal) { console.log(`Process killed with signal: ${signal}`); - process.exit(1); + exit(mongoose, 1); } else { // check if gZip is enabled if (!gZip) { @@ -129,7 +165,7 @@ function restore_backup(settings, backupPath, extractedPath, gZip) { // restore backup complete console.log(`Backup restored from ${path.basename(backupPath)} successfully`); - process.exit(0); + exit(mongoose, 0); } }); } @@ -178,67 +214,92 @@ if (process.argv[2] != null && process.argv[2] != '') { case 'yes': case 'YES': case 'Yes': - const settings = require('../lib/settings'); + // check if the "restore backup" process is already running + if (lib.is_locked([restoreLockName]) == false) { + // create a new restore lock before checking the rest of the locks to minimize problems with running scripts at the same time + lib.create_lock(restoreLockName); + // ensure the lock will be deleted on exit + lockCreated = true; + // check all other possible locks since restoring backups should not run at the same time that data is being changed + if (lib.is_locked(['backup', 'delete', 'index', 'markets', 'peers', 'masternodes']) == false) { + // all tests passed. OK to run restore + console.log("Script launched with pid: " + process.pid); - // check if this is a tar.gz (older explorer backup format) - if (!backupPath.endsWith(oldArchiveSuffix)) { - // newer backup format (.bak) - // delete all collections from existing database - delete_database(settings, function(retVal) { - if (retVal) { - // move on to the restore process - restore_backup(settings, backupPath, backupPath, true); - } - }); - } else { - // older backup format (.tar.gz) - // check if the tar module is already installed - check_module_directory_exists('tar', function(retVal) { - const tar = require('tar'); + const settings = require('../lib/settings'); - console.log('Extracting backup files.. Please wait..'); + // check if this is a tar.gz (older explorer backup format) + if (!backupPath.endsWith(oldArchiveSuffix)) { + const mongoose = require('mongoose'); - // extract the backup archive - tar.x({ file: backupPath, cwd: defaultBackupPath, gzip: true }, function() { - var extractedPath = path.join(defaultBackupPath, path.basename(backupPath).replace(oldArchiveSuffix, '')); + // newer backup format (.bak) + // delete all collections from existing database + delete_database(mongoose, settings, function(retVal) { + if (retVal) { + // move on to the restore process + restore_backup(mongoose, settings, backupPath, backupPath, true); + } + }); + } else { + // older backup format (.tar.gz) + // check if the tar module is already installed + check_module_directory_exists('tar', function(retVal) { + const tar = require('tar'); - // check if this is a valid backup archive now that the files have been extracted - if (fs.existsSync(`${path.join(extractedPath, settings.dbsettings.database)}`)) { - // delete all collections from existing database - delete_database(settings, function(retVal) { - if (retVal) { - // move on to the restore process - restore_backup(settings, backupPath, extractedPath, false); + console.log('Extracting backup files.. Please wait..'); + + // extract the backup archive + tar.x({ file: backupPath, cwd: defaultBackupPath, gzip: true }, function() { + var extractedPath = path.join(defaultBackupPath, path.basename(backupPath).replace(oldArchiveSuffix, '')); + + // check if this is a valid backup archive now that the files have been extracted + if (fs.existsSync(`${path.join(extractedPath, settings.dbsettings.database)}`)) { + const mongoose = require('mongoose'); + + // delete all collections from existing database + delete_database(mongoose, settings, function(retVal) { + if (retVal) { + // move on to the restore process + restore_backup(mongoose, settings, backupPath, extractedPath, false); + } + }); + } else { + // backup file is not a valid mongo database backup + // try to remove the backup directory + try { + fs.rmSync(extractedPath, { recursive: true }); + } catch { + // do nothing + } finally { + console.log(`${path.basename(backupPath)} is not a valid backup file`); + exit(null, 1); + } } }); - } else { - // backup file is not a valid mongo database backup - // try to remove the backup directory - try { - fs.rmSync(extractedPath, { recursive: true }); - } catch { - // do nothing - } finally { - console.log(`${path.basename(backupPath)} is not a valid backup file`); - process.exit(1); - } - } - }); - }); + }); + } + } else { + // another script process is currently running + console.log("Restore aborted"); + exit(null, 2); + } + } else { + // restore process is already running + console.log("Restore aborted"); + exit(null, 2); } break; default: console.log('Process aborted. Nothing was restored.'); - process.exit(1); + exit(null, 2); } }); } else { // backup does not exist console.log(`${backupPath} cannot be found`); - process.exit(1); + exit(null, 2); } } else { console.log('No backup file specified'); - process.exit(1); + exit(null, 2); } \ No newline at end of file diff --git a/scripts/sync.js b/scripts/sync.js index 07cca5a..5f0acb3 100644 --- a/scripts/sync.js +++ b/scripts/sync.js @@ -10,6 +10,7 @@ var mongoose = require('mongoose'), var mode = 'update'; var database = 'index'; var block_start = 1; +var lockCreated = false; // displays usage and exits function usage() { @@ -33,7 +34,103 @@ function usage() { 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); + process.exit(100); +} + +// exit function used to cleanup before finishing script +function exit(exitCode) { + // always disconnect mongo connection + mongoose.disconnect(); + + // only remove sync lock if it was created in this session + if (!lockCreated || lib.remove_lock(database) == true) { + // clean exit with previous exit code + process.exit(exitCode); + } else { + // error removing lock + process.exit(1); + } +} + +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 update_network_history(height, network_history_enabled, cb) { + if (network_history_enabled == true) { + db.update_network_history(height, 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 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(0); + }); + } else { + // display error msg + console.log('Error: %s', err); + exit(1); + } + }); +} + +/** 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; } // check options @@ -81,7 +178,7 @@ if (process.argv[2] == null || process.argv[2] == 'index' || process.argv[2] == mode = 'reindex'; } else { console.log('Process aborted. Nothing was deleted'); - process.exit(0); + exit(2); } break; @@ -97,325 +194,99 @@ if (process.argv[2] == null || process.argv[2] == 'index' || process.argv[2] == default: usage(); } - - // check if mode is set - if (mode != null) { - const path = require('path'); - const pidFile = path.join(path.dirname(__dirname), 'tmp', `${database}.pid`); - - // check if the script is already running (tmp/index.pid file already exists) - if (db.fs.existsSync(pidFile)) { - const { execSync } = require('child_process'); - var deactivateLock = false; - - // the tmp/index.pid file exists - // determine the operating system - switch (process.platform) { - case 'win32': - // windows - // run a cmd that will determine if the lock should still be active - var cmdResult = execSync(`tasklist /FI "PID eq ${db.fs.readFileSync(pidFile).toString()}"`); - - // check if the process that created the lock is actually still running (crude check by testing for # of carriage returns or node.exe process running, but should work universally across different systems and languages) - if (cmdResult.toString().split('\n').length < 4 || cmdResult.toString().toLowerCase().indexOf('\nnode.exe') == -1) { - // lock should be deactivated - deactivateLock = true; - } - - break; - default: - // linux or other - // run a cmd that will determine if the lock should still be active - - try { - var cmdResult = execSync('ps -p `cat ' + pidFile + '` > /dev/null'); - } catch (err) { - // if an error occurs, the process is NOT running and therefore the lock should be deactivated - deactivateLock = true; - } - } - - // check if the lock should be deactivated - if (deactivateLock) { - // script is not actually running so the lock file can be deleted - db.fs.rmSync(pidFile); - } - } - } -} 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 if (process.argv[2] == 'peers' || process.argv[2] == 'masternodes') + database = process.argv[2]; +else if (process.argv[2] == 'market') + database = `${process.argv[2]}s`; else usage(); -function create_lock(cb) { - if (database == 'index') { - var fname = './tmp/' + database + '.pid'; +// check if this sync option is already running/locked +if (lib.is_locked([database]) == false) { + // create a new sync lock before checking the rest of the locks to minimize problems with running scripts at the same time + lib.create_lock(database); + // ensure the lock will be deleted on exit + lockCreated = true; + // check the backup, restore and delete locks since those functions would be problematic when updating data + if (lib.is_locked(['backup', 'restore', 'delete']) == false) { + // all tests passed. OK to run sync + console.log("Script launched with pid: " + process.pid); - db.fs.appendFile(fname, process.pid.toString(), function (err) { + if (mode == 'update') + console.log(`Syncing ${(database == 'index' ? 'blocks' : database)}.. Please wait..`); + + 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; + + mongoose.connect(dbString, function(err) { if (err) { - console.log("Error: unable to create %s", fname); - process.exit(1); - } else - return cb(); - }); - } else - return cb(); -} + console.log('Error: Unable to connect to database: %s', dbString); + exit(1); + } 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(1); + } 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'); -function remove_lock(cb) { - if (database == 'index') { - var fname = './tmp/' + database + '.pid'; + console.log('Deleting addresses.. Please wait..'); + Address.deleteMany({}, function(err2) { + console.log('Addresses deleted successfully'); - 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(); -} + console.log('Deleting address transactions.. Please wait..'); + AddressTx.deleteMany({}, function(err3) { + console.log('Address transactions deleted successfully'); -function is_locked(cb) { - if (database == 'index') { - var fname = './tmp/' + database + '.pid'; + 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'); - db.fs.exists(fname, function (exists) { - if (exists) - return cb(true); - else - return cb(false); - }); - } else - return cb(); -} + 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'); -function exit() { - remove_lock(function() { - mongoose.disconnect(); - process.exit(0); - }); -} + // Check if the sync msg should be shown + check_show_sync_message(stats.count); -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; + 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) { + // check for and update network history data if applicable + update_network_history(nstats.last, settings.network_history.enabled, function(network_hist) { + // always check for and remove the sync msg if exists + db.remove_sync_message(); -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) { - // check for and update network history data if applicable - update_network_history(nstats.last, settings.network_history.enabled, function(network_hist) { - // always check for and remove the sync msg if exists - remove_sync_message(); - - console.log('reindex complete (block: %s)', nstats.last); - exit(); - }); - }); + console.log('Reindex complete (block: %s)', nstats.last); + exit(0); }); }); }); @@ -427,314 +298,336 @@ if (database == 'peers') { }); }); }); - } else if (mode == 'check') { - console.log('starting check.. please wait..'); + }); + }); + } else if (mode == 'check') { + console.log('Checking blocks.. 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, block_start, stats.count, stats.txes, settings.sync.check_timeout, true, function() { + db.get_stats(settings.coin.name, function(nstats) { + console.log('Block check complete (block: %s)', nstats.last); + exit(0); + }); + }); + } 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() { - // 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) { - // check for and update network history data if applicable - update_network_history(nstats.last, settings.network_history.enabled, function(network_hist) { - // always check for and remove the sync msg if exists - remove_sync_message(); + db.update_tx_db(settings.coin.name, last, 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) { + // check for and update network history data if applicable + update_network_history(nstats.last, settings.network_history.enabled, function(network_hist) { + // always check for and remove the sync msg if exists + db.remove_sync_message(); - console.log('update complete (block: %s)', nstats.last); - exit(); - }); - }); + console.log('Block update complete (block: %s)', nstats.last); + exit(0); }); }); }); }); }); }); - } else if (mode == 'reindex-rich') { - console.log('check richlist'); + }); + }); + } 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.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.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.create_richlist(settings.coin.name, false, function() { + console.log('Richlist created'); - db.update_richlist('received', function() { - console.log('richlist updated received.'); + 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(); - }); - }); + 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(0); }); }); }); }); - } else if (mode == 'reindex-txcount') { - console.log('calculating tx count.. please wait..'); + }); + }); + } 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 + // 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(0); + }); + }); + } 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(0); + }); + } 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(0); + }); + } + }); + } + } else { + // update_db threw an error so exit + exit(1); + } + }); + } + }); + } else if (database == 'peers') { + 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(1); + }); + } + + // peer already exists + console.log('Updated peer %s [%s/%s]', address, (i + 1).toString(), body.length.toString()); + loop.next(); + } else { + const rateLimitLib = require('../lib/ratelimit'); + const 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(1); + } 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('tx count update complete'); - exit(); + console.log('Added new peer %s [%s/%s]', address, (i + 1).toString(), body.length.toString()); + loop.next(); }); - }); - } else if (mode == 'reindex-last') { - console.log('finding last blockindex.. please wait..'); + } + }); + }); + } + }); + }, 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(0); + }); + }); + } else { + console.log('No peers found'); + exit(2); + } + }); + } else if (database == 'masternodes') { + lib.get_masternodelist(function(body) { + if (body != null) { + var isObject = false; + var objectKeys = null; - // 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.'); + // 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; + } - Stats.updateOne({coin: settings.coin.name}, { - last: 0 - }, function() { - console.log('last blockindex update complete'); - exit(); + 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(1); + } + }); + }, 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(0); + }); + }); + }); + } else { + console.log('No masternodes found'); + exit(2); + } + }); + } 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('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(); - }); + 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 { - // update_db threw an error so exit - exit(); - } - }); + }); + } else { + // market not installed + console.log('%s market not installed', key); + complete++; + + if (complete == total_pairs) + get_last_usd_price(); + } } }); } 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(); - } + // no market trading pairs are enabled + console.log('Error: No market trading pairs are enabled in settings'); + exit(1); } - }); - }); - } - }); -} - -function update_heavy(coin, height, count, heavycoin_enabled, cb) { - if (heavycoin_enabled == true) { - db.update_heavy(coin, height, count, function() { - return cb(true); + } else { + // market page is not enabled + console.log('Error: Market feature is disabled in settings'); + exit(1); + } + } }); - } else - return cb(false); -} - -function update_network_history(height, network_history_enabled, cb) { - if (network_history_enabled == true) { - db.update_network_history(height, 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; + } else { + // another script process is currently running + console.log("Sync aborted"); + exit(2); } - - 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; +} else { + // sync process is already running + console.log("Sync aborted"); + exit(2); } \ No newline at end of file