Files
purple-explorer/scripts/sync.js
T
Joe Uhren 65c48ea829 Added market sync options: average and coingecko
-The previous market price calculation setting was hardcoded to only display market and USD prices for a single exchange and trading pair which was not very accurate for coins listed on multiple exchanges or with multiple trading pairs. The new default is to average the market prices for all supported exchanges and trading pairs
-The coingecko market price option was added to allow fetching the market price directly from the coingecko api instead of calculating it via supported exchanges known to the explorer
-Added a new root setting option for default_coingecko_ids which allows presetting symbols to their associated internal coingecko id to help prevent matching to the wrong currency with same symbol via coingecko api calls
-Fixed an issue where the explorer would fail to start with an enabled exchange that had no defined trading pairs
2024-01-05 00:47:22 -07:00

1826 lines
70 KiB
JavaScript

var mongoose = require('mongoose'),
lib = require('../lib/explorer'),
db = require('../lib/database'),
Tx = require('../models/tx'),
Address = require('../models/address'),
AddressTx = require('../models/addresstx'),
Orphans = require('../models/orphans'),
Richlist = require('../models/richlist'),
Stats = require('../models/stats'),
settings = require('../lib/settings'),
async = require('async');
var mode = 'update';
var database = 'index';
var block_start = 1;
var lockCreated = false;
var stopSync = false;
// prevent stopping of the sync script to be able to gracefully shut down
process.on('SIGINT', () => {
console.log('Stopping sync process.. Please wait..');
stopSync = true;
});
// prevent killing of the sync script to be able to gracefully shut down
process.on('SIGTERM', () => {
console.log('Stopping sync process.. Please wait..');
stopSync = true;
});
// displays usage and exits
function usage() {
console.log('Usage: /path/to/node scripts/sync.js [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(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);
}
}
// updates tx & address balances
function update_tx_db(coin, start, end, txes, timeout, check_only, cb) {
var complete = false;
var blocks_to_scan = [];
var task_limit_blocks = settings.sync.block_parallel_tasks;
var task_limit_txs = 1;
// fix for invalid block height (skip genesis block as it should not have valid txs)
if (typeof start === 'undefined' || start < 1)
start = 1;
if (task_limit_blocks < 1)
task_limit_blocks = 1;
for (i = start; i < (end + 1); i++)
blocks_to_scan.push(i);
async.eachLimit(blocks_to_scan, task_limit_blocks, function(block_height, next_block) {
if (check_only == 0 && block_height % settings.sync.save_stats_after_sync_blocks === 0) {
Stats.updateOne({coin: coin}, {
last: block_height - 1,
txes: txes
}).then(() => {});
} else if (check_only == 1) {
console.log('Checking block ' + block_height + '...');
}
lib.get_blockhash(block_height, function(blockhash) {
if (blockhash) {
lib.get_block(blockhash, function(block) {
if (block) {
async.eachLimit(block.tx, task_limit_txs, function(txid, next_tx) {
Tx.findOne({txid: txid}).then((tx) => {
if (tx && check_only != 2) {
setTimeout(function() {
tx = null;
// check if the script is stopping
if (stopSync && check_only != 2) {
// stop the loop
next_tx({});
} else
next_tx();
}, timeout);
} else {
// check if the transaction exists but doesn't match the current block height
check_delete_tx(tx, block_height, txes, timeout, function(updated_txes, tx_deleted) {
// update the running tx count
txes = updated_txes;
// check if this tx should be added to the local database
if (tx_deleted || !tx) {
// save the transaction to local database
db.save_tx(txid, block_height, function(err, tx_has_vout) {
if (err)
console.log(err);
else
console.log('%s: %s', block_height, txid);
if (tx_has_vout)
txes++;
setTimeout(function() {
tx = null;
// check if the script is stopping
if (stopSync && check_only != 2) {
// stop the loop
next_tx({});
} else
next_tx();
}, timeout);
});
} else {
// skip adding the current tx
setTimeout(function() {
tx = null;
// check if the script is stopping
if (stopSync && check_only != 2) {
// stop the loop
next_tx({});
} else
next_tx();
}, timeout);
}
});
}
}).catch((err) => {
console.log(err);
setTimeout(function() {
tx = null;
// check if the script is stopping
if (stopSync && check_only != 2) {
// stop the loop
next_tx({});
} else
next_tx();
}, timeout);
});
}, function() {
setTimeout(function() {
blockhash = null;
block = null;
// check if the script is stopping
if (stopSync && check_only != 2) {
// stop the loop
next_block({});
} else
next_block();
}, timeout);
});
} else {
console.log('Block not found: %s', blockhash);
setTimeout(function() {
// check if the script is stopping
if (stopSync && check_only != 2) {
// stop the loop
next_block({});
} else
next_block();
}, timeout);
}
});
} else {
setTimeout(function() {
// check if the script is stopping
if (stopSync && check_only != 2) {
// stop the loop
next_block({});
} else
next_block();
}, timeout);
}
});
}, function() {
var statUpdateObject = {};
// check what stats data should be updated
if (stopSync || check_only == 2) {
// only update txes when fixing invalid and missing blocks or when a "normal" sync was stopped prematurely
statUpdateObject.txes = txes;
} else {
// update last and txes values for "normal" sync that finishes without being stopped prematurely
statUpdateObject = {
txes: txes,
last: end
};
}
// update local stats
Stats.updateOne({coin: coin}, statUpdateObject).then(() => {
return cb(txes);
}).catch((err) => {
console.log(err);
return cb(txes);
});
});
}
// fixes data belonging to orphaned blocks
function update_orphans(orphan_index, orphan_current, last_blockindex, timeout, cb) {
// lookup the earliest orphaned block if this is the first time that orphans are being checked
get_earliest_orphan_block(orphan_index, orphan_current, last_blockindex, function(orphan_data, err) {
if (err != null) {
console.log(err);
return cb();
} else {
// re-populate the orphan data in case it has changed
orphan_index = orphan_data.orphan_index;
orphan_current = orphan_data.orphan_current;
// Check if the sync msg should be shown
check_show_sync_message(last_blockindex - orphan_index);
// start from the current orphan (if exists) or else use the last orphan index
let current_block = (orphan_current == 0 ? orphan_index : orphan_current);
let unresolved_forks = [];
let correct_block_data = null;
// loop infinitely until finished iterating through all known blocks
async.forever(function(next) {
// check if the script is stopping or if the last block has been reached
if (stopSync || current_block >= last_blockindex) {
// stop the main loop
next('stop');
} else {
if (unresolved_forks.length == 0 && current_block % settings.sync.save_stats_after_sync_blocks === 0) {
Stats.updateOne({coin: settings.coin.name}, {
orphan_index: current_block - 1,
orphan_current: (unresolved_forks.length == 0 ? 0 : unresolved_forks[0])
}).then(() => {}).catch((staterr) => {
console.log(staterr);
});
}
// do not show the 'checking...' msg if this block is about to be resolved
if (correct_block_data == null)
console.log(current_block.toString() + ': Checking for forks...');
// check if there is a fork in this block
check_block_height_for_fork(current_block, function(blockhashes, fork_err) {
if (fork_err != null) {
// an error occurred
// stop looking for orphans
next(fork_err);
} else if (blockhashes == null) {
// no forks found in this block
// check if there are previously unresolved forks
if (unresolved_forks.length == 0) {
// move to the next block
current_block++;
setTimeout(function() {
// process next block
next(null);
}, timeout);
} else {
// one or more forks still need to be resolved
// set the current block to the block after the next orphaned block
current_block = unresolved_forks[unresolved_forks.length - 1] + 1;
// lookup the block hash using the block height
lib.get_blockhash(current_block, function(block_hash) {
// check if the block hash was found
if (block_hash) {
// lookup the current block data from the wallet
lib.get_block(block_hash, function (block_data) {
// remember the correct block hashes
correct_block_data = {
prev_hash: block_data.previousblockhash,
next_hash: block_data.hash
};
console.log('Good ' + current_block.toString() + ' block found. Returning to fix block ' + unresolved_forks[unresolved_forks.length -1].toString());
// go back to the last unresolved fork
current_block = unresolved_forks.pop();
setTimeout(function() {
// process next block
next(null);
}, timeout);
});
} else {
// an error occurred
// stop looking for orphans
next('cannot find block ' + current_block.toString());
}
});
}
} else {
// there is at least one fork in this block height
// check if the correct block hash is known
if (correct_block_data != null) {
// this fork needs to be resolved
// lookup the current block data from the wallet
lib.get_block(correct_block_data.prev_hash, function (block_data) {
var tx_count = 0;
// check if the good block hash is in the list of blockhashes
if (blockhashes.indexOf(correct_block_data.prev_hash) > -1) {
// remove the good block hash
blockhashes.splice(blockhashes.indexOf(correct_block_data.prev_hash), 1);
}
// loop through the remaining orphaned block hashes
lib.syncLoop(blockhashes.length, function(block_loop) {
var i = block_loop.iteration();
console.log('Resolving orphaned block [' + (i + 1).toString() + '/' + blockhashes.length.toString() + ']: ' + blockhashes[i]);
// find all orphaned txid's from the current orphan block hash
get_orphaned_txids(blockhashes[i], function(txids) {
// save the orphan block data to the orphan collection
create_orphan(current_block, blockhashes[i], correct_block_data.prev_hash, block_data.previousblockhash, correct_block_data.next_hash, function() {
// loop through the remaining orphaned block hashes
lib.syncLoop(txids.length, function(tx_loop) {
var t = tx_loop.iteration();
// remove the orphaned tx and cleanup all associated data
delete_and_cleanup_tx(txids[t], current_block, tx_count, timeout, function(updated_tx_count) {
// update the running tx count
tx_count = updated_tx_count;
// some blockchains will reuse the same orphaned transaction ids
// lookup the transaction that was just deleted to ensure it doesn't belong to another block
check_add_tx(txids[t], blockhashes[i], tx_count, function(updated_tx_count2) {
// update the running tx count
tx_count = updated_tx_count2;
setTimeout(function() {
// move to the next tx record
tx_loop.next();
}, timeout);
});
});
}, function() {
setTimeout(function() {
// move to the next block record
block_loop.next();
}, timeout);
});
});
});
}, function() {
// get the most recent stats
Stats.findOne({coin: settings.coin.name}).then((stats) => {
// add missing txes for the current block
update_tx_db(settings.coin.name, current_block, current_block, (stats.txes + tx_count), timeout, 2, function(updated_tx_count) {
// update the stats collection by removing the orphaned txes in this block from the tx count
// and setting the orphan_index and orphan_current values in case the sync is interrupted before finishing
Stats.updateOne({coin: settings.coin.name}, {
orphan_index: current_block,
orphan_current: (unresolved_forks.length == 0 ? 0 : unresolved_forks[0])
}).then(() => {
// clear the saved block hash data
correct_block_data = null;
// move to the next block
current_block++;
setTimeout(function() {
// process next block
next(null);
}, timeout);
}).catch((err) => {
console.log(err);
// clear the saved block hash data
correct_block_data = null;
// move to the next block
current_block++;
setTimeout(function() {
// process next block
next(null);
}, timeout);
});
});
}).catch((err) => {
console.log(err);
setTimeout(function() {
// process next block
next(null);
}, timeout);
});
});
});
} else {
// correct block hash is unknown
console.log('Block ' + current_block.toString() + ' has ' + (blockhashes.length - 1).toString() + ' unresolved fork' + (blockhashes.length - 1 == 1 ? '' : 's'));
// add this block height to the list of unresolved forks
unresolved_forks.push(current_block);
// move to the next block
current_block++;
setTimeout(function() {
// process next block
next(null);
}, timeout);
}
}
});
}
},
function(err) {
// check if there is a msg to display
if (err != '' && err != 'stop') {
// display the msg
console.log(err);
// stop fixing orphaned block data
return cb();
} else {
// check if the script is stopping
if (!stopSync)
console.log('Finished looking for forks');
// get the list of orphans with a null next_blockhash
Orphans.find({next_blockhash: null}).exec().then((orphans) => {
// loop through the list of orphans
lib.syncLoop(orphans.length, function(orphan_loop) {
var o = orphan_loop.iteration();
// lookup the block data from the wallet
lib.get_block(orphans[o].good_blockhash, function (good_block_data) {
// check if the next block hash is known
if (good_block_data.nextblockhash != null) {
// update the next blockhash for this orphan record
Orphans.updateOne({blockindex: orphans[o].blockindex, orphan_blockhash: orphans[o].orphan_blockhash}, {
next_blockhash: good_block_data.nextblockhash
}).then(() => {
setTimeout(function() {
// move to the next orphan record
orphan_loop.next();
}, timeout);
}).catch((err) => {
console.log(err);
setTimeout(function() {
// move to the next orphan record
orphan_loop.next();
}, timeout);
});
} else {
setTimeout(function() {
// move to the next orphan record
orphan_loop.next();
}, timeout);
}
});
}, function() {
// update the orphan stats
Stats.updateOne({coin: settings.coin.name}, {
orphan_index: current_block - 1,
orphan_current: (unresolved_forks.length == 0 ? 0 : unresolved_forks[0])
}).then(() => {
// finished fixing orphaned block data
return cb();
}).catch((err) => {
console.log(err);
return cb();
});
});
}).catch((err) => {
console.log(err);
return cb();
});
}
});
}
});
}
function get_earliest_orphan_block(orphan_index, orphan_current, last_blockindex, cb) {
// check if it is necessary to search for orphan data
if (orphan_index == null || orphan_index == 0) {
console.log('Finding the earliest orphaned blockindex.. Please wait..');
Tx.aggregate([
{ $match: {
"vout": { $exists: true, $ne: [] }
}},
{ $group: {
_id: "$blockindex",
blockhashes: { $addToSet: "$blockhash" }
}},
{ $match: {
"blockhashes.1": { "$exists": true }
}},
{ $sort: {
"_id": 1
}},
{ $limit: 1 },
{ $project: {
"_id": 1
}}
]).option({ allowDiskUse: true }).then((data) => {
if (data.length > 0) {
// found the first unprocessed orphaned block
orphan_current = data[0]._id;
orphan_index = (orphan_current - 1);
console.log('Found orphan block index: ' + orphan_current.toString());
Stats.updateOne({coin: settings.coin.name}, {
orphan_current: orphan_current,
orphan_index: orphan_index
}).then(() => {
return cb({orphan_index: orphan_index, orphan_current: orphan_current}, null);
});
} else {
// no unprocessed orphaned blocks found
orphan_current = 0;
orphan_index = last_blockindex;
console.log('No orphaned blocks found');
Stats.updateOne({coin: settings.coin.name}, {
orphan_current: orphan_current,
orphan_index: orphan_index
}).then(() => {
return cb({orphan_index: orphan_index, orphan_current: orphan_current}, null);
});
}
}).catch((err) => {
return cb(null, err);
});
} else
return cb({orphan_index: orphan_index, orphan_current: orphan_current}, null);
}
function check_block_height_for_fork(block_height, cb) {
// find all unique blockhashes in the txes collections for this block height
Tx.aggregate([
{ $match: {
$and: [
{"blockindex": { $eq: block_height }},
{"vout": { $exists: true, $ne: [] }}
]
}},
{ $group: {
_id: "$blockindex",
blockhashes: { $addToSet: "$blockhash" }
}}
]).then((data) => {
if (data.length > 0) {
// lookup the "good" block hash using the block height
lib.get_blockhash(block_height, function(block_hash) {
// check if there is more than 1 block hash
if (data[0].blockhashes.length == 1) {
// 1 block found
// check if the found block is the good block
if (data[0].blockhashes[0] == block_hash) {
// no forks found
return cb(null, null);
} else {
// add the good block to the list of blockhashes
data[0].blockhashes.push(block_hash);
// return the block hashes
return cb(data[0].blockhashes, null);
}
} else {
// more than 1 block found
// check if the good block is already in the list
if (data[0].blockhashes.indexOf(block_hash) == -1) {
// add the good block to the list of blockhashes
data[0].blockhashes.push(block_hash);
}
// return the block hashes
return cb(data[0].blockhashes, null);
}
});
} else {
// no blocks found
return cb(null, null);
}
}).catch((err) => {
// an error was returned
return cb(null, err);
});
}
function create_orphan(blockindex, orphan_blockhash, good_blockhash, prev_blockhash, next_blockhash, cb) {
var newOrphan = new Orphans({
blockindex: blockindex,
orphan_blockhash: orphan_blockhash,
good_blockhash: good_blockhash,
prev_blockhash: prev_blockhash,
next_blockhash: next_blockhash
});
// create a new orphan record in the local database
newOrphan.save().then(() => {
// new orphan record saved successfully
return cb();
}).catch((err) => {
// check if this is a duplicate key error which can be ignored
if (!(err.toString().indexOf('E11000') > -1 || err.toString().indexOf('duplicate key error') > -1))
console.log(err);
return cb();
});
}
function get_orphaned_txids(block_hash, cb) {
// get all transactions by block hash
Tx.find({blockhash: block_hash}).exec().then((txes) => {
if (txes.length > 0) {
// found at least one orphaned transaction
var txids = [];
// populate an array of txids without the object data
for (t = 0; t < txes.length; t++)
txids.push(txes[t].txid);
return cb(txids, null);
} else {
// no txes found
return cb(null, null);
}
}).catch((err) => {
// an error was returned
return cb(null, err);
});
}
function check_add_tx(txid, blockhash, tx_count, cb) {
// lookup the transaction to ensure it doesn't belong to another block
lib.get_rawtransaction(txid, function(tx) {
// check if this txid belongs to the main blockchain
if (tx && tx.txid && tx.blockhash != blockhash && tx.confirmations > 0) {
// lookup the correct block index in case it is not the same as the current block
lib.get_block(tx.blockhash, function(block) {
// check if the block was found
if (block) {
// save the tx to the local database
db.save_tx(txid, block.height, function(save_tx_err, tx_has_vout) {
// check if there were any save errors
if (save_tx_err)
console.log(save_tx_err);
else
console.log('%s: %s', block.height, txid);
// check if the tx was saved correctly
if (tx_has_vout) {
// keep a running total of txes that were added
tx_count++;
}
return cb(tx_count);
});
} else {
// block not found so there is nothing to fix
return cb(tx_count);
}
});
} else {
// block does not belong to main blockchain so there is nothing to fix
return cb(tx_count);
}
});
}
function delete_and_cleanup_tx(txid, block_height, tx_count, timeout, cb) {
// lookup all address tx records associated with the current tx
AddressTx.find({txid: txid}).exec().then((address_txes) => {
if (address_txes.length == 0) {
// no vouts for this tx, so just delete the tx without cleaning up addresses
delete_tx(txid, block_height, function(tx_err, tx_result) {
if (tx_err) {
console.log(tx_err);
return cb(tx_count);
} else {
// NOTE: do not subtract from the tx_count here because only txes with vouts are counted
return cb(tx_count);
}
});
} else {
// lookup the current tx in the local database
Tx.findOne({txid: txid}).then((tx) => {
var addressTxArray = [];
var has_vouts = (tx.vout != null && tx.vout.length > 0);
// check if this is a coinbase tx
if (tx.vin == null || tx.vin.length == 0) {
// add a coinbase tx into the addressTxArray array
addressTxArray.push({
txid: txid,
a_id: 'coinbase',
amount: tx.total
});
}
// check if there are any vin addresses
if (tx.vin != null && tx.vin.length > 0) {
// loop through the vin data
for (var vin_tx_counter = tx.vin.length - 1; vin_tx_counter >= 0; vin_tx_counter--) {
// loop through the addresstxe data
for (var vin_addresstx_counter = address_txes.length - 1; vin_addresstx_counter >= 0; vin_addresstx_counter--) {
// check if there is a tx record that exactly matches to the addresstx
if (tx.vin[vin_tx_counter].addresses == address_txes[vin_addresstx_counter].a_id && tx.vin[vin_tx_counter].amount == -address_txes[vin_addresstx_counter].amount) {
// add the address into the addressTxArray array
addressTxArray.push({
txid: txid,
a_id: tx.vin[vin_tx_counter].addresses,
amount: address_txes[vin_addresstx_counter].amount
});
// remove the found records from both arrays
tx.vin.splice(vin_tx_counter, 1);
address_txes.splice(vin_addresstx_counter, 1);
break;
}
}
}
}
// check if there are any vout addresses
if (tx.vout != null && tx.vout.length > 0) {
// loop through the vout data
for (var vout_tx_counter = tx.vout.length - 1; vout_tx_counter >= 0; vout_tx_counter--) {
// loop through the addresstxe data
for (var vout_addresstx_counter = address_txes.length - 1; vout_addresstx_counter >= 0; vout_addresstx_counter--) {
// check if there is a tx record that exactly matches to the addresstx
if (tx.vout[vout_tx_counter].addresses == address_txes[vout_addresstx_counter].a_id && tx.vout[vout_tx_counter].amount == address_txes[vout_addresstx_counter].amount) {
// add the address into the addressTxArray array
addressTxArray.push({
txid: txid,
a_id: tx.vout[vout_tx_counter].addresses,
amount: address_txes[vout_addresstx_counter].amount
});
// remove the found records from both arrays
tx.vout.splice(vout_tx_counter, 1);
address_txes.splice(vout_addresstx_counter, 1);
break;
}
}
}
}
// check if there are still more vin/vout records to process
if (tx.vin.length > 0 || tx.vout.length > 0 || address_txes.length > 0) {
// get all unique remaining addresses
var address_list = [];
// get unique addresses from the tx vin
tx.vin.forEach(function(vin) {
if (address_list.indexOf(vin.addresses) == -1)
address_list.push(vin.addresses);
});
// get unique addresses from the tx vout
tx.vout.forEach(function(vout) {
if (address_list.indexOf(vout.addresses) == -1)
address_list.push(vout.addresses);
});
// get unique addresses from the addresstxes
address_txes.forEach(function(address_tx) {
if (address_list.indexOf(address_tx.a_id) == -1)
address_list.push(address_tx.a_id);
});
// loop through each unique address
address_list.forEach(function(address) {
var vin_total = 0;
var vout_total = 0;
var address_tx_total = 0;
// add up all the vin amounts for this address
tx.vin.forEach(function(vin) {
// check if this is the correct address
if (vin.addresses == address)
vin_total += vin.amount;
});
// add up all the vout amounts for this address
tx.vout.forEach(function(vout) {
// check if this is the correct address
if (vout.addresses == address)
vout_total += vout.amount;
});
// add up all the addresstx amounts for this address
address_txes.forEach(function(address_tx) {
// check if this is the correct address
if (address_tx.a_id == address)
address_tx_total += address_tx.amount;
});
// check if the tx and addresstx totals match
if ((vout_total - vin_total) == address_tx_total) {
// the values match (this indicates that this address sent coins to themselves)
// add a vin record for this address into the addressTxArray array
addressTxArray.push({
txid: txid,
a_id: address,
amount: -vin_total
});
// add a vout record for this address into the addressTxArray array
addressTxArray.push({
txid: txid,
a_id: address,
amount: vout_total
});
} else {
// the values do not match (this indicates there was a problem saving the data)
// output the data for this address as-is, using the addresstx values
address_txes.forEach(function(address_tx) {
// check if this is the correct address
if (address_tx.a_id == address) {
// add a record for this address into the addressTxArray array
addressTxArray.push({
txid: txid,
a_id: address,
amount: address_tx.amount
});
}
});
}
});
}
// loop through the address txes
lib.syncLoop(addressTxArray.length, function(address_loop) {
var a = address_loop.iteration();
// fix the balance, sent and received data for the current address
fix_address_data(addressTxArray[a], function() {
setTimeout(function() {
// move to the next address record
address_loop.next();
}, timeout);
});
}, function() {
// delete all AddressTx records from the local collection for this tx
AddressTx.deleteMany({txid: txid}).then((address_tx_result) => {
// delete the tx from the local database
delete_tx(txid, block_height, function(tx_err, tx_result) {
if (tx_err) {
console.log(tx_err);
return cb(tx_count);
} else {
// check if the deleted tx had vouts
if (has_vouts) {
// keep a running total of txes that were removed
tx_count -= tx_result.deletedCount;
}
return cb(tx_count);
}
});
}).catch((err) => {
console.log(err);
// delete the tx from the local database
delete_tx(txid, block_height, function(tx_err, tx_result) {
if (tx_err) {
console.log(tx_err);
return cb(tx_count);
} else {
// check if the deleted tx had vouts
if (has_vouts) {
// keep a running total of txes that were removed
tx_count -= tx_result.deletedCount;
}
return cb(tx_count);
}
});
});
});
}).catch((err) => {
console.log(err);
return cb(tx_count);
});
}
}).catch((err) => {
console.log(err);
return cb(tx_count);
});
}
function fix_address_data(address_data, cb) {
var addr_inc = {};
var amount = address_data.amount;
// determine how to fix the address balances
if (address_data.a_id == 'coinbase')
addr_inc.sent = -amount;
else if (amount < 0) {
// vin
addr_inc.sent = amount;
addr_inc.balance = -amount;
} else {
// vout
addr_inc.received = -amount;
addr_inc.balance = -amount;
}
// reverse the amount from the running totals in the Address collection for the current address
Address.findOneAndUpdate({a_id: address_data.a_id}, {
$inc: addr_inc
}, {
upsert: false
}).then((return_address) => {
// finished fixing the address balance data
return cb();
}).catch((err) => {
console.log(err);
return cb();
});
}
function delete_tx(txid, block_height, cb) {
// delete the tx from the local database
Tx.deleteOne({txid: txid, blockindex: block_height}).then((tx_result) => {
return cb(null, tx_result);
}).catch((err) => {
return cb(err, null);
});
}
function check_delete_tx(tx, block_height, tx_count, timeout, cb) {
// check if the tx object exists and does not match the current block height
if (tx && tx.blockindex != block_height) {
// the transaction exists but does not match the correct block height, therefore it should be deleted
delete_and_cleanup_tx(tx.txid, tx.blockindex, tx_count, timeout, function(updated_tx_count) {
// finished removing the transaction
return cb(updated_tx_count, true);
});
} else {
// tx dosn't exist or block heights match so nothing to do
return cb(tx_count, false);
}
}
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_market_price(market_array) {
// check how the market price should be updated
if (settings.markets_page.market_price == 'COINGECKO') {
// find the coingecko id
find_coingecko_id(settings.coin.symbol, function(coingecko_id) {
// check if the coingecko_id was found
if (coingecko_id != null && coingecko_id != '') {
const coingecko = require('../lib/apis/coingecko');
const currency = lib.get_market_currency_code();
console.log('Calculating market price.. Please wait..');
// get the market price from coingecko api
coingecko.get_market_prices(coingecko_id, currency, function (err, last_price, last_usd_price) {
// check for errors
if (err == null) {
// get current stats
Stats.findOne({coin: settings.coin.name}).then((stats) => {
// update market stat prices
Stats.updateOne({coin: settings.coin.name}, {
last_price: (last_price == null ? 0 : last_price),
last_usd_price: (last_usd_price == null ? 0 : last_usd_price)
}).then(() => {
// market prices updated successfully
finish_market_sync();
}).catch((err) => {
// error saving stats
console.log(err);
exit(1);
});
}).catch((err) => {
// error getting stats
console.log(err);
exit(1);
});
} else {
// coingecko api returned an error
console.log(err);
exit(1);
}
});
} else {
// coingecko_id is not set which should have already thrown an error, so just exit
exit(1);
}
});
} else {
console.log('Calculating market price.. Please wait..');
// get the list of coins from coingecko
coingecko_coin_list_api(market_array, function (coin_err, coin_list) {
// check for errors
if (coin_err == null) {
let api_ids = '';
// loop through all unique currencies in the market_array
for (let m = 0; m < market_array.length; m++) {
const index = coin_list.findIndex(p => p.symbol.toLowerCase() == market_array[m].currency.toLowerCase());
// check if the market currency is found in the coin list
if (index > -1) {
// add to the list of api_ids
api_ids += (api_ids == '' ? '' : ',') + coin_list[index].id;
// add the coingecko id back to the market_array
market_array[m].coingecko_id = coin_list[index].id;
} else {
// coin symbol not found in the api
console.log('Error: Cannot find symbol "' + market_array[m].currency + '" in the coingecko api');
}
}
// check if any api_ids were found
if (api_ids != '') {
const coingecko = require('../lib/apis/coingecko');
const currency = lib.get_market_currency_code();
// get the market price from coingecko api
coingecko.get_avg_market_prices(api_ids, currency, market_array, function (mkt_err, last_price, last_usd) {
// check for errors
if (mkt_err == null) {
// update the last usd price
Stats.updateOne({coin: settings.coin.name}, {
last_price: last_price,
last_usd_price: last_usd
}).then(() => {
// market price updated successfully
finish_market_sync();
}).catch((err) => {
// error saving stat data
console.log(err);
exit(1);
});
} else {
// coingecko api returned an error
console.log(mkt_err);
exit(1);
}
});
} else {
// no api_ids found so cannot continue to getting the usd price and error msgs were already thrown, so just exit
exit(1);
}
} else {
// coingecko api returned an error
console.log(coin_err);
exit(1);
}
});
}
}
function finish_market_sync() {
// update markets_last_updated value
db.update_last_updated_stats(settings.coin.name, { markets_last_updated: Math.floor(new Date() / 1000) }, function() {
// check if the script stopped prematurely
if (stopSync) {
console.log('Market sync was stopped prematurely');
exit(1);
} else {
console.log('Market sync complete');
exit(0);
}
});
}
function coingecko_coin_list_api(market_symbols, cb) {
let coin_array = [];
let call_coin_list_api = false;
// check if market_symbols is an array
if (!Array.isArray(market_symbols)) {
// add this symbol to an array
market_symbols = [{currency: market_symbols}];
}
// loop through all symbols
for (var symbol of market_symbols) {
// check if this symbol has a default coingecko id in the settings
const index = settings.default_coingecko_ids.findIndex(p => p.symbol.toLowerCase() == symbol.currency.toLowerCase());
// check if the coin symbol is found in settings
if (index > -1) {
// add this symbol and id to a new array
coin_array.push({
id: settings.default_coingecko_ids[index].id.toLowerCase(),
symbol: symbol.currency.toLowerCase()
});
} else {
// missing at least 1 symbol, so the coingecko api must be called
call_coin_list_api = true;
break;
}
}
// check if the coin list api needs to be called
if (call_coin_list_api) {
const coingecko = require('../lib/apis/coingecko');
// get the list of coins from coingecko
coingecko.get_coin_data(function (err, coin_list) {
// check if there was an error
if (err == null) {
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../lib/ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
// automatically pause for 2 seconds in between requests
rateLimit.schedule(function() {
return cb(err, coin_list);
});
} else {
return cb(err, coin_list);
}
});
} else {
// return the custom array of known symbols and ids
return cb(null, coin_array);
}
}
function find_coingecko_id(symbol, cb) {
coingecko_coin_list_api(symbol, function (err, coin_list) {
// check for errors
if (err == null) {
// find the index of the first coin symbol match
const index = coin_list.findIndex(p => p.symbol.toLowerCase() == symbol.toLowerCase());
// check if the coin symbol is found in the api coin list
if (index > -1)
return cb(coin_list[index].id);
else {
// coin symbol not found in the api
console.log('Error: Cannot find symbol "' + symbol + '" in the coingecko api');
return cb('');
}
} else {
// failed to get the coingecko api list
console.log(err);
return cb('');
}
});
}
/** 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;
}
function block_sync(reindex, stats) {
// 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);
update_tx_db(settings.coin.name, last, count, stats.txes, settings.sync.update_timeout, 0, function() {
// check if the script stopped prematurely
if (stopSync) {
console.log(`${(reindex ? 'Reindex' : 'Block sync')} was stopped prematurely`);
exit(1);
} else {
// update blockchain_last_updated value
db.update_last_updated_stats(settings.coin.name, { blockchain_last_updated: Math.floor(new Date() / 1000) }, function(cb) {
// fix data from orphaned blocks
update_orphans(stats.orphan_index, stats.orphan_current, count, settings.sync.update_timeout, function() {
// check if the script stopped prematurely
if (stopSync) {
console.log(`${(reindex ? 'Reindex' : 'Block sync')} was stopped prematurely`);
exit(1);
} else {
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, 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(`${(reindex ? 'Reindex' : 'Block sync')} complete (block: %s)`, nstats.last);
exit(0);
});
});
});
});
});
});
}
});
});
}
});
}
// check options
if (process.argv[2] == null || process.argv[2] == 'index' || process.argv[2] == 'update') {
mode = null;
switch (process.argv[3]) {
case undefined:
case null:
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] == 'peers' || process.argv[2] == 'masternodes')
database = process.argv[2];
else if (process.argv[2] == 'market')
database = `${process.argv[2]}s`;
else
usage();
// 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);
if (mode == 'update')
console.log(`Syncing ${(database == 'index' ? 'blocks' : database)}.. Please wait..`);
var dbString = 'mongodb://' + encodeURIComponent(settings.dbsettings.user);
dbString = dbString + ':' + encodeURIComponent(settings.dbsettings.password);
dbString = dbString + '@' + settings.dbsettings.address;
dbString = dbString + ':' + settings.dbsettings.port;
dbString = dbString + '/' + settings.dbsettings.database;
mongoose.set('strictQuery', true);
mongoose.connect(dbString).then(() => {
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 {
// determine which index mode to run
if (mode == 'reindex') {
const { execSync } = require('child_process');
try {
// delete the database
execSync(`node ./scripts/delete_database.js ${mode}`, {stdio : 'inherit'});
} catch (err) {
// delete_database.js was not successful, so exit
exit(1);
}
db.update_db(settings.coin.name, function(stats) {
// check if stats returned properly
if (stats !== false) {
// start the block sync
block_sync(true, stats);
} else {
// update_db threw an error so exit
exit(1);
}
});
} else if (mode == 'check') {
db.update_db(settings.coin.name, function(stats) {
// check if stats returned properly
if (stats !== false) {
console.log('Checking blocks.. Please wait..');
update_tx_db(settings.coin.name, block_start, stats.count, stats.txes, settings.sync.check_timeout, 1, function() {
// check if the script stopped prematurely
if (stopSync) {
console.log('Block check was stopped prematurely');
exit(1);
} else {
db.get_stats(settings.coin.name, function(nstats) {
console.log('Block check complete (block: %s)', nstats.last);
exit(0);
});
}
});
} else {
// update_db threw an error so exit
exit(1);
}
});
} else if (mode == 'update') {
db.update_db(settings.coin.name, function(stats) {
// check if stats returned properly
if (stats !== false) {
// start the block sync
block_sync(false, stats);
} else {
// update_db threw an error so exit
exit(1);
}
});
} else if (mode == 'reindex-rich') {
db.update_db(settings.coin.name, function(stats) {
// check if stats returned properly
if (stats !== false) {
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, false, 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(0);
});
});
});
});
});
});
} else {
// update_db threw an error so exit
exit(1);
}
});
} else if (mode == 'reindex-txcount') {
db.update_db(settings.coin.name, function(stats) {
// check if stats returned properly
if (stats !== false) {
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().then((count) => {
console.log('Found tx count: ' + count.toString());
Stats.updateOne({coin: settings.coin.name}, {
txes: count
}).then(() => {
console.log('Tx count update complete');
exit(0);
}).catch((err) => {
console.log(err);
exit(1);
});
}).catch((err) => {
console.log(err);
exit(1);
});
} else {
// update_db threw an error so exit
exit(1);
}
});
} else if (mode == 'reindex-last') {
db.update_db(settings.coin.name, function(stats) {
// check if stats returned properly
if (stats !== false) {
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().then((tx) => {
// check if any blocks exists
if (tx == null || tx.length == 0) {
console.log('No blocks found. setting last blockindex to 0.');
Stats.updateOne({coin: settings.coin.name}, {
last: 0
}).then(() => {
console.log('Last blockindex update complete');
exit(0);
}).catch((err) => {
console.log(err);
exit(1);
});
} else {
console.log('Found last blockindex: ' + tx[0].blockindex.toString());
Stats.updateOne({coin: settings.coin.name}, {
last: tx[0].blockindex
}).then(() => {
console.log('Last blockindex update complete');
exit(0);
}).catch((err) => {
console.log(err);
exit(1);
});
}
}).catch((err) => {
console.log(err);
exit(1);
});
} 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, port, function(peer) {
if (peer) {
if (peer['port'] != null && (isNaN(peer['port']) || peer['port'].length < 2)) {
db.drop_peers(function() {
console.log('Removing peers due to missing port information. Re-run this script to add peers again.');
exit(1);
});
}
// peer already exists and should be refreshed
// drop peer
db.drop_peer(address, port, function() {
// re-add the peer to refresh the data and extend the expiry date
db.create_peer({
address: address,
port: port,
protocol: peer.protocol,
version: peer.version,
country: peer.country,
country_code: peer.country_code
}, function() {
console.log('Updated peer %s%s [%s/%s]', address, (port == null || port == '' ? '' : ':' + port.toString()), (i + 1).toString(), body.length.toString());
// check if the script is stopping
if (stopSync) {
// stop the loop
loop.break(true);
}
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 if (geo == null || typeof geo != 'object') {
console.log('Error: geolocation api did not return a valid object');
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('Added new peer %s%s [%s/%s]', address, (port == null || port == '' ? '' : ':' + port.toString()), (i + 1).toString(), body.length.toString());
// check if the script is stopping
if (stopSync) {
// stop the loop
loop.break(true);
}
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) {
// check if the script stopped prematurely
if (stopSync) {
console.log('Peer sync was stopped prematurely');
exit(1);
} else {
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;
// 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) {
// check if the script is stopping
if (stopSync) {
// stop the loop
loop.break(true);
}
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) {
// check if the script stopped prematurely
if (stopSync) {
console.log('Masternode sync was stopped prematurely');
exit(1);
} else {
console.log('Masternode sync complete');
exit(0);
}
});
});
});
} else {
console.log('No masternodes found');
exit(2);
}
});
} else {
// start market sync
// check if market feature is enabled or the market_price option is set to COINGECKO
if (settings.markets_page.enabled == true || settings.markets_page.market_price == 'COINGECKO') {
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.enabled == true && 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) {
let market_array = [];
// 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);
var complete = 0;
// 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, last_price) {
if (!err) {
console.log('%s[%s]: Market data updated successfully', key, pair_key);
// only add to the market_array if market data is being averaged
if (settings.markets_page.market_price == 'AVERAGE') {
// check if the currency already exists in the market array
const index = market_array.findIndex(item => item.currency.toUpperCase() == split_pair[1].toUpperCase());
if (index != -1) {
// update the last_price
market_array[index].last_price = (market_array[index].last_price + last_price) / 2;
} else {
// add new object to the array
market_array.push({currency: split_pair[1], last_price: last_price});
}
}
} else
console.log('%s[%s] Error: %s', key, pair_key, err);
complete++;
if (complete == total_pairs || stopSync)
get_market_price(market_array);
});
});
} else {
console.log('%s[%s] Error: Market not found in local database. Please restart the explorer', key, pair_key);
complete++;
if (complete == total_pairs || stopSync)
get_market_price(market_array);
}
});
} else {
// market pair not formatted correctly
console.log('%s market pair is invalid', pair_key);
complete++;
if (complete == total_pairs || stopSync)
get_market_price(market_array);
}
});
} else {
// market not installed
console.log('%s market not installed', key);
complete++;
if (complete == total_pairs || stopSync)
get_market_price(market_array);
}
}
});
} else if (settings.markets_page.market_price == 'COINGECKO')
get_market_price([]);
else {
// no market trading pairs are enabled
console.log('Error: No market trading pairs are enabled in settings');
exit(1);
}
} else {
// market page is not enabled
console.log('Error: Market feature is disabled in settings');
exit(1);
}
}
}).catch((err) => {
console.log('Error: Unable to connect to database: %s', err);
exit(1);
});
} else {
// another script process is currently running
console.log("Sync aborted");
exit(2);
}
} else {
// sync process is already running
console.log("Sync aborted");
exit(2);
}