diff --git a/coinConfig.json b/coinConfig.json
index 7333407..e1595a7 100644
--- a/coinConfig.json
+++ b/coinConfig.json
@@ -1,11 +1,28 @@
{
"xmr": {
"funcFile": "./lib/coins/xmr.js",
+ "paymentFile": "./payment_systems/xmr.js",
"sigDigits": 1000000000000,
"shapeshift": "xmr_btc",
"xmrTo": true,
"name": "Monero",
"mixIn": 4,
"shortCode": "XMR"
+ },
+ "krb": {
+ "funcFile": "./lib/coins/krb.js",
+ "paymentFile": "./payment_systems/krb.js",
+ "sigDigits": 1000000000000,
+ "name": "Karbowanec",
+ "mixIn": 4,
+ "shortCode": "KRB"
+ },
+ "aeon": {
+ "funcFile": "./lib/coins/aeon.js",
+ "paymentFile": "./payment_systems/aeon.js",
+ "sigDigits": 1000000000000,
+ "name": "Aeon Coin",
+ "mixIn": 4,
+ "shortCode": "AEON"
}
}
diff --git a/lib/api.js b/lib/api.js
index bbfd86c..e4274bf 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -11,9 +11,6 @@ const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
const crypto = require('crypto');
const cors = require('cors');
-const zmq = require('zmq');
-const sock = zmq.socket('sub');
-const io = require('socket.io')(server);
let addressBase58Prefix = cnUtil.address_decode(new Buffer(global.config.pool.address));
let threadName = "";
@@ -39,127 +36,6 @@ app.use(cors({origin: true}));
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
-
-if (!cluster.isMaster) {
- // Init the ZMQ listeners here.
- sock.connect('tcp://127.0.0.1:3000');
- sock.connect('tcp://127.0.0.1:3001');
- // miner_hash_graph - Hash graphs updated
- // miner_hash_stats - Miner hashes updated
- // network_block_info - New block information
- // pool_stats - Pool statistics update
- sock.subscribe('');
-}
-
-require('sticky-cluster')(
- // server initialization function
- function (callback) {
- // don't do server.listen(), just pass the server instance into the callback
- callback(server);
- },
- // options
- {
- concurrency: require('os').cpus().length,
- port: 8001,
- debug: false,
- env: function (index) { return { stickycluster_worker_index: index }; }
- }
-);
-
-// SocketIO Routes
-// ===============
-io.on('connection', (socket) => {
- socket.on('room', function(room) {
- socket.join(room);
- });
-});
-
-// As the majority of updates come in upstream from the main SocketIO server, we use ZMQ to manage this.
-// The following is the ZMQ logic. Pray to whatever deity you like.
-
-sock.on('message', function(topic, message) {
- topic = topic.toString();
- message = message.toString();
- /*
- Registered ZMQ Messages:
- miner_hash_graph - Hash graphs updated
- miner_hash_stats - Miner hashes updated
- network_block_info - New block information
- pool_stats - Pool statistics update
- payments - Payments complete - Trip the frontend to hit the frontend for new payment information
-
- Registered Rooms:
- payments
- hash_chart_
- equiv to https://api.xmrpool.net/miner//chart/hashrate/allWorkers
- worker_stats_ - equiv to https://api.xmrpool.net/miner//stats/allWorkers
- worker_ids_ - equiv to https://api.xmrpool.net/miner//identifiers
- address_stats_ - equiv to https://api.xmrpool.net/miner//stats
- network_block_info - equiv to https://api.xmrpool.net/network/stats
- pool_stats_ - equiv to https://api.xmrpool.net/pool/stats/
- pool_stats - equiv to https://api.xmrpool.net/pool/stats/
- */
- switch(topic){
- case 'payments':
- io.sockets.in('payments').emit('message', 'newPaymentsAvailable');
- break;
- case 'miner_hash_graph':
- message = JSON.parse(message);
- message.forEach(function(address){
- getAllWorkerHashCharts(address, function(err, data){
- return io.sockets.in('hash_chart_'+address).emit('message', JSON.stringify(data));
- });
- });
- break;
- case 'miner_hash_stats':
- message = JSON.parse(message);
- message.forEach(function(address){
- getAddressStats(address, function(err,data){
- io.sockets.in('address_stats_'+address).emit('message', JSON.stringify(data));
- });
- getAllWorkerStats(address, function(err, data){
- io.sockets.in('worker_stats_'+address).emit('message', JSON.stringify(data));
- });
- io.sockets.in('worker_ids_'+address).emit('message', JSON.stringify(global.database.getCache(address + '_identifiers')));
- });
- break;
- case 'network_block_info':
- io.sockets.in('block_update').emit('message', message);
- break;
- case 'pool_stats':
- if (message === 'global'){
- let localCache = global.database.getCache('pool_stats_global');
- delete(localCache.minerHistory);
- delete(localCache.hashHistory);
- let lastPayment = global.database.getCache('lastPaymentCycle');
- io.sockets.in('pool_stats').emit('message', JSON.stringify({pool_list: pool_list, pool_statistics: localCache, last_payment: !lastPayment ? 0 : lastPayment}));
- } else {
- let pool_type = message;
- let localCache;
- switch (pool_type) {
- case 'pplns':
- localCache = global.database.getCache('pool_stats_pplns');
- localCache.fee = global.config.payout.pplnsFee;
- break;
- case 'pps':
- localCache = global.database.getCache('pool_stats_pps');
- localCache.fee = global.config.payout.ppsFee;
- break;
- case 'solo':
- localCache = global.database.getCache('pool_stats_solo');
- localCache.fee = global.config.payout.soloFee;
- break;
- case 'default':
- io.sockets.in('pool_stats_' + message).emit('message', JSON.stringify({'error': 'Invalid pool type'}));
- }
- localCache.poolType = pool_type;
- delete(localCache.minerHistory);
- delete(localCache.hashHistory);
- io.sockets.in('pool_stats_' + message).emit('message', JSON.stringify({pool_statistics: localCache}));
- }
- break;
- }
-});
-
// Support Functions that are reused now
function getAllWorkerHashCharts(address, callback){
let identifiers = global.database.getCache(address + '_identifiers');
@@ -944,4 +820,31 @@ adminRoutes.get('/userList', function (req, res) {
app.use('/authed', secureRoutes);
app.use('/admin', adminRoutes);
-// Authenticated routes
\ No newline at end of file
+// Authenticated routes
+
+let workerList = [];
+
+if (cluster.isMaster) {
+ let numWorkers = require('os').cpus().length;
+ console.log('Master cluster setting up ' + numWorkers + ' workers...');
+
+ for (let i = 0; i < numWorkers; i++) {
+ let worker = cluster.fork();
+ workerList.push(worker);
+ }
+
+ cluster.on('online', function (worker) {
+ console.log('Worker ' + worker.process.pid + ' is online');
+ });
+
+ cluster.on('exit', function (worker, code, signal) {
+ console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
+ console.log('Starting a new worker');
+ worker = cluster.fork();
+ workerList.push(worker);
+ });
+} else {
+ app.listen(8001, function () {
+ console.log('Process ' + process.pid + ' is listening to all incoming requests');
+ });
+}
diff --git a/lib/blockManager.js b/lib/blockManager.js
index 02a286a..89fccc3 100644
--- a/lib/blockManager.js
+++ b/lib/blockManager.js
@@ -16,6 +16,7 @@ let lastBlock = 0;
let balanceIDCache = {};
let blockScannerTask;
let blockQueue = async.queue(function (task, callback) {
+ // Todo: Implement within the coins/.js file.
global.support.rpcDaemon('getblockheaderbyheight', {"height": task.blockID}, function (body) {
let blockData = body.result.block_header;
if (blockData.hash in blockHexCache) {
@@ -397,9 +398,11 @@ function blockUnlocker() {
}
debug("Running block unlocker");
let blockList = global.database.getValidLockedBlocks();
+ // Todo: Implement within the coins/.js file.
global.support.rpcDaemon('getlastblockheader', [], function (body) {
let blockHeight = body.result.block_header.height;
blockList.forEach(function (row) {
+ // Todo: Implement within the coins/.js file.
global.support.rpcDaemon('getblockheaderbyheight', {"height": row.height}, function (body) {
if (body.result.block_header.hash !== row.hash) {
global.database.invalidateBlock(row.height);
@@ -421,22 +424,29 @@ function blockPayments(block) {
switch (block.poolType) {
case global.protos.POOLTYPE.PPS:
// PPS is paid out per share find per block, so this is handled in the main block-find loop.
+ global.database.unlockBlock(block.hash);
break;
case global.protos.POOLTYPE.PPLNS:
- global.coinFuncs.getBlockHeaderByHash(block.hash, function (header) {
- calculatePPLNSPayments(header);
+ global.coinFuncs.getBlockHeaderByHash(block.hash, function (err, header) {
+ if (err === null){
+ calculatePPLNSPayments(header);
+ global.database.unlockBlock(block.hash);
+ }
});
break;
case global.protos.POOLTYPE.SOLO:
- global.coinFuncs.getBlockHeaderByHash(block.hash, function (header) {
- calculateSoloPayments(header);
+ global.coinFuncs.getBlockHeaderByHash(block.hash, function (err, header) {
+ if (err === null){
+ calculateSoloPayments(header);
+ global.database.unlockBlock(block.hash);
+ }
});
break;
default:
console.log("Unknown payment type. FREAKOUT");
+ global.database.unlockBlock(block.hash);
break;
}
- global.database.unlockBlock(block.hash);
}
function blockScanner() {
@@ -446,27 +456,33 @@ function blockScanner() {
return;
}
scanInProgress = true;
- global.coinFuncs.getLastBlockHeader(function (blockHeader) {
- if (lastBlock === blockHeader.height) {
- debug("No new work to be performed, block header matches last block");
- scanInProgress = false;
- return;
- }
- debug("Parsing data for new blocks");
- lastBlock = blockHeader.height;
- range.range(0, (blockHeader.height - Math.floor(global.config.payout.blocksRequired/2))).forEach(function (blockID) {
- if (!blockIDCache.hasOwnProperty(blockID)) {
- inc_check += 1;
- blockQueue.push({blockID: blockID}, function (err) {
- debug("Completed block scan on " + blockID);
- if (err) {
- console.error("Error processing " + blockID);
- }
- });
+ global.coinFuncs.getLastBlockHeader(function (err, blockHeader) {
+ if (err === null){
+ if (lastBlock === blockHeader.height) {
+ debug("No new work to be performed, block header matches last block");
+ scanInProgress = false;
+ return;
}
- });
- if (inc_check === 0) {
- debug("No new work to be performed, initial scan complete");
+ debug("Parsing data for new blocks");
+ lastBlock = blockHeader.height;
+ range.range(0, (blockHeader.height - Math.floor(global.config.payout.blocksRequired/2))).forEach(function (blockID) {
+ if (!blockIDCache.hasOwnProperty(blockID)) {
+ inc_check += 1;
+ blockQueue.push({blockID: blockID}, function (err) {
+ debug("Completed block scan on " + blockID);
+ if (err) {
+ console.error("Error processing " + blockID);
+ }
+ });
+ }
+ });
+ if (inc_check === 0) {
+ debug("No new work to be performed, initial scan complete");
+ scanInProgress = false;
+ blockScannerTask = setInterval(blockScanner, 1000);
+ }
+ } else {
+ console.error(`Upstream error from the block daemon. Resetting scanner due to: ${JSON.stringify(blockHeader)}`);
scanInProgress = false;
blockScannerTask = setInterval(blockScanner, 1000);
}
diff --git a/lib/coins/aeon.js b/lib/coins/aeon.js
new file mode 100644
index 0000000..4d0f934
--- /dev/null
+++ b/lib/coins/aeon.js
@@ -0,0 +1,155 @@
+"use strict";
+const bignum = require('bignum');
+const cnUtil = require('cryptonote-util');
+const multiHashing = require('multi-hashing');
+const crypto = require('crypto');
+const debug = require('debug')('coinFuncs');
+
+let hexChars = new RegExp("[0-9a-f]+");
+
+function Coin(data){
+ this.bestExchange = global.config.payout.bestExchange;
+ this.data = data;
+ let instanceId = crypto.randomBytes(4);
+ this.coinDevAddress = "WmsSWgtT1JPg5e3cK41hKXSHVpKW7e47bjgiKmWZkYrhSS5LhRemNyqayaSBtAQ6517eo5PtH9wxHVmM78JDZSUu2W8PqRiNs"; // Developer Address
+ this.poolDevAddress = "WmtvM6SoYya4qzkoPB4wX7FACWcXyFPWAYzfz7CADECgKyBemAeb3dVb3QomHjRWwGS3VYzMJAnBXfUx5CfGLFZd1U7ssdXTu"; // Snipa Address
+
+ this.blockedAddresses = [
+ this.coinDevAddress,
+ this.poolDevAddress,
+ ];
+
+ this.exchangeAddresses = [
+ ]; // These are addresses that MUST have a paymentID to perform logins with.
+
+ this.prefix = 178;
+
+ this.supportsAutoExchange = false;
+
+ this.niceHashDiff = 200000;
+
+ this.getBlockHeaderByID = function(blockId, callback){
+ global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) {
+ if (body.hasOwnProperty('result')){
+ return callback(null, body.result.block_header);
+ } else {
+ console.error(JSON.stringify(body));
+ callback(true, body);
+ }
+ });
+ };
+
+ this.getBlockHeaderByHash = function(blockHash, callback){
+ global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) {
+ if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){
+ return callback(null, body.result.block_header);
+ } else {
+ console.error(JSON.stringify(body));
+ return callback(true, body);
+ }
+ });
+ };
+
+ this.getLastBlockHeader = function(callback){
+ global.support.rpcDaemon('getlastblockheader', [], function (body) {
+ if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){
+ return callback(null, body.result.block_header);
+ } else {
+ console.error(JSON.stringify(body));
+ return callback(true, body);
+ }
+ });
+ };
+
+ this.getBlockTemplate = function(walletAddress, callback){
+ global.support.rpcDaemon('getblocktemplate', {
+ reserve_size: 17,
+ wallet_address: walletAddress
+ }, function(body){
+ return callback(body);
+ });
+ };
+
+ this.baseDiff = function(){
+ return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16);
+ };
+
+ this.validateAddress = function(address){
+ // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address.
+ address = new Buffer(address);
+ if (cnUtil.address_decode(address) === this.prefix){
+ return true;
+ }
+ return cnUtil.address_decode_integrated(address) === this.intPrefix;
+ };
+
+ this.convertBlob = function(blobBuffer){
+ return cnUtil.convert_blob(blobBuffer);
+ };
+
+ this.constructNewBlob = function(blockTemplate, NonceBuffer){
+ return cnUtil.construct_block_blob(blockTemplate, NonceBuffer);
+ };
+
+ this.getBlockID = function(blockBuffer){
+ return cnUtil.get_block_id(blockBuffer);
+ };
+
+ this.BlockTemplate = function(template) {
+ /*
+ Generating a block template is a simple thing. Ask for a boatload of information, and go from there.
+ Important things to consider.
+ The reserved space is 13 bytes long now in the following format:
+ Assuming that the extraNonce starts at byte 130:
+ |130-133|134-137|138-141|142-145|
+ |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes|
+ This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce)
+ Each with 4 billion clients. (clientNonce)
+ While being unique to this particular pool thread (instanceId)
+ With up to 4 billion clients (minerNonce/extraNonce)
+ Overkill? Sure. But that's what we do here. Overkill.
+ */
+
+ // Set this.blob equal to the BT blob that we get from upstream.
+ this.blob = template.blocktemplate_blob;
+ this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex');
+ // Set this.diff equal to the known diff for this block.
+ this.difficulty = template.difficulty;
+ // Set this.height equal to the known height for this block.
+ this.height = template.height;
+ // Set this.reserveOffset to the byte location of the reserved offset.
+ this.reserveOffset = template.reserved_offset;
+ // Set this.buffer to the binary decoded version of the BT blob.
+ this.buffer = new Buffer(this.blob, 'hex');
+ // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes.
+ instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3);
+ // Generate a clean, shiny new buffer.
+ this.previous_hash = new Buffer(32);
+ // Copy in bytes 7 through 39 to this.previous_hash from the current BT.
+ this.buffer.copy(this.previous_hash, 0, 7, 39);
+ // Reset the Nonce. - This is the per-miner/pool nonce
+ this.extraNonce = 0;
+ // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients.
+ this.clientNonceLocation = this.reserveOffset + 12;
+ // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers.
+ this.clientPoolLocation = this.reserveOffset + 8;
+ this.nextBlob = function () {
+ // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset.
+ this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset);
+ // Convert the blob into something hashable.
+ return global.coinFuncs.convertBlob(this.buffer).toString('hex');
+ };
+ // Make it so you can get the raw block blob out.
+ this.nextBlobWithChildNonce = function () {
+ // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset.
+ this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset);
+ // Don't convert the blob to something hashable. You bad.
+ return this.buffer.toString('hex');
+ };
+ };
+
+ this.cryptoNight = multiHashing.cryptonight_light;
+
+}
+
+module.exports = Coin;
diff --git a/lib/coins/krb.js b/lib/coins/krb.js
new file mode 100644
index 0000000..1494da8
--- /dev/null
+++ b/lib/coins/krb.js
@@ -0,0 +1,155 @@
+"use strict";
+const bignum = require('bignum');
+const cnUtil = require('cryptonote-util');
+const multiHashing = require('multi-hashing');
+const crypto = require('crypto');
+const debug = require('debug')('coinFuncs');
+
+let hexChars = new RegExp("[0-9a-f]+");
+
+function Coin(data){
+ this.bestExchange = global.config.payout.bestExchange;
+ this.data = data;
+ let instanceId = crypto.randomBytes(4);
+ this.coinDevAddress = "Kdev1L9V5ow3cdKNqDpLcFFxZCqu5W2GE9xMKewsB2pUXWxcXvJaUWHcSrHuZw91eYfQFzRtGfTemReSSMN4kE445i6Etb3"; // Developer Address
+ this.poolDevAddress = "KgseWakG2bMXHGJSsAUfzL1HykCyvD4m8gd9qgcuyZ1ufy8PqUCKRxEfAv3nahfdTrCjZByiWoCiRiohxq4u2rf2RgQ1pcJ"; // Snipa Address
+
+ this.blockedAddresses = [
+ this.coinDevAddress,
+ this.poolDevAddress,
+ ];
+
+ this.exchangeAddresses = [
+ ]; // These are addresses that MUST have a paymentID to perform logins with.
+
+ this.prefix = 111;
+
+ this.supportsAutoExchange = false;
+
+ this.niceHashDiff = 200000;
+
+ this.getBlockHeaderByID = function(blockId, callback){
+ global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) {
+ if (body.hasOwnProperty('result')){
+ return callback(null, body.result.block_header);
+ } else {
+ console.error(JSON.stringify(body));
+ return callback(true, body);
+ }
+ });
+ };
+
+ this.getBlockHeaderByHash = function(blockHash, callback){
+ global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) {
+ if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){
+ return callback(null, body.result.block_header);
+ } else {
+ console.error(JSON.stringify(body));
+ return callback(true, body);
+ }
+ });
+ };
+
+ this.getLastBlockHeader = function(callback){
+ global.support.rpcDaemon('getlastblockheader', [], function (body) {
+ if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){
+ return callback(null, body.result.block_header);
+ } else {
+ console.error(JSON.stringify(body));
+ return callback(true, body);
+ }
+ });
+ };
+
+ this.getBlockTemplate = function(walletAddress, callback){
+ global.support.rpcDaemon('getblocktemplate', {
+ reserve_size: 17,
+ wallet_address: walletAddress
+ }, function(body){
+ return callback(body);
+ });
+ };
+
+ this.baseDiff = function(){
+ return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16);
+ };
+
+ this.validateAddress = function(address){
+ // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address.
+ address = new Buffer(address);
+ if (cnUtil.address_decode(address) === this.prefix){
+ return true;
+ }
+ return cnUtil.address_decode_integrated(address) === this.intPrefix;
+ };
+
+ this.convertBlob = function(blobBuffer){
+ return cnUtil.convert_blob(blobBuffer);
+ };
+
+ this.constructNewBlob = function(blockTemplate, NonceBuffer){
+ return cnUtil.construct_block_blob(blockTemplate, NonceBuffer);
+ };
+
+ this.getBlockID = function(blockBuffer){
+ return cnUtil.get_block_id(blockBuffer);
+ };
+
+ this.BlockTemplate = function(template) {
+ /*
+ Generating a block template is a simple thing. Ask for a boatload of information, and go from there.
+ Important things to consider.
+ The reserved space is 13 bytes long now in the following format:
+ Assuming that the extraNonce starts at byte 130:
+ |130-133|134-137|138-141|142-145|
+ |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes|
+ This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce)
+ Each with 4 billion clients. (clientNonce)
+ While being unique to this particular pool thread (instanceId)
+ With up to 4 billion clients (minerNonce/extraNonce)
+ Overkill? Sure. But that's what we do here. Overkill.
+ */
+
+ // Set this.blob equal to the BT blob that we get from upstream.
+ this.blob = template.blocktemplate_blob;
+ this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex');
+ // Set this.diff equal to the known diff for this block.
+ this.difficulty = template.difficulty;
+ // Set this.height equal to the known height for this block.
+ this.height = template.height;
+ // Set this.reserveOffset to the byte location of the reserved offset.
+ this.reserveOffset = template.reserved_offset;
+ // Set this.buffer to the binary decoded version of the BT blob.
+ this.buffer = new Buffer(this.blob, 'hex');
+ // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes.
+ instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3);
+ // Generate a clean, shiny new buffer.
+ this.previous_hash = new Buffer(32);
+ // Copy in bytes 7 through 39 to this.previous_hash from the current BT.
+ this.buffer.copy(this.previous_hash, 0, 7, 39);
+ // Reset the Nonce. - This is the per-miner/pool nonce
+ this.extraNonce = 0;
+ // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients.
+ this.clientNonceLocation = this.reserveOffset + 12;
+ // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers.
+ this.clientPoolLocation = this.reserveOffset + 8;
+ this.nextBlob = function () {
+ // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset.
+ this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset);
+ // Convert the blob into something hashable.
+ return global.coinFuncs.convertBlob(this.buffer).toString('hex');
+ };
+ // Make it so you can get the raw block blob out.
+ this.nextBlobWithChildNonce = function () {
+ // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset.
+ this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset);
+ // Don't convert the blob to something hashable. You bad.
+ return this.buffer.toString('hex');
+ };
+ };
+
+ this.cryptoNight = multiHashing.cryptonight;
+
+}
+
+module.exports = Coin;
diff --git a/lib/coins/xmr.js b/lib/coins/xmr.js
index e07c280..ac97e8b 100644
--- a/lib/coins/xmr.js
+++ b/lib/coins/xmr.js
@@ -3,8 +3,12 @@ const bignum = require('bignum');
const cnUtil = require('cryptonote-util');
const multiHashing = require('multi-hashing');
const crypto = require('crypto');
+const debug = require('debug')('coinFuncs');
+
+let hexChars = new RegExp("[0-9a-f]+");
function Coin(data){
+ this.bestExchange = global.config.payout.bestExchange;
this.data = data;
let instanceId = crypto.randomBytes(4);
this.coinDevAddress = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; // Developer Address
@@ -34,14 +38,17 @@ function Coin(data){
this.intPrefix = 54;
}
- this.niceHashDiff = 100000;
+ this.supportsAutoExchange = true;
+
+ this.niceHashDiff = 200000;
this.getBlockHeaderByID = function(blockId, callback){
global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) {
if (body.hasOwnProperty('result')){
- return callback(body.result.block_header);
+ return callback(null, body.result.block_header);
} else {
console.error(JSON.stringify(body));
+ return callback(true, body);
}
});
};
@@ -49,10 +56,10 @@ function Coin(data){
this.getBlockHeaderByHash = function(blockHash, callback){
global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) {
if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){
- return callback(body.result.block_header);
+ return callback(null, body.result.block_header);
} else {
console.error(JSON.stringify(body));
- return callback(false);
+ return callback(true, body);
}
});
};
@@ -60,9 +67,10 @@ function Coin(data){
this.getLastBlockHeader = function(callback){
global.support.rpcDaemon('getlastblockheader', [], function (body) {
if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){
- return callback(body.result.block_header);
+ return callback(null, body.result.block_header);
} else {
console.error(JSON.stringify(body));
+ return callback(true, body);
}
});
};
@@ -72,7 +80,7 @@ function Coin(data){
reserve_size: 17,
wallet_address: walletAddress
}, function(body){
- return callback(body);
+ return callback(err, body);
});
};
diff --git a/lib/local_comms.js b/lib/local_comms.js
index cb996e4..b86bea4 100644
--- a/lib/local_comms.js
+++ b/lib/local_comms.js
@@ -375,8 +375,8 @@ function Database(){
this.refreshEnv();
try{
let blockDataDecoded = global.protos.Block.decode(blockData);
- global.coinFuncs.getBlockHeaderByHash(blockDataDecoded.hash, function(header){
- if (typeof(header) === 'undefined' || !header){
+ global.coinFuncs.getBlockHeaderByHash(blockDataDecoded.hash, function(err, header){
+ if (err || typeof(header) === 'undefined' || !header){
return callback(false);
}
blockDataDecoded.value = header.reward;
@@ -409,12 +409,14 @@ function Database(){
let blockData = txn.getBinary(global.database.blockDB, blockId);
txn.abort();
let blockDataDecoded = global.protos.Block.decode(blockData);
- global.coinFuncs.getBlockHeaderByHash(blockDataDecoded.hash, function(header){
- blockDataDecoded.value = header.reward;
- blockData = global.database.calculateShares(blockDataDecoded, blockId);
- let txn = global.database.env.beginTxn();
- txn.putBinary(global.database.blockDB, blockId, blockData);
- txn.commit();
+ global.coinFuncs.getBlockHeaderByHash(blockDataDecoded.hash, function(err, header){
+ if (err === null) {
+ blockDataDecoded.value = header.reward;
+ blockData = global.database.calculateShares(blockDataDecoded, blockId);
+ let txn = global.database.env.beginTxn();
+ txn.putBinary(global.database.blockDB, blockId, blockData);
+ txn.commit();
+ }
});
};
@@ -534,8 +536,11 @@ function Database(){
// Hopping into async, we need the current block height to know where to start our indexing...
async.waterfall([
function(callback){
- global.coinFuncs.getLastBlockHeader(function(body){
- callback(null, body.height, Math.floor(body.difficulty * 1.5 * global.config.pplns.shareMultiLog));
+ global.coinFuncs.getLastBlockHeader(function(err, body){
+ if (err) {
+ return callback(true, body);
+ }
+ return callback(null, body.height, Math.floor(body.difficulty * 1.5 * global.config.pplns.shareMultiLog));
});
},
function (lastBlock, difficulty, callback) {
@@ -584,7 +589,7 @@ function Database(){
callback(null, blockList);
}
], function(err, data){
- if (global.config.general.blockCleaner === true){
+ if (err === null && global.config.general.blockCleaner === true){
if(data.length > 0){
global.database.refreshEnv();
let blockList = global.database.getBlockList();
diff --git a/lib/payment_systems/aeon.js b/lib/payment_systems/aeon.js
new file mode 100644
index 0000000..28ba1a9
--- /dev/null
+++ b/lib/payment_systems/aeon.js
@@ -0,0 +1,257 @@
+"use strict";
+const async = require("async");
+const debug = require("debug")("payments");
+
+let hexChars = new RegExp("[0-9a-f]+");
+let extraPaymentRound = false;
+let paymentTimer = null;
+
+let paymentQueue = async.queue(function (paymentDetails, callback) {
+ if (paymentTimer !== null){
+ clearInterval(paymentTimer);
+ paymentTimer = null;
+ }
+ debug("Making payment based on: " + JSON.stringify(paymentDetails));
+ let transferFunc = 'transfer';
+ global.support.rpcWallet(transferFunc, paymentDetails, function (body) {
+ debug("Payment made: " + JSON.stringify(body));
+ if (body.hasOwnProperty('error')) {
+ if (body.error.message === "not enough money"){
+ console.error("Issue making payments, not enough money, will try later");
+ if(!extraPaymentRound){
+ setTimeout(function(){
+ makePayments();
+ }, global.config.payout.timerRetry * 60 * 1000);
+ }
+ extraPaymentRound = true;
+ return callback(false);
+ } else {
+ console.error("Issue making payments" + JSON.stringify(body.error));
+ console.error("Will not make more payments until the payment daemon is restarted!");
+ //toAddress, subject, body
+ global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment",
+ "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) +
+ ". Please investigate and restart the payment daemon as appropriate");
+ return;
+ }
+ }
+ if (paymentDetails.hasOwnProperty('payment_id')) {
+ console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee");
+ return callback(body.result);
+ } else {
+ if (transferFunc === 'transfer') {
+ console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR");
+ }
+ let intCount = 0;
+ paymentDetails.destinations.forEach(function (details) {
+ console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR");
+ intCount += 1;
+ if (intCount === paymentDetails.destinations.length) {
+ return callback(body.result);
+ }
+ });
+ }
+ });
+}, 1);
+
+paymentQueue.drain = function(){
+ extraPaymentRound = false;
+ if (global.config.payout.timer > 35791){
+ console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows.");
+ } else {
+ paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000);
+ }
+ global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000));
+};
+
+function Payee(amount, address, paymentID, bitcoin) {
+ this.amount = amount;
+ this.address = address;
+ this.paymentID = paymentID;
+ this.bitcoin = bitcoin;
+ this.blockID = 0;
+ this.poolType = '';
+ this.transactionID = 0;
+ this.sqlID = 0;
+ if (paymentID === null) {
+ this.id = address;
+ } else {
+ this.id = address + "." + paymentID;
+ }
+ this.fee = 0;
+ this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount);
+ this.setFeeAmount = function () {
+ if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) {
+ this.fee = this.baseFee;
+ } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) {
+ let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin));
+ this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue);
+ }
+ this.fee = Math.floor(this.fee);
+ };
+
+ this.makePaymentWithID = function () {
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: this.amount - this.fee,
+ address: this.address
+ }
+ ],
+ fee: global.config.payout.fee,
+ unlock_time: global.config.payout.unlock_time,
+ mixin: global.config.payout.mixIn,
+ payment_id: this.paymentID
+ };
+ let identifier = this.id;
+ let amount = this.amount;
+ let address = this.address;
+ let paymentID = this.paymentID;
+ let payee = this;
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (typeof body.tx_hash !== 'undefined') {
+ debug("Successful payment sent to: " + identifier);
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, 1]).then(function (result) {
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ };
+
+ this.makePaymentAsIntegrated = function () {
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: this.amount - this.fee,
+ address: this.address
+ }
+ ],
+ fee: global.config.payout.fee,
+ unlock_time: global.config.payout.unlock_time,
+ mixin: global.config.payout.mixIn
+ };
+ let identifier = this.id;
+ let amount = this.amount;
+ let address = this.address;
+ let payee = this;
+
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (typeof body.tx_hash !== 'undefined') {
+ debug("Successful payment sent to: " + identifier);
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, 1]).then(function (result) {
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ };
+
+ this.trackPayment = function () {
+ global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]);
+ global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" +
+ " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]);
+ };
+}
+
+function makePayments() {
+ global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) {
+ console.log("Loaded all payees into the system for processing");
+ let paymentDestinations = [];
+ let totalAmount = 0;
+ let roundCount = 0;
+ let payeeList = [];
+ let payeeObjects = {};
+ rows.forEach(function (row) {
+ debug("Starting round for: " + JSON.stringify(row));
+ let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin);
+ payeeObjects[row.payment_address] = payee;
+ global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) {
+ roundCount += 1;
+ let threshold = 0;
+ if (userRow.length !== 0) {
+ threshold = userRow[0].payout_threshold;
+ }
+ payee.poolType = row.pool_type;
+ payee.sqlID = row.id;
+ if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) {
+ debug("This is the fee address internal check for value");
+ payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN);
+ } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") {
+ debug("Unable to pay fee address.");
+ payee.amount = 0;
+ }
+ let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor);
+ if (remainder !== 0) {
+ payee.amount -= remainder;
+ }
+ if (payee.amount > threshold) {
+ payee.setFeeAmount();
+ if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) {
+ debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address});
+ totalAmount += payee.amount;
+ payeeList.push(payee);
+ } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) {
+ debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ payee.makePaymentWithID();
+ }
+ }
+ debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows");
+ if (roundCount === rows.length && paymentDestinations.length > 0) {
+ while (paymentDestinations.length > 0) {
+ let paymentDetails = {
+ destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns),
+ mixin: global.config.payout.mixIn,
+ fee: global.config.payout.fee,
+ unlock_time: global.config.payout.unlock_time
+ };
+ console.log("Paying out: " + paymentDetails.destinations.length + " people");
+ paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line
+ // This is the only section that could potentially contain multiple txns. Lets do this safely eh?
+ if (typeof body.tx_hash !== 'undefined') {
+ debug("Made it to the SQL insert for transactions");
+ let totalAmount = 0;
+ paymentDetails.destinations.forEach(function (payeeItem) {
+ totalAmount += payeeObjects[payeeItem.address].amount;
+ totalAmount += payeeObjects[payeeItem.address].fee;
+ });
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, paymentDetails.destinations.length]).then(function (result) {
+ paymentDetails.destinations.forEach(function (payeeItem) {
+ payee = payeeObjects[payeeItem.address];
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ }
+ }
+ });
+ });
+ });
+}
+
+function init() {
+ global.support.rpcWallet("store", [], function () {
+ });
+ setInterval(function () {
+ global.support.rpcWallet("store", [], function () {
+ });
+ }, 60000);
+ console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money");
+ makePayments();
+}
+
+init();
\ No newline at end of file
diff --git a/lib/payment_systems/krb.js b/lib/payment_systems/krb.js
new file mode 100644
index 0000000..bc907c1
--- /dev/null
+++ b/lib/payment_systems/krb.js
@@ -0,0 +1,254 @@
+"use strict";
+const async = require("async");
+const debug = require("debug")("payments");
+
+let hexChars = new RegExp("[0-9a-f]+");
+let extraPaymentRound = false;
+let paymentTimer = null;
+
+let paymentQueue = async.queue(function (paymentDetails, callback) {
+ if (paymentTimer !== null){
+ clearInterval(paymentTimer);
+ paymentTimer = null;
+ }
+ debug("Making payment based on: " + JSON.stringify(paymentDetails));
+ let transferFunc = 'transfer';
+ global.support.rpcWallet(transferFunc, paymentDetails, function (body) {
+ debug("Payment made: " + JSON.stringify(body));
+ if (body.hasOwnProperty('error')) {
+ if (body.error.message === "not enough money"){
+ console.error("Issue making payments, not enough money, will try later");
+ if(!extraPaymentRound){
+ setTimeout(function(){
+ makePayments();
+ }, global.config.payout.timerRetry * 60 * 1000);
+ }
+ extraPaymentRound = true;
+ return callback(false);
+ } else {
+ console.error("Issue making payments" + JSON.stringify(body.error));
+ console.error("Will not make more payments until the payment daemon is restarted!");
+ //toAddress, subject, body
+ global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment",
+ "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) +
+ ". Please investigate and restart the payment daemon as appropriate");
+ return;
+ }
+ }
+ if (paymentDetails.hasOwnProperty('payment_id')) {
+ console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee");
+ return callback(body.result);
+ } else {
+ if (transferFunc === 'transfer') {
+ console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR");
+ }
+ let intCount = 0;
+ paymentDetails.destinations.forEach(function (details) {
+ console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR");
+ intCount += 1;
+ if (intCount === paymentDetails.destinations.length) {
+ return callback(body.result);
+ }
+ });
+ }
+ });
+}, 1);
+
+paymentQueue.drain = function(){
+ extraPaymentRound = false;
+ if (global.config.payout.timer > 35791){
+ console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows.");
+ } else {
+ paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000);
+ }
+ global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000));
+};
+
+function Payee(amount, address, paymentID, bitcoin) {
+ this.amount = amount;
+ this.address = address;
+ this.paymentID = paymentID;
+ this.bitcoin = bitcoin;
+ this.blockID = 0;
+ this.poolType = '';
+ this.transactionID = 0;
+ this.sqlID = 0;
+ if (paymentID === null) {
+ this.id = address;
+ } else {
+ this.id = address + "." + paymentID;
+ }
+ this.fee = 0;
+ this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount);
+ this.setFeeAmount = function () {
+ if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) {
+ this.fee = this.baseFee;
+ } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) {
+ let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin));
+ this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue);
+ }
+ this.fee = Math.floor(this.fee);
+ };
+
+ this.makePaymentWithID = function () {
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: this.amount - this.fee,
+ address: this.address
+ }
+ ],
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn,
+ payment_id: this.paymentID
+ };
+ let identifier = this.id;
+ let amount = this.amount;
+ let address = this.address;
+ let paymentID = this.paymentID;
+ let payee = this;
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (body.fee && body.fee > 10) {
+ debug("Successful payment sent to: " + identifier);
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) {
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ };
+
+ this.makePaymentAsIntegrated = function () {
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: this.amount - this.fee,
+ address: this.address
+ }
+ ],
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn
+ };
+ let identifier = this.id;
+ let amount = this.amount;
+ let address = this.address;
+ let payee = this;
+
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (body.fee && body.fee > 10) {
+ debug("Successful payment sent to: " + identifier);
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) {
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ };
+
+ this.trackPayment = function () {
+ global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]);
+ global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" +
+ " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]);
+ };
+}
+
+function makePayments() {
+ global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) {
+ console.log("Loaded all payees into the system for processing");
+ let paymentDestinations = [];
+ let totalAmount = 0;
+ let roundCount = 0;
+ let payeeList = [];
+ let payeeObjects = {};
+ rows.forEach(function (row) {
+ debug("Starting round for: " + JSON.stringify(row));
+ let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin);
+ payeeObjects[row.payment_address] = payee;
+ global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) {
+ roundCount += 1;
+ let threshold = 0;
+ if (userRow.length !== 0) {
+ threshold = userRow[0].payout_threshold;
+ }
+ payee.poolType = row.pool_type;
+ payee.sqlID = row.id;
+ if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) {
+ debug("This is the fee address internal check for value");
+ payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN);
+ } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") {
+ debug("Unable to pay fee address.");
+ payee.amount = 0;
+ }
+ let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor);
+ if (remainder !== 0) {
+ payee.amount -= remainder;
+ }
+ if (payee.amount > threshold) {
+ payee.setFeeAmount();
+ if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) {
+ debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address});
+ totalAmount += payee.amount;
+ payeeList.push(payee);
+ } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) {
+ debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ payee.makePaymentWithID();
+ }
+ }
+ debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows");
+ if (roundCount === rows.length && paymentDestinations.length > 0) {
+ while (paymentDestinations.length > 0) {
+ let paymentDetails = {
+ destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns),
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn
+ };
+ console.log("Paying out: " + paymentDetails.destinations.length + " people");
+ paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line
+ // This is the only section that could potentially contain multiple txns. Lets do this safely eh?
+ if (body.fee && body.fee > 10) {
+ debug("Made it to the SQL insert for transactions");
+ let totalAmount = 0;
+ paymentDetails.destinations.forEach(function (payeeItem) {
+ totalAmount += payeeObjects[payeeItem.address].amount;
+ totalAmount += payeeObjects[payeeItem.address].fee;
+ });
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) {
+ paymentDetails.destinations.forEach(function (payeeItem) {
+ payee = payeeObjects[payeeItem.address];
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ }
+ }
+ });
+ });
+ });
+}
+
+function init() {
+ global.support.rpcWallet("store", [], function () {
+ });
+ setInterval(function () {
+ global.support.rpcWallet("store", [], function () {
+ });
+ }, 60000);
+ console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money");
+ makePayments();
+}
+
+init();
\ No newline at end of file
diff --git a/lib/payment_systems/xmr.js b/lib/payment_systems/xmr.js
new file mode 100644
index 0000000..596397c
--- /dev/null
+++ b/lib/payment_systems/xmr.js
@@ -0,0 +1,696 @@
+"use strict";
+const shapeshift = require('shapeshift.io');
+const async = require("async");
+const debug = require("debug")("payments");
+const request = require('request-json');
+const range = require('range');
+
+let hexChars = new RegExp("[0-9a-f]+");
+let bestExchange = global.config.payout.bestExchange;
+let xmrAPIClient = request.createClient('https://xmr.to/api/v1/xmr2btc/');
+let extraPaymentRound = false;
+let paymentTimer = null;
+
+let shapeshiftQueue = async.queue(function (task, callback) {
+ // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values..
+ let amount = task.amount - task.fee;
+ // Address is the destination address IN BTC.
+ let address = task.address;
+ // PaymentIDs are the paymentID's to flag as paid by this transaction.
+ // Should be a massive list of ID's so we can bulk-update them, by merging them with 's.
+ // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around.
+ // Once there's enough funds, then we active txn
+ // Do a wallet call to xfer.
+ // Setup a monitor on the transaction
+ async.waterfall([
+ function (intCallback) {
+ // Verify if the coin is active in ShapeShift first.
+ shapeshift.coins(function (err, coinData) {
+ if (err) {
+ intCallback(err);
+ } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") {
+ intCallback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift.");
+ } else {
+ intCallback(null);
+ }
+ });
+ },
+ function (intCallback) {
+ // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc.
+ shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) {
+ if (err) {
+ intCallback(err);
+ } else if (!marketInfo.hasOwnProperty("limit") || marketInfo.limit <= global.support.coinToDecimal(amount)) {
+ intCallback("Not enough coin in shapeshift to process at this time.");
+ } else if (!marketInfo.hasOwnProperty("min") || marketInfo.min >= global.support.coinToDecimal(amount)) {
+ intCallback("Not enough coin to hit the shapeshift minimum deposits.");
+ } else {
+ intCallback(null, marketInfo);
+ }
+ });
+ },
+ function (marketInfo, intCallback) {
+ // Validated there's enough coin. Time to make our dank txn.
+ // Return:
+ /*
+ {
+ "orderId": "cc49c556-e645-4c15-a943-d50a935274e4",
+ "sAddress": "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6",
+ "deposit": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e",
+ "depositType": "XMR",
+ "withdrawal": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu",
+ "withdrawalType": "BTC",
+ "public": null,
+ "apiPubKey": "shapeshift",
+ "returnAddress": "46XWBqE1iwsVxSDP1qDrxhE1XvsZV6eALG5LwnoMdjbT4GPdy2bZTb99kagzxp2MMjUamTYZ4WgvZdFadvMimTjvR6Gv8hL",
+ "returnAddressType": "XMR"
+ }
+ Valid Statuses:
+ "received"
+ "complete"
+ "error"
+ "no_deposits"
+ Complete State Information:
+ {
+ "status": "complete",
+ "address": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e",
+ "withdraw": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu",
+ "incomingCoin": 3,
+ "incomingType": "XMR",
+ "outgoingCoin": "0.04186155",
+ "outgoingType": "BTC",
+ "transaction": "be9d97f6fc75262151f8f63e035c6ed638b9eb2a4e93fef43ea63124b045dbfb"
+ }
+ */
+ shapeshift.shift(address, global.config.payout.shapeshiftPair, {returnAddress: global.config.pool.address}, function (err, returnData) {
+ if (err) {
+ intCallback(err);
+ } else {
+ global.mysql.query("INSERT INTO shapeshiftTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus) VALUES (?,?,?,?,?,?,?,?,?)",
+ [returnData.orderId, returnData.sAddress, returnData.deposit, returnData.depositType, returnData.withdrawl, returnData.withdrawlType, returnData.returnAddress, returnData.returnAddressType, 'no_deposits']).then(function () {
+ intCallback(null, marketInfo, returnData);
+ }).catch(function (error) {
+ intCallback(error);
+ });
+ }
+ });
+ },
+ function (marketInfo, shapeshiftTxnData, intCallback) {
+ // Make the payment to ShapeShift
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: amount,
+ address: shapeshiftTxnData.sAddress
+ }
+ ],
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn,
+ payment_id: shapeshiftTxnData.deposit
+ };
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (body.fee && body.fee > 10) {
+ intCallback(null, marketInfo, shapeshiftTxnData, body);
+ } else {
+ intCallback("Unknown error from the wallet.");
+ }
+ });
+ },
+ function (marketInfo, shapeshiftTxnData, body, intCallback) {
+ // body.tx_hash = XMR transaction hash.
+ // Need to add transaction.
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ [1, address, null, task.amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.support.decimalToCoin(marketInfo.minerFee), 1, global.support.decimalToCoin(marketInfo.rate), 'shapeshift', shapeshiftTxnData.orderId]).then(function (result) {
+ intCallback(null, result.insertId);
+ }).catch(function (error) {
+ intCallback(error);
+ });
+ }
+ ], function (err, result) {
+ if (err) {
+ console.error("Error processing shapeshift txn: " + JSON.stringify(err));
+ callback(true);
+ } else {
+ // Need to fill out this data pronto!
+ console.log("Processed ShapeShift transaction for: " + address + " Paid out: " + result + " payments in the db");
+ callback(null, result);
+ }
+ });
+}, 2);
+
+let xmrToQueue = async.queue(function (task, callback) {
+ // http://xmrto-api.readthedocs.io/en/latest/introduction.html
+ // Documentation looks good!
+ // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values..
+ let amount = task.amount - task.fee;
+ // Address is the destination address IN BTC.
+ let address = task.address;
+ // PaymentIDs are the paymentID's to flag as paid by this transaction.
+ // Should be a massive list of ID's so we can bulk-update them, by merging them with 's.
+ // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around.
+ // Once there's enough funds, then we active txn
+ // Do a wallet call to xfer.
+ // Setup a monitor on the transaction
+ async.waterfall([
+ function (intCallback) {
+ // Verify if XMR.to is ready to get to work.
+ xmrAPIClient.get('order_parameter_query/', function (err, res, body) {
+ if (err) {
+ return intCallback(err);
+ } else if (body.error_msg) {
+ return intCallback(body.error_msg);
+ } else {
+ let amtOfBTC = ((amount / global.config.general.sigDivisor) * body.price).toPrecision(5);
+ console.log("Attempting to pay: " + address + " Amount: " + amtOfBTC + " BTC or " + amount / global.config.general.sigDivisor + " XMR");
+ console.log("Response from XMR.to: " + JSON.stringify(body));
+ if (body.lower_limit >= amtOfBTC) {
+ return intCallback("Not enough XMR to hit the minimum deposit");
+ } else if (body.upper_limit <= amtOfBTC) {
+ return intCallback("Too much XMR to pay out to xmr.to");
+ } else {
+ return intCallback(null, amtOfBTC);
+ }
+ }
+ });
+ },
+ function (btcValue, intCallback) {
+ // Validated there's enough coin. Time to make our dank txn.
+ // Return:
+ /*
+ {
+ "state": "TO_BE_CREATED",
+ "btc_amount": ,
+ "btc_dest_address": "",
+ "uuid": ""
+ }
+ Valid Statuses:
+ "TO_BE_CREATED"
+ "UNPAID"
+ "UNDERPAID"
+ "PAID_UNCONFIRMED"
+ "PAID"
+ "BTC_SENT"
+ "TIMED_OUT"
+ "NOT_FOUND"
+ // Create, then immediately update with the new information w/ a status call.
+ */
+ console.log("Amount of BTC to pay: " + btcValue);
+ xmrAPIClient.post('order_create/', {
+ btc_amount: btcValue,
+ btc_dest_address: address
+ }, function (err, res, body) {
+ if (err) {
+ return intCallback(err);
+ } else if (body.error_msg) {
+ return intCallback(body.error_msg);
+ } else {
+ return intCallback(null, body.uuid);
+ }
+ });
+ },
+ function (txnID, intCallback) {
+ // This function only exists because xmr.to is a pretty little fucking princess.
+ async.doUntil(function (xmrCallback) {
+ xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) {
+ if (err) {
+ return intCallback(err);
+ } else if (body.error_msg) {
+ return intCallback(body.error_msg);
+ } else {
+ xmrCallback(null, body.state);
+ }
+ });
+ },
+ function (xmrCallback) {
+ return xmrCallback !== "TO_BE_CREATED";
+ },
+ function () {
+ intCallback(null, txnID);
+ });
+ },
+ function (txnID, intCallback) {
+ xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) {
+ if (err) {
+ return intCallback(err);
+ } else if (body.error_msg) {
+ return intCallback(body.error_msg);
+ } else {
+ console.log(JSON.stringify(body));
+ global.mysql.query("INSERT INTO xmrtoTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus, amountDeposited, amountSent) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
+ [txnID, body.xmr_receiving_address, body.xmr_required_payment_id, 'XMR', body.btc_dest_address, 'BTC', global.config.pool.address, 'XMR', body.state_str, global.support.decimalToCoin(body.xmr_amount_total), global.support.decimalToCoin(body.btc_amount)]).then(function () {
+ return intCallback(null, body, global.support.decimalToCoin(body.xmr_amount_total));
+ }).catch(function (error) {
+ return intCallback(error);
+ });
+ }
+ });
+ },
+ function (orderStatus, xmrDeposit, intCallback) {
+ // Make the payment to ShapeShift
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: xmrDeposit,
+ address: orderStatus.xmr_receiving_address
+ }
+ ],
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn,
+ payment_id: orderStatus.xmr_required_payment_id
+ };
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (body.fee && body.fee > 10) {
+ return intCallback(null, orderStatus, body);
+ } else {
+ return intCallback("Unknown error from the wallet.");
+ }
+ });
+ },
+ function (orderStatus, body, intCallback) {
+ // body.tx_hash = XMR transaction hash.
+ // Need to add transaction.
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ [1, address, null, global.support.decimalToCoin(orderStatus.xmr_amount_total), body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1, global.support.decimalToCoin(orderStatus.xmr_price_btc), 'xmrto', orderStatus.uuid]).then(function (result) {
+ return intCallback(null, result.insertId);
+ }).catch(function (error) {
+ return intCallback(error);
+ });
+ }
+ ], function (err, result) {
+ if (err) {
+ console.error("Error processing XMRTo txn: " + JSON.stringify(err));
+ return callback("Error!");
+ } else {
+ // Need to fill out this data pronto!
+ console.log("Processed XMRTo transaction for: " + address + " Paid out: " + result + " payments in the db");
+ return callback(null, result);
+ }
+ });
+}, 2);
+
+let paymentQueue = async.queue(function (paymentDetails, callback) {
+ /*
+ support JSON URI: http://10.0.0.2:28082/json_rpc Args: {"id":"0","jsonrpc":"2.0","method":"transfer","params":{"destinations":[{"amount":68130252045355,"address":"A2MSrn49ziBPJBh8ZNEhhbfyLMou6mao4C1F5TLGUatmUnCxZArDYkcbAnVkVEopWVeak2rKDrmc8JpoS7n5dvfN9YDPBTG"}],"mixin":4,"payment_id":"7e52c5266de9fede7fb3abc0cd88f937b38b51426f7b34ff99729d28ce4e1142"}} +1ms
+ payments Payment made: {"id":"0","jsonrpc":"2.0","result":{"fee":40199391255,"tx_hash":"c418708643f72635edf522490bfb2cae9d42a6dc1df30dcde844862dfd88f5b3","tx_key":""}} +2s
+ */
+ if (paymentTimer !== null){
+ clearInterval(paymentTimer);
+ paymentTimer = null;
+ }
+ debug("Making payment based on: " + JSON.stringify(paymentDetails));
+ let transferFunc = 'transfer';
+ global.support.rpcWallet(transferFunc, paymentDetails, function (body) {
+ debug("Payment made: " + JSON.stringify(body));
+ if (body.hasOwnProperty('error')) {
+ if (body.error.message === "not enough money"){
+ console.error("Issue making payments, not enough money, will try later");
+ if(!extraPaymentRound){
+ setTimeout(function(){
+ makePayments();
+ }, global.config.payout.timerRetry * 60 * 1000);
+ }
+ extraPaymentRound = true;
+ return callback(false);
+ } else {
+ console.error("Issue making payments" + JSON.stringify(body.error));
+ console.error("Will not make more payments until the payment daemon is restarted!");
+ //toAddress, subject, body
+ global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment",
+ "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) +
+ ". Please investigate and restart the payment daemon as appropriate");
+ return;
+ }
+ }
+ if (paymentDetails.hasOwnProperty('payment_id')) {
+ console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee");
+ return callback(body.result);
+ } else {
+ if (transferFunc === 'transfer') {
+ console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR");
+ }
+ let intCount = 0;
+ paymentDetails.destinations.forEach(function (details) {
+ console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR");
+ intCount += 1;
+ if (intCount === paymentDetails.destinations.length) {
+ return callback(body.result);
+ }
+ });
+ }
+ });
+}, 1);
+
+paymentQueue.drain = function(){
+ extraPaymentRound = false;
+ if (global.config.payout.timer > 35791){
+ console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows.");
+ } else {
+ paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000);
+ }
+ global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000));
+};
+
+function updateShapeshiftCompletion() {
+ global.mysql.query("SELECT * FROM shapeshiftTxn WHERE txnStatus NOT IN ('complete', 'error')").then(function (rows) {
+ rows.forEach(function (row) {
+ shapeshift.status(row.paymentID, function (err, status, returnData) {
+ if (err) {
+ return;
+ }
+ global.mysql.query("UPDATE shapeshiftTxn SET txnStatus = ? WHERE id = ?", [status, row.id]).then(function () {
+ if (status === 'complete') {
+ global.mysql.query("UPDATE shapeshiftTxn SET amountDeposited = ?, amountSent = ?, transactionHash = ? WHERE id = ?",
+ [global.support.decimalToCoin(returnData.incomingCoin), global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), returnData.transaction, row.id]).then(function () {
+ global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), row.id]);
+ });
+ } else if (status === 'error') {
+ // Failed txn. Need to rollback and delete all related data. Here we go!
+ global.mysql.query("DELETE FROM shapeshiftTxn WHERE id = ?", [row.id]);
+ global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) {
+ global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]);
+ global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]);
+ global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]);
+ });
+ console.error("Failed transaction from ShapeShift " + JSON.stringify(returnData));
+ }
+ });
+ });
+ });
+ });
+}
+
+function updateXMRToCompletion() {
+ global.mysql.query("SELECT * FROM xmrtoTxn WHERE txnStatus NOT IN ('PAID', 'TIMED_OUT', 'NOT_FOUND', 'BTC_SENT')").then(function (rows) {
+ rows.forEach(function (row) {
+ xmrAPIClient.post('order_status_query/', {uuid: row.id}, function (err, res, body) {
+ if (err) {
+ console.log("Error in getting order status: " + JSON.stringify(err));
+ return;
+ }
+ if (body.error_msg) {
+ console.log("Error in getting order status: " + body.error_msg);
+ return;
+ }
+ global.mysql.query("UPDATE xmrtoTxn SET txnStatus = ? WHERE id = ?", [body.state, row.id]).then(function () {
+ if (body.status === 'BTC_SENT') {
+ global.mysql.query("UPDATE xmrtoTxn SET transactionHash = ? WHERE id = ?", [body.btc_transaction_id, row.id]).then(function () {
+ global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(body.btc_amount), row.id]);
+ });
+ } else if (body.status === 'TIMED_OUT' || body.status === 'NOT_FOUND') {
+ global.mysql.query("DELETE FROM xmrtoTxn WHERE id = ?", [row.id]);
+ global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) {
+ global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]);
+ global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]);
+ global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]);
+ });
+ console.error("Failed transaction from XMRto " + JSON.stringify(body));
+ }
+ });
+ });
+ });
+ });
+}
+
+function determineBestExchange() {
+ async.waterfall([
+ function (callback) {
+ // Verify if the coin is active in ShapeShift first.
+ shapeshift.coins(function (err, coinData) {
+ if (err) {
+ return callback(err);
+ } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") {
+ return callback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift.");
+ } else {
+ return callback(null);
+ }
+ });
+ },
+ function (callback) {
+ // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc.
+ shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) {
+ if (err) {
+ return callback(err);
+ } else if (!marketInfo.hasOwnProperty("rate")) {
+ return callback("Shapeshift did not return the rate.");
+ } else {
+ return callback(null, global.support.bitcoinDecimalToCoin(marketInfo.rate));
+ }
+ });
+ },
+ function (ssValue, callback) {
+ xmrAPIClient.get('order_parameter_query/', function (err, res, body) {
+ console.log("XMR.to pricing body: " + JSON.stringify(body));
+ if (err) {
+ return callback(err);
+ } else if (body.error_msg) {
+ return callback(body.error_msg);
+ } else {
+ return callback(null, ssValue, global.support.bitcoinDecimalToCoin(body.price));
+ }
+ });
+ }
+ ], function (err, ssValue, xmrToValue) {
+ if (err) {
+ return console.error("Error processing exchange value: " + JSON.stringify(err));
+ }
+ debug("ShapeShift Value: " + global.support.bitcoinCoinToDecimal(ssValue) + " XMR.to Value: " + global.support.bitcoinCoinToDecimal(xmrToValue));
+ if (ssValue >= xmrToValue) {
+ console.log("ShapeShift is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(ssValue));
+ bestExchange = 'shapeshift';
+ global.mysql.query("UPDATE config SET item_value = 'shapeshift' where item='bestExchange'");
+ global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [ssValue]);
+ } else {
+ console.log("XMR.to is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(xmrToValue));
+ bestExchange = 'xmrto';
+ global.mysql.query("UPDATE config SET item_value = 'xmrto' where item='bestExchange'");
+ global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [xmrToValue]);
+ }
+ });
+}
+
+function Payee(amount, address, paymentID, bitcoin) {
+ this.amount = amount;
+ this.address = address;
+ this.paymentID = paymentID;
+ this.bitcoin = bitcoin;
+ this.blockID = 0;
+ this.poolType = '';
+ this.transactionID = 0;
+ this.sqlID = 0;
+ if (paymentID === null) {
+ this.id = address;
+ } else {
+ this.id = address + "." + paymentID;
+ }
+ this.fee = 0;
+ this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount);
+ this.setFeeAmount = function () {
+ if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) {
+ this.fee = this.baseFee;
+ } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) {
+ let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin));
+ this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue);
+ }
+ this.fee = Math.floor(this.fee);
+ };
+
+ this.makePaymentWithID = function () {
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: this.amount - this.fee,
+ address: this.address
+ }
+ ],
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn,
+ payment_id: this.paymentID
+ };
+ let identifier = this.id;
+ let amount = this.amount;
+ let address = this.address;
+ let paymentID = this.paymentID;
+ let payee = this;
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (body.fee && body.fee > 10) {
+ debug("Successful payment sent to: " + identifier);
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) {
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ };
+
+ this.makePaymentAsIntegrated = function () {
+ let paymentDetails = {
+ destinations: [
+ {
+ amount: this.amount - this.fee,
+ address: this.address
+ }
+ ],
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn
+ };
+ let identifier = this.id;
+ let amount = this.amount;
+ let address = this.address;
+ let payee = this;
+
+ debug("Payment Details: " + JSON.stringify(paymentDetails));
+ paymentQueue.push(paymentDetails, function (body) {
+ if (body.fee && body.fee > 10) {
+ debug("Successful payment sent to: " + identifier);
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) {
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ };
+
+ this.makeBitcoinPayment = function () {
+ let functionalData = {address: this.address, amount: this.amount, fee: this.fee};
+ let payee = this;
+ if (bestExchange === 'xmrto') {
+ xmrToQueue.push(functionalData, function (err, transactionID) {
+ if (err) {
+ return console.error("Error processing payment for " + functionalData.address);
+ }
+ payee.transactionID = transactionID;
+ payee.trackPayment();
+ });
+ } else {
+ shapeshiftQueue.push(functionalData, function (err, transactionID) {
+ if (err) {
+ return console.error("Error processing payment for " + functionalData.address);
+ }
+ payee.transactionID = transactionID;
+ payee.trackPayment();
+ });
+ }
+ };
+
+ this.trackPayment = function () {
+ global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]);
+ global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" +
+ " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]);
+ };
+}
+
+function makePayments() {
+ global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) {
+ console.log("Loaded all payees into the system for processing");
+ let paymentDestinations = [];
+ let totalAmount = 0;
+ let roundCount = 0;
+ let payeeList = [];
+ let payeeObjects = {};
+ rows.forEach(function (row) {
+ debug("Starting round for: " + JSON.stringify(row));
+ let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin);
+ payeeObjects[row.payment_address] = payee;
+ global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) {
+ roundCount += 1;
+ let threshold = 0;
+ if (userRow.length !== 0) {
+ threshold = userRow[0].payout_threshold;
+ }
+ payee.poolType = row.pool_type;
+ payee.sqlID = row.id;
+ if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) {
+ debug("This is the fee address internal check for value");
+ payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN);
+ } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") {
+ debug("Unable to pay fee address.");
+ payee.amount = 0;
+ }
+ let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor);
+ if (remainder !== 0) {
+ payee.amount -= remainder;
+ }
+ if (payee.amount > threshold) {
+ payee.setFeeAmount();
+ if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) {
+ debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address});
+ totalAmount += payee.amount;
+ payeeList.push(payee);
+ } else if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length === 106 && (payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0))) {
+ // Special code to handle integrated payment addresses. What a pain in the rear.
+ // These are exchange addresses though, so they need to hit the exchange payout amount.
+ debug("Adding " + payee.id + " to the list of people to pay (Integrated Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ payee.makePaymentAsIntegrated();
+ } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) {
+ debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ payee.makePaymentWithID();
+ } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 1) {
+ debug("Adding " + payee.id + " to the list of people to pay (Bitcoin Payout). Payee balance: " + global.support.coinToDecimal(payee.amount));
+ payee.makeBitcoinPayment();
+ }
+ }
+ debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows");
+ if (roundCount === rows.length && paymentDestinations.length > 0) {
+ while (paymentDestinations.length > 0) {
+ let paymentDetails = {
+ destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns),
+ priority: global.config.payout.priority,
+ mixin: global.config.payout.mixIn
+ };
+ console.log("Paying out: " + paymentDetails.destinations.length + " people");
+ paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line
+ // This is the only section that could potentially contain multiple txns. Lets do this safely eh?
+ if (body.fee && body.fee > 10) {
+ debug("Made it to the SQL insert for transactions");
+ let totalAmount = 0;
+ paymentDetails.destinations.forEach(function (payeeItem) {
+ totalAmount += payeeObjects[payeeItem.address].amount;
+ totalAmount += payeeObjects[payeeItem.address].fee;
+ });
+ global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) {
+ paymentDetails.destinations.forEach(function (payeeItem) {
+ payee = payeeObjects[payeeItem.address];
+ payee.transactionID = result.insertId;
+ payee.trackPayment();
+ });
+ });
+ } else {
+ console.error("Unknown error from the wallet.");
+ }
+ });
+ }
+ }
+ });
+ });
+ });
+}
+
+function init() {
+ global.support.rpcWallet("store", [], function () {
+ });
+ if (global.config.allowBitcoin) {
+ determineBestExchange();
+ setInterval(updateXMRToCompletion, 90000);
+ setInterval(updateShapeshiftCompletion, 90000);
+ setInterval(determineBestExchange, 60000);
+ }
+ setInterval(function () {
+ global.support.rpcWallet("store", [], function () {
+ });
+ }, 60000);
+ console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money");
+ makePayments();
+}
+
+init();
\ No newline at end of file
diff --git a/lib/payments.js b/lib/payments.js
index 39cd88c..3113c54 100644
--- a/lib/payments.js
+++ b/lib/payments.js
@@ -1,700 +1,3 @@
"use strict";
-const shapeshift = require('shapeshift.io');
-const async = require("async");
-const debug = require("debug")("payments");
-const request = require('request-json');
-const range = require('range');
-const zmq = require('zmq');
-const sock = zmq.socket('pub');
-let hexChars = new RegExp("[0-9a-f]+");
-let bestExchange = global.config.payout.bestExchange;
-let xmrAPIClient = request.createClient('https://xmr.to/api/v1/xmr2btc/');
-let extraPaymentRound = false;
-let paymentTimer = null;
-sock.bindSync('tcp://127.0.0.1:3001');
-
-let shapeshiftQueue = async.queue(function (task, callback) {
- // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values..
- let amount = task.amount - task.fee;
- // Address is the destination address IN BTC.
- let address = task.address;
- // PaymentIDs are the paymentID's to flag as paid by this transaction.
- // Should be a massive list of ID's so we can bulk-update them, by merging them with 's.
- // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around.
- // Once there's enough funds, then we active txn
- // Do a wallet call to xfer.
- // Setup a monitor on the transaction
- async.waterfall([
- function (intCallback) {
- // Verify if the coin is active in ShapeShift first.
- shapeshift.coins(function (err, coinData) {
- if (err) {
- intCallback(err);
- } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") {
- intCallback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift.");
- } else {
- intCallback(null);
- }
- });
- },
- function (intCallback) {
- // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc.
- shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) {
- if (err) {
- intCallback(err);
- } else if (!marketInfo.hasOwnProperty("limit") || marketInfo.limit <= global.support.coinToDecimal(amount)) {
- intCallback("Not enough coin in shapeshift to process at this time.");
- } else if (!marketInfo.hasOwnProperty("min") || marketInfo.min >= global.support.coinToDecimal(amount)) {
- intCallback("Not enough coin to hit the shapeshift minimum deposits.");
- } else {
- intCallback(null, marketInfo);
- }
- });
- },
- function (marketInfo, intCallback) {
- // Validated there's enough coin. Time to make our dank txn.
- // Return:
- /*
- {
- "orderId": "cc49c556-e645-4c15-a943-d50a935274e4",
- "sAddress": "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6",
- "deposit": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e",
- "depositType": "XMR",
- "withdrawal": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu",
- "withdrawalType": "BTC",
- "public": null,
- "apiPubKey": "shapeshift",
- "returnAddress": "46XWBqE1iwsVxSDP1qDrxhE1XvsZV6eALG5LwnoMdjbT4GPdy2bZTb99kagzxp2MMjUamTYZ4WgvZdFadvMimTjvR6Gv8hL",
- "returnAddressType": "XMR"
- }
- Valid Statuses:
- "received"
- "complete"
- "error"
- "no_deposits"
- Complete State Information:
- {
- "status": "complete",
- "address": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e",
- "withdraw": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu",
- "incomingCoin": 3,
- "incomingType": "XMR",
- "outgoingCoin": "0.04186155",
- "outgoingType": "BTC",
- "transaction": "be9d97f6fc75262151f8f63e035c6ed638b9eb2a4e93fef43ea63124b045dbfb"
- }
- */
- shapeshift.shift(address, global.config.payout.shapeshiftPair, {returnAddress: global.config.pool.address}, function (err, returnData) {
- if (err) {
- intCallback(err);
- } else {
- global.mysql.query("INSERT INTO shapeshiftTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus) VALUES (?,?,?,?,?,?,?,?,?)",
- [returnData.orderId, returnData.sAddress, returnData.deposit, returnData.depositType, returnData.withdrawl, returnData.withdrawlType, returnData.returnAddress, returnData.returnAddressType, 'no_deposits']).then(function () {
- intCallback(null, marketInfo, returnData);
- }).catch(function (error) {
- intCallback(error);
- });
- }
- });
- },
- function (marketInfo, shapeshiftTxnData, intCallback) {
- // Make the payment to ShapeShift
- let paymentDetails = {
- destinations: [
- {
- amount: amount,
- address: shapeshiftTxnData.sAddress
- }
- ],
- priority: global.config.payout.priority,
- mixin: global.config.payout.mixIn,
- payment_id: shapeshiftTxnData.deposit
- };
- debug("Payment Details: " + JSON.stringify(paymentDetails));
- paymentQueue.push(paymentDetails, function (body) {
- if (body.fee && body.fee > 10) {
- intCallback(null, marketInfo, shapeshiftTxnData, body);
- } else {
- intCallback("Unknown error from the wallet.");
- }
- });
- },
- function (marketInfo, shapeshiftTxnData, body, intCallback) {
- // body.tx_hash = XMR transaction hash.
- // Need to add transaction.
- global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- [1, address, null, task.amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.support.decimalToCoin(marketInfo.minerFee), 1, global.support.decimalToCoin(marketInfo.rate), 'shapeshift', shapeshiftTxnData.orderId]).then(function (result) {
- intCallback(null, result.insertId);
- }).catch(function (error) {
- intCallback(error);
- });
- }
- ], function (err, result) {
- if (err) {
- console.error("Error processing shapeshift txn: " + JSON.stringify(err));
- callback(true);
- } else {
- // Need to fill out this data pronto!
- console.log("Processed ShapeShift transaction for: " + address + " Paid out: " + result + " payments in the db");
- callback(null, result);
- }
- });
-}, 2);
-
-let xmrToQueue = async.queue(function (task, callback) {
- // http://xmrto-api.readthedocs.io/en/latest/introduction.html
- // Documentation looks good!
- // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values..
- let amount = task.amount - task.fee;
- // Address is the destination address IN BTC.
- let address = task.address;
- // PaymentIDs are the paymentID's to flag as paid by this transaction.
- // Should be a massive list of ID's so we can bulk-update them, by merging them with 's.
- // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around.
- // Once there's enough funds, then we active txn
- // Do a wallet call to xfer.
- // Setup a monitor on the transaction
- async.waterfall([
- function (intCallback) {
- // Verify if XMR.to is ready to get to work.
- xmrAPIClient.get('order_parameter_query/', function (err, res, body) {
- if (err) {
- return intCallback(err);
- } else if (body.error_msg) {
- return intCallback(body.error_msg);
- } else {
- let amtOfBTC = ((amount / global.config.general.sigDivisor) * body.price).toPrecision(5);
- console.log("Attempting to pay: " + address + " Amount: " + amtOfBTC + " BTC or " + amount / global.config.general.sigDivisor + " XMR");
- console.log("Response from XMR.to: " + JSON.stringify(body));
- if (body.lower_limit >= amtOfBTC) {
- return intCallback("Not enough XMR to hit the minimum deposit");
- } else if (body.upper_limit <= amtOfBTC) {
- return intCallback("Too much XMR to pay out to xmr.to");
- } else {
- return intCallback(null, amtOfBTC);
- }
- }
- });
- },
- function (btcValue, intCallback) {
- // Validated there's enough coin. Time to make our dank txn.
- // Return:
- /*
- {
- "state": "TO_BE_CREATED",
- "btc_amount": ,
- "btc_dest_address": "",
- "uuid": ""
- }
- Valid Statuses:
- "TO_BE_CREATED"
- "UNPAID"
- "UNDERPAID"
- "PAID_UNCONFIRMED"
- "PAID"
- "BTC_SENT"
- "TIMED_OUT"
- "NOT_FOUND"
- // Create, then immediately update with the new information w/ a status call.
- */
- console.log("Amount of BTC to pay: " + btcValue);
- xmrAPIClient.post('order_create/', {
- btc_amount: btcValue,
- btc_dest_address: address
- }, function (err, res, body) {
- if (err) {
- return intCallback(err);
- } else if (body.error_msg) {
- return intCallback(body.error_msg);
- } else {
- return intCallback(null, body.uuid);
- }
- });
- },
- function (txnID, intCallback) {
- // This function only exists because xmr.to is a pretty little fucking princess.
- async.doUntil(function (xmrCallback) {
- xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) {
- if (err) {
- return intCallback(err);
- } else if (body.error_msg) {
- return intCallback(body.error_msg);
- } else {
- xmrCallback(null, body.state);
- }
- });
- },
- function (xmrCallback) {
- return xmrCallback !== "TO_BE_CREATED";
- },
- function () {
- intCallback(null, txnID);
- });
- },
- function (txnID, intCallback) {
- xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) {
- if (err) {
- return intCallback(err);
- } else if (body.error_msg) {
- return intCallback(body.error_msg);
- } else {
- console.log(JSON.stringify(body));
- global.mysql.query("INSERT INTO xmrtoTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus, amountDeposited, amountSent) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
- [txnID, body.xmr_receiving_address, body.xmr_required_payment_id, 'XMR', body.btc_dest_address, 'BTC', global.config.pool.address, 'XMR', body.state_str, global.support.decimalToCoin(body.xmr_amount_total), global.support.decimalToCoin(body.btc_amount)]).then(function () {
- return intCallback(null, body, global.support.decimalToCoin(body.xmr_amount_total));
- }).catch(function (error) {
- return intCallback(error);
- });
- }
- });
- },
- function (orderStatus, xmrDeposit, intCallback) {
- // Make the payment to ShapeShift
- let paymentDetails = {
- destinations: [
- {
- amount: xmrDeposit,
- address: orderStatus.xmr_receiving_address
- }
- ],
- priority: global.config.payout.priority,
- mixin: global.config.payout.mixIn,
- payment_id: orderStatus.xmr_required_payment_id
- };
- debug("Payment Details: " + JSON.stringify(paymentDetails));
- paymentQueue.push(paymentDetails, function (body) {
- if (body.fee && body.fee > 10) {
- return intCallback(null, orderStatus, body);
- } else {
- return intCallback("Unknown error from the wallet.");
- }
- });
- },
- function (orderStatus, body, intCallback) {
- // body.tx_hash = XMR transaction hash.
- // Need to add transaction.
- global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- [1, address, null, global.support.decimalToCoin(orderStatus.xmr_amount_total), body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1, global.support.decimalToCoin(orderStatus.xmr_price_btc), 'xmrto', orderStatus.uuid]).then(function (result) {
- return intCallback(null, result.insertId);
- }).catch(function (error) {
- return intCallback(error);
- });
- }
- ], function (err, result) {
- if (err) {
- console.error("Error processing XMRTo txn: " + JSON.stringify(err));
- return callback("Error!");
- } else {
- // Need to fill out this data pronto!
- console.log("Processed XMRTo transaction for: " + address + " Paid out: " + result + " payments in the db");
- return callback(null, result);
- }
- });
-}, 2);
-
-let paymentQueue = async.queue(function (paymentDetails, callback) {
- /*
- support JSON URI: http://10.0.0.2:28082/json_rpc Args: {"id":"0","jsonrpc":"2.0","method":"transfer","params":{"destinations":[{"amount":68130252045355,"address":"A2MSrn49ziBPJBh8ZNEhhbfyLMou6mao4C1F5TLGUatmUnCxZArDYkcbAnVkVEopWVeak2rKDrmc8JpoS7n5dvfN9YDPBTG"}],"mixin":4,"payment_id":"7e52c5266de9fede7fb3abc0cd88f937b38b51426f7b34ff99729d28ce4e1142"}} +1ms
- payments Payment made: {"id":"0","jsonrpc":"2.0","result":{"fee":40199391255,"tx_hash":"c418708643f72635edf522490bfb2cae9d42a6dc1df30dcde844862dfd88f5b3","tx_key":""}} +2s
- */
- if (paymentTimer !== null){
- clearInterval(paymentTimer);
- paymentTimer = null;
- }
- debug("Making payment based on: " + JSON.stringify(paymentDetails));
- let transferFunc = 'transfer';
- global.support.rpcWallet(transferFunc, paymentDetails, function (body) {
- debug("Payment made: " + JSON.stringify(body));
- if (body.hasOwnProperty('error')) {
- if (body.error.message === "not enough money"){
- console.error("Issue making payments, not enough money, will try later");
- if(!extraPaymentRound){
- setTimeout(function(){
- makePayments();
- }, global.config.payout.timerRetry * 60 * 1000);
- }
- extraPaymentRound = true;
- return callback(false);
- } else {
- console.error("Issue making payments" + JSON.stringify(body.error));
- console.error("Will not make more payments until the payment daemon is restarted!");
- //toAddress, subject, body
- global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment",
- "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) +
- ". Please investigate and restart the payment daemon as appropriate");
- return;
- }
- }
- if (paymentDetails.hasOwnProperty('payment_id')) {
- console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee");
- return callback(body.result);
- } else {
- if (transferFunc === 'transfer') {
- console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR");
- }
- let intCount = 0;
- paymentDetails.destinations.forEach(function (details) {
- console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR");
- intCount += 1;
- if (intCount === paymentDetails.destinations.length) {
- return callback(body.result);
- }
- });
- }
- });
-}, 1);
-
-paymentQueue.drain = function(){
- extraPaymentRound = false;
- if (global.config.payout.timer > 35791){
- console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows.");
- } else {
- paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000);
- }
- global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000));
- sock.send(['payments', 'complete']);
-};
-
-function updateShapeshiftCompletion() {
- global.mysql.query("SELECT * FROM shapeshiftTxn WHERE txnStatus NOT IN ('complete', 'error')").then(function (rows) {
- rows.forEach(function (row) {
- shapeshift.status(row.paymentID, function (err, status, returnData) {
- if (err) {
- return;
- }
- global.mysql.query("UPDATE shapeshiftTxn SET txnStatus = ? WHERE id = ?", [status, row.id]).then(function () {
- if (status === 'complete') {
- global.mysql.query("UPDATE shapeshiftTxn SET amountDeposited = ?, amountSent = ?, transactionHash = ? WHERE id = ?",
- [global.support.decimalToCoin(returnData.incomingCoin), global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), returnData.transaction, row.id]).then(function () {
- global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), row.id]);
- });
- } else if (status === 'error') {
- // Failed txn. Need to rollback and delete all related data. Here we go!
- global.mysql.query("DELETE FROM shapeshiftTxn WHERE id = ?", [row.id]);
- global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) {
- global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]);
- global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]);
- global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]);
- });
- console.error("Failed transaction from ShapeShift " + JSON.stringify(returnData));
- }
- });
- });
- });
- });
-}
-
-function updateXMRToCompletion() {
- global.mysql.query("SELECT * FROM xmrtoTxn WHERE txnStatus NOT IN ('PAID', 'TIMED_OUT', 'NOT_FOUND', 'BTC_SENT')").then(function (rows) {
- rows.forEach(function (row) {
- xmrAPIClient.post('order_status_query/', {uuid: row.id}, function (err, res, body) {
- if (err) {
- console.log("Error in getting order status: " + JSON.stringify(err));
- return;
- }
- if (body.error_msg) {
- console.log("Error in getting order status: " + body.error_msg);
- return;
- }
- global.mysql.query("UPDATE xmrtoTxn SET txnStatus = ? WHERE id = ?", [body.state, row.id]).then(function () {
- if (body.status === 'BTC_SENT') {
- global.mysql.query("UPDATE xmrtoTxn SET transactionHash = ? WHERE id = ?", [body.btc_transaction_id, row.id]).then(function () {
- global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(body.btc_amount), row.id]);
- });
- } else if (body.status === 'TIMED_OUT' || body.status === 'NOT_FOUND') {
- global.mysql.query("DELETE FROM xmrtoTxn WHERE id = ?", [row.id]);
- global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) {
- global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]);
- global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]);
- global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]);
- });
- console.error("Failed transaction from XMRto " + JSON.stringify(body));
- }
- });
- });
- });
- });
-}
-
-function determineBestExchange() {
- async.waterfall([
- function (callback) {
- // Verify if the coin is active in ShapeShift first.
- shapeshift.coins(function (err, coinData) {
- if (err) {
- return callback(err);
- } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") {
- return callback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift.");
- } else {
- return callback(null);
- }
- });
- },
- function (callback) {
- // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc.
- shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) {
- if (err) {
- return callback(err);
- } else if (!marketInfo.hasOwnProperty("rate")) {
- return callback("Shapeshift did not return the rate.");
- } else {
- return callback(null, global.support.bitcoinDecimalToCoin(marketInfo.rate));
- }
- });
- },
- function (ssValue, callback) {
- xmrAPIClient.get('order_parameter_query/', function (err, res, body) {
- console.log("XMR.to pricing body: " + JSON.stringify(body));
- if (err) {
- return callback(err);
- } else if (body.error_msg) {
- return callback(body.error_msg);
- } else {
- return callback(null, ssValue, global.support.bitcoinDecimalToCoin(body.price));
- }
- });
- }
- ], function (err, ssValue, xmrToValue) {
- if (err) {
- return console.error("Error processing exchange value: " + JSON.stringify(err));
- }
- debug("ShapeShift Value: " + global.support.bitcoinCoinToDecimal(ssValue) + " XMR.to Value: " + global.support.bitcoinCoinToDecimal(xmrToValue));
- if (ssValue >= xmrToValue) {
- console.log("ShapeShift is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(ssValue));
- bestExchange = 'shapeshift';
- global.mysql.query("UPDATE config SET item_value = 'shapeshift' where item='bestExchange'");
- global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [ssValue]);
- } else {
- console.log("XMR.to is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(xmrToValue));
- bestExchange = 'xmrto';
- global.mysql.query("UPDATE config SET item_value = 'xmrto' where item='bestExchange'");
- global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [xmrToValue]);
- }
- });
-}
-
-function Payee(amount, address, paymentID, bitcoin) {
- this.amount = amount;
- this.address = address;
- this.paymentID = paymentID;
- this.bitcoin = bitcoin;
- this.blockID = 0;
- this.poolType = '';
- this.transactionID = 0;
- this.sqlID = 0;
- if (paymentID === null) {
- this.id = address;
- } else {
- this.id = address + "." + paymentID;
- }
- this.fee = 0;
- this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount);
- this.setFeeAmount = function () {
- if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) {
- this.fee = this.baseFee;
- } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) {
- let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin));
- this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue);
- }
- this.fee = Math.floor(this.fee);
- };
-
- this.makePaymentWithID = function () {
- let paymentDetails = {
- destinations: [
- {
- amount: this.amount - this.fee,
- address: this.address
- }
- ],
- priority: global.config.payout.priority,
- mixin: global.config.payout.mixIn,
- payment_id: this.paymentID
- };
- let identifier = this.id;
- let amount = this.amount;
- let address = this.address;
- let paymentID = this.paymentID;
- let payee = this;
- debug("Payment Details: " + JSON.stringify(paymentDetails));
- paymentQueue.push(paymentDetails, function (body) {
- if (body.fee && body.fee > 10) {
- debug("Successful payment sent to: " + identifier);
- global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
- [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) {
- payee.transactionID = result.insertId;
- payee.trackPayment();
- });
- } else {
- console.error("Unknown error from the wallet.");
- }
- });
- };
-
- this.makePaymentAsIntegrated = function () {
- let paymentDetails = {
- destinations: [
- {
- amount: this.amount - this.fee,
- address: this.address
- }
- ],
- priority: global.config.payout.priority,
- mixin: global.config.payout.mixIn
- };
- let identifier = this.id;
- let amount = this.amount;
- let address = this.address;
- let payee = this;
-
- debug("Payment Details: " + JSON.stringify(paymentDetails));
- paymentQueue.push(paymentDetails, function (body) {
- if (body.fee && body.fee > 10) {
- debug("Successful payment sent to: " + identifier);
- global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)",
- [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) {
- payee.transactionID = result.insertId;
- payee.trackPayment();
- });
- } else {
- console.error("Unknown error from the wallet.");
- }
- });
- };
-
- this.makeBitcoinPayment = function () {
- let functionalData = {address: this.address, amount: this.amount, fee: this.fee};
- let payee = this;
- if (bestExchange === 'xmrto') {
- xmrToQueue.push(functionalData, function (err, transactionID) {
- if (err) {
- return console.error("Error processing payment for " + functionalData.address);
- }
- payee.transactionID = transactionID;
- payee.trackPayment();
- });
- } else {
- shapeshiftQueue.push(functionalData, function (err, transactionID) {
- if (err) {
- return console.error("Error processing payment for " + functionalData.address);
- }
- payee.transactionID = transactionID;
- payee.trackPayment();
- });
- }
- };
-
- this.trackPayment = function () {
- global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]);
- global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" +
- " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]);
- };
-}
-
-function makePayments() {
- global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) {
- console.log("Loaded all payees into the system for processing");
- let paymentDestinations = [];
- let totalAmount = 0;
- let roundCount = 0;
- let payeeList = [];
- let payeeObjects = {};
- rows.forEach(function (row) {
- debug("Starting round for: " + JSON.stringify(row));
- let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin);
- payeeObjects[row.payment_address] = payee;
- global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) {
- roundCount += 1;
- let threshold = 0;
- if (userRow.length !== 0) {
- threshold = userRow[0].payout_threshold;
- }
- payee.poolType = row.pool_type;
- payee.sqlID = row.id;
- if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) {
- debug("This is the fee address internal check for value");
- payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN);
- } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") {
- debug("Unable to pay fee address.");
- payee.amount = 0;
- }
- let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor);
- if (remainder !== 0) {
- payee.amount -= remainder;
- }
- if (payee.amount > threshold) {
- payee.setFeeAmount();
- if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) {
- debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
- paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address});
- totalAmount += payee.amount;
- payeeList.push(payee);
- } else if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length === 106 && (payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0))) {
- // Special code to handle integrated payment addresses. What a pain in the rear.
- // These are exchange addresses though, so they need to hit the exchange payout amount.
- debug("Adding " + payee.id + " to the list of people to pay (Integrated Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
- payee.makePaymentAsIntegrated();
- } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) {
- debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount));
- payee.makePaymentWithID();
- } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 1) {
- debug("Adding " + payee.id + " to the list of people to pay (Bitcoin Payout). Payee balance: " + global.support.coinToDecimal(payee.amount));
- payee.makeBitcoinPayment();
- }
- }
- debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows");
- if (roundCount === rows.length && paymentDestinations.length > 0) {
- while (paymentDestinations.length > 0) {
- let paymentDetails = {
- destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns),
- priority: global.config.payout.priority,
- mixin: global.config.payout.mixIn
- };
- console.log("Paying out: " + paymentDetails.destinations.length + " people");
- paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line
- // This is the only section that could potentially contain multiple txns. Lets do this safely eh?
- if (body.fee && body.fee > 10) {
- debug("Made it to the SQL insert for transactions");
- let totalAmount = 0;
- paymentDetails.destinations.forEach(function (payeeItem) {
- totalAmount += payeeObjects[payeeItem.address].amount;
- totalAmount += payeeObjects[payeeItem.address].fee;
- });
- global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
- [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) {
- paymentDetails.destinations.forEach(function (payeeItem) {
- payee = payeeObjects[payeeItem.address];
- payee.transactionID = result.insertId;
- payee.trackPayment();
- });
- });
- } else {
- console.error("Unknown error from the wallet.");
- }
- });
- }
- }
- });
- });
- });
-}
-
-function init() {
- global.support.rpcWallet("store", [], function () {
- });
- if (global.config.allowBitcoin) {
- determineBestExchange();
- setInterval(updateXMRToCompletion, 90000);
- setInterval(updateShapeshiftCompletion, 90000);
- setInterval(determineBestExchange, 60000);
- }
- setInterval(function () {
- global.support.rpcWallet("store", [], function () {
- });
- }, 60000);
- console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money");
- makePayments();
-}
-
-init();
+require(global.config.coin.paymentFile);
\ No newline at end of file
diff --git a/lib/pool.js b/lib/pool.js
index 41572f5..b0e1e46 100644
--- a/lib/pool.js
+++ b/lib/pool.js
@@ -156,7 +156,7 @@ function checkAliveMiners() {
function templateUpdate(repeating) {
global.coinFuncs.getBlockTemplate(global.config.pool.address, function (rpcResponse) {
- if (rpcResponse) {
+ if (rpcResponse && typeof rpcResponse.result !== 'undefined') {
rpcResponse = rpcResponse.result;
let buffer = new Buffer(rpcResponse.blocktemplate_blob, 'hex');
let new_hash = new Buffer(32);
@@ -174,6 +174,8 @@ function templateUpdate(repeating) {
if (repeating !== false) {
setTimeout(templateUpdate, 300);
}
+ } else {
+ setTimeout(templateUpdate, 300, false);
}
});
}
@@ -297,7 +299,7 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer
if (global.coinFuncs.validateAddress(this.address)) {
this.bitcoin = 0;
- } else if (btcValidator.validate(this.address) && global.config.general.allowBitcoin) {
+ } else if (btcValidator.validate(this.address) && global.config.general.allowBitcoin && global.coinFuncs.supportsAutoExchange) {
this.bitcoin = 1;
} else if (btcValidator.validate(this.address)) {
this.error = "This pool does not allow payouts to bitcoin.";
@@ -620,6 +622,7 @@ function processShare(miner, job, blockTemplate, params) {
if (hashDiff.ge(blockTemplate.difficulty)) {
// Submit block to the RPC Daemon.
+ // Todo: Implement within the coins/.js file.
global.support.rpcDaemon('submitblock', [shareBuffer.toString('hex')], function (rpcResult) {
if (rpcResult.error) {
// Did not manage to submit a block. Log and continue on.
diff --git a/lib/worker.js b/lib/worker.js
index 8e31caf..8714ca9 100644
--- a/lib/worker.js
+++ b/lib/worker.js
@@ -2,12 +2,9 @@
const debug = require("debug")("worker");
const async = require("async");
const sprintf = require("sprintf-js").sprintf;
-const zmq = require('zmq');
-const sock = zmq.socket('pub');
let threadName = "Worker Server ";
let cycleCount = 0;
-sock.bindSync('tcp://127.0.0.1:3000');
let lastBlockHash = null;
function updateShareStats() {
@@ -19,7 +16,10 @@ function updateShareStats() {
let activeAddresses = [];
async.waterfall([
function (callback) {
- global.coinFuncs.getLastBlockHeader(function (body) {
+ global.coinFuncs.getLastBlockHeader(function (err, body) {
+ if (err !== null){
+ return callback(err, "Invalid block header");
+ }
callback(null, body.height + 1);
});
},
@@ -242,7 +242,7 @@ function updateShareStats() {
Object.keys(identifiers).forEach(function (key) {
cache_updates[key + '_identifiers'] = identifiers[key];
});
- cache_updates['minerList'] = globalMinerList;
+ cache_updates.minerList = globalMinerList;
global.database.bulkSetCache(cache_updates);
callback(null);
});
@@ -251,9 +251,7 @@ function updateShareStats() {
cycleCount += 1;
if (cycleCount === 6){
cycleCount = 0;
- sock.send(['miner_hash_graph', JSON.stringify(activeAddresses)]);
}
- sock.send(['miner_hash_stats', JSON.stringify(activeAddresses)]);
});
setTimeout(updateShareStats, 10000);
}
@@ -345,7 +343,6 @@ function updatePoolStats(poolType) {
totalPayments: result[7] || 0,
roundHashes: result[8] || 0
});
- sock.send(['pool_stats', poolType]);
});
}
@@ -434,7 +431,11 @@ function updatePoolInformation() {
}
function updateBlockHeader() {
+ // Todo: Implement within the coins/.js file.
global.support.rpcDaemon('getlastblockheader', [], function (body) {
+ if (typeof body.error !== 'undefined'){
+ return console.error(`Issue getting last block header: ${JSON.stringify(body)}`);
+ }
if (body.result && body.result.block_header.hash !== lastBlockHash) {
lastBlockHash = body.result.block_header.hash;
global.database.setCache('networkBlockInfo', {
@@ -444,13 +445,6 @@ function updateBlockHeader() {
value: body.result.block_header.reward,
ts: body.result.block_header.timestamp
});
- sock.send(['network_block_info', JSON.stringify({
- difficulty: body.result.block_header.difficulty,
- hash: body.result.block_header.hash,
- height: body.result.block_header.height,
- value: body.result.block_header.reward,
- ts: body.result.block_header.timestamp
- })]);
} else if (body.result.block_header.hash === lastBlockHash) {
console.log("Block headers identical to historical header. Ignoring");
} else {
@@ -462,6 +456,7 @@ function updateBlockHeader() {
function updateWalletStats() {
async.waterfall([
function (callback) {
+ // Todo: Implement within the coins/.js file.
global.support.rpcWallet('getbalance', [], function (body) {
if (body.result) {
return callback(null, {
@@ -475,10 +470,14 @@ function updateWalletStats() {
});
},
function (state, callback) {
+ // Todo: Implement within the coins/.js file.
global.support.rpcWallet('getheight', [], function (body) {
if (body.result) {
state.height = body.result.height;
return callback(null, state);
+ } else if (typeof body.error !== 'undefined' && body.error.message === 'Method not found') {
+ state.height = 0;
+ return callback(null, state);
} else {
return callback(true, "Unable to get current wallet height");
}
@@ -506,8 +505,12 @@ function updateWalletStats() {
}
function monitorNodes() {
- global.mysql.query("SELECT blockID FROM pools WHERE last_checkin > date_sub(now(), interval 30 minute)").then(function (rows) {
- global.coinFuncs.getLastBlockHeader(function (block) {
+ global.mysql.query("SELECT blockID, hostname, ip FROM pools WHERE last_checkin > date_sub(now(), interval 30 minute)").then(function (rows) {
+ global.coinFuncs.getLastBlockHeader(function (err, block) {
+ if (err !== null){
+ console.error("Issue in getting block header. Skipping node monitor");
+ return;
+ }
rows.forEach(function (row) {
if (row.blockID < block.height - 3) {
global.support.sendEmail(global.config.general.adminEmail, "Pool server behind in blocks", "The pool server: "+row.hostname+" with IP: "+row.ip+" is "+block.height - row.blockID+ " blocks behind");
diff --git a/sql_sync/config_entries.json b/sql_sync/config_entries.json
index fbd2f2d..8d320da 100644
--- a/sql_sync/config_entries.json
+++ b/sql_sync/config_entries.json
@@ -558,5 +558,21 @@
"item_value": "0",
"item_type": "int",
"Item_desc": "Payout priority setting. 0 = use default (4x fee); 1 = low prio (1x fee)"
+ },
+ {
+ "id": 76,
+ "module": "payout",
+ "item": "fee",
+ "item_value": "10000000000",
+ "item_type": "int",
+ "Item_desc": "Atomic units of coin to use ass a fee"
+ },
+ {
+ "id": 77,
+ "module": "payout",
+ "item": "unlock_time",
+ "item_value": "0",
+ "item_type": "int",
+ "Item_desc": "Number of blocks assumed before the payout unlocks."
}
]