Update blockUnlocker.js
This commit is contained in:
@@ -8,21 +8,21 @@
|
||||
// Load required modules
|
||||
let async = require('async');
|
||||
|
||||
let apiInterfaces = require('./apiInterfaces.js')(config.daemon, config.wallet, config.api)
|
||||
let notifications = require('./notifications.js')
|
||||
let utils = require('./utils.js')
|
||||
let apiInterfaces = require('./apiInterfaces.js')(config.daemon, config.wallet, config.api);
|
||||
let notifications = require('./notifications.js');
|
||||
let utils = require('./utils.js');
|
||||
|
||||
let slushMiningEnabled = config.poolServer.slushMining && config.poolServer.slushMining.enabled
|
||||
let slushMiningEnabled = config.poolServer.slushMining && config.poolServer.slushMining.enabled;
|
||||
|
||||
// Initialize log system
|
||||
let logSystem = 'unlocker'
|
||||
require('./exceptionWriter.js')(logSystem)
|
||||
let logSystem = 'unlocker';
|
||||
require('./exceptionWriter.js')(logSystem);
|
||||
|
||||
/**
|
||||
* Run block unlocker
|
||||
**/
|
||||
|
||||
log('info', logSystem, 'Started')
|
||||
log('info', logSystem, 'Started');
|
||||
|
||||
function runInterval () {
|
||||
async.waterfall([
|
||||
@@ -31,20 +31,19 @@ function runInterval () {
|
||||
function (callback) {
|
||||
redisClient.zrange(config.coin + ':blocks:candidates', 0, -1, 'WITHSCORES', function (error, results) {
|
||||
if (error) {
|
||||
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error])
|
||||
callback(true)
|
||||
return
|
||||
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
if (results.length === 0) {
|
||||
log('info', logSystem, 'No blocks candidates in redis')
|
||||
callback(true)
|
||||
return
|
||||
log('info', logSystem, 'No blocks candidates in redis');
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let blocks = []
|
||||
|
||||
let blocks = [];
|
||||
for (let i = 0; i < results.length; i += 2) {
|
||||
let parts = results[i].split(':')
|
||||
let parts = results[i].split(':');
|
||||
blocks.push({
|
||||
serialized: results[i],
|
||||
height: parseInt(results[i + 1]),
|
||||
@@ -55,66 +54,63 @@ function runInterval () {
|
||||
difficulty: parts[4],
|
||||
shares: parts[5],
|
||||
score: parts.length >= 7 ? parts[6] : parts[5]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
callback(null, blocks)
|
||||
callback(null, blocks);
|
||||
})
|
||||
},
|
||||
|
||||
// Check if blocks are orphaned
|
||||
function (blocks, callback) {
|
||||
async.filter(blocks, function (block, mapCback) {
|
||||
let daemonType = config.daemonType ? config.daemonType.toLowerCase() : "default"
|
||||
let blockHeight = ((daemonType === "forknote" || daemonType === "bytecoin") && config.blockUnlocker.fixBlockHeightRPC) ? block.height + 1 : block.height
|
||||
let rpcMethod = config.blockUnlocker.useFirstVout ? 'getblock' : 'getblockheaderbyheight'
|
||||
let daemonType = config.daemonType ? config.daemonType.toLowerCase() : "default";
|
||||
let blockHeight = ((daemonType === "forknote" || daemonType === "bytecoin") && config.blockUnlocker.fixBlockHeightRPC) ? block.height + 1 : block.height;
|
||||
let rpcMethod = config.blockUnlocker.useFirstVout ? 'getblock' : 'getblockheaderbyheight';
|
||||
apiInterfaces.rpcDaemon(rpcMethod, {
|
||||
height: blockHeight
|
||||
}, function (error, result) {
|
||||
if (error) {
|
||||
log('error', logSystem, 'Error with %s RPC request for block %s - %j', [rpcMethod, block.serialized, error])
|
||||
block.unlocked = false
|
||||
mapCback()
|
||||
return
|
||||
log('error', logSystem, 'Error with %s RPC request for block %s - %j', [rpcMethod, block.serialized, error]);
|
||||
block.unlocked = false;
|
||||
mapCback();
|
||||
return;
|
||||
}
|
||||
if (!result.block_header) {
|
||||
log('error', logSystem, 'Error with getblockheaderbyheight RPC request for block %s - %j', [block.serialized, error])
|
||||
block.unlocked = false
|
||||
mapCback()
|
||||
return
|
||||
log('error', logSystem, 'Error with getblockheaderbyheight RPC request for block %s - %j', [block.serialized, error]);
|
||||
block.unlocked = false;
|
||||
mapCback();
|
||||
return;
|
||||
}
|
||||
let blockHeader = result.block_header
|
||||
block.orphaned = blockHeader.hash === block.hash ? 0 : 1
|
||||
block.unlocked = blockHeader.depth >= config.blockUnlocker.depth
|
||||
block.reward = blockHeader.reward
|
||||
let blockHeader = result.block_header;
|
||||
block.orphaned = blockHeader.hash === block.hash ? 0 : 1;
|
||||
block.unlocked = blockHeader.depth >= config.blockUnlocker.depth;
|
||||
block.reward = blockHeader.reward;
|
||||
if (config.blockUnlocker.useFirstVout) {
|
||||
let vout = JSON.parse(result.json)
|
||||
.miner_tx.vout
|
||||
let vout = JSON.parse(result.json).miner_tx.vout;
|
||||
if (!vout.length) {
|
||||
log('error', logSystem, 'Error: tx at height %s has no vouts!', [blockHeight])
|
||||
block.unlocked = false
|
||||
mapCback()
|
||||
return
|
||||
log('error', logSystem, 'Error: tx at height %s has no vouts!', [blockHeight]);
|
||||
block.unlocked = false;
|
||||
mapCback();
|
||||
return;
|
||||
}
|
||||
block.reward = vout[0].amount
|
||||
block.reward = vout[0].amount;
|
||||
} else {
|
||||
block.reward = blockHeader.reward
|
||||
block.reward = blockHeader.reward;
|
||||
}
|
||||
if (config.blockUnlocker.networkFee) {
|
||||
let networkFeePercent = config.blockUnlocker.networkFee / 100
|
||||
block.reward = block.reward - (block.reward * networkFeePercent)
|
||||
let networkFeePercent = config.blockUnlocker.networkFee / 100;
|
||||
block.reward = block.reward - (block.reward * networkFeePercent);
|
||||
}
|
||||
mapCback(block.unlocked)
|
||||
mapCback(block.unlocked);
|
||||
})
|
||||
}, function (unlockedBlocks) {
|
||||
|
||||
if (unlockedBlocks.length === 0) {
|
||||
log('info', logSystem, 'No pending blocks are unlocked yet (%d pending)', [blocks.length])
|
||||
callback(true)
|
||||
return
|
||||
log('info', logSystem, 'No pending blocks are unlocked yet (%d pending)', [blocks.length]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, unlockedBlocks)
|
||||
callback(null, unlockedBlocks);
|
||||
})
|
||||
},
|
||||
|
||||
@@ -122,39 +118,40 @@ function runInterval () {
|
||||
function (blocks, callback) {
|
||||
|
||||
let redisCommands = blocks.map(function (block) {
|
||||
if (block.rewardType === 'prop')
|
||||
return ['hgetall', config.coin + ':scores:prop:round' + block.height]
|
||||
else
|
||||
return ['hgetall', config.coin + ':scores:solo:round' + block.height]
|
||||
if (block.rewardType === 'prop') {
|
||||
return ['hgetall', config.coin + ':scores:prop:round' + block.height];
|
||||
} else {
|
||||
return ['hgetall', config.coin + ':scores:solo:round' + block.height];
|
||||
}
|
||||
})
|
||||
|
||||
redisClient.multi(redisCommands)
|
||||
.exec(function (error, replies) {
|
||||
if (error) {
|
||||
log('error', logSystem, 'Error with getting round shares from redis %j', [error])
|
||||
callback(true)
|
||||
return
|
||||
log('error', logSystem, 'Error with getting round shares from redis %j', [error]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < replies.length; i++) {
|
||||
let workerScores = replies[i]
|
||||
blocks[i].workerScores = workerScores
|
||||
let workerScores = replies[i];
|
||||
blocks[i].workerScores = workerScores;
|
||||
}
|
||||
callback(null, blocks)
|
||||
callback(null, blocks);
|
||||
})
|
||||
},
|
||||
|
||||
// Handle orphaned blocks
|
||||
function (blocks, callback) {
|
||||
let orphanCommands = []
|
||||
|
||||
let orphanCommands = [];
|
||||
blocks.forEach(function (block) {
|
||||
if (!block.orphaned) return
|
||||
|
||||
orphanCommands.push(['del', config.coin + ':scores:solo:round' + block.height])
|
||||
orphanCommands.push(['del', config.coin + ':scores:prop:round' + block.height])
|
||||
orphanCommands.push(['del', config.coin + ':shares_actual:solo:round' + block.height])
|
||||
orphanCommands.push(['del', config.coin + ':shares_actual:prop:round' + block.height])
|
||||
orphanCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized])
|
||||
if (!block.orphaned) {
|
||||
return;
|
||||
}
|
||||
orphanCommands.push(['del', config.coin + ':scores:solo:round' + block.height]);
|
||||
orphanCommands.push(['del', config.coin + ':scores:prop:round' + block.height]);
|
||||
orphanCommands.push(['del', config.coin + ':shares_actual:solo:round' + block.height]);
|
||||
orphanCommands.push(['del', config.coin + ':shares_actual:prop:round' + block.height]);
|
||||
orphanCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
|
||||
orphanCommands.push(['zadd', config.coin + ':blocks:matured', block.height, [
|
||||
block.rewardType,
|
||||
block.login,
|
||||
@@ -163,13 +160,12 @@ function runInterval () {
|
||||
block.difficulty,
|
||||
block.shares,
|
||||
block.orphaned
|
||||
].join(':')])
|
||||
].join(':')]);
|
||||
|
||||
if (block.workerScores && !slushMiningEnabled) {
|
||||
let workerScores = block.workerScores
|
||||
Object.keys(workerScores)
|
||||
.forEach(function (worker) {
|
||||
orphanCommands.push(['hincrby', config.coin + ':scores:roundCurrent', worker, workerScores[worker]])
|
||||
let workerScores = block.workerScores;
|
||||
Object.keys(workerScores).forEach(function (worker) {
|
||||
orphanCommands.push(['hincrby', config.coin + ':scores:roundCurrent', worker, workerScores[worker]]);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -184,34 +180,35 @@ function runInterval () {
|
||||
})
|
||||
|
||||
if (orphanCommands.length > 0) {
|
||||
redisClient.multi(orphanCommands)
|
||||
.exec(function (error, replies) {
|
||||
redisClient.multi(orphanCommands).exec(function (error, replies) {
|
||||
if (error) {
|
||||
log('error', logSystem, 'Error with cleaning up data in redis for orphan block(s) %j', [error])
|
||||
callback(true)
|
||||
return
|
||||
log('error', logSystem, 'Error with cleaning up data in redis for orphan block(s) %j', [error]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
callback(null, blocks)
|
||||
callback(null, blocks);
|
||||
})
|
||||
} else {
|
||||
callback(null, blocks)
|
||||
callback(null, blocks);
|
||||
}
|
||||
},
|
||||
|
||||
// Handle unlocked blocks
|
||||
function (blocks, callback) {
|
||||
let unlockedBlocksCommands = []
|
||||
let payments = {}
|
||||
let totalBlocksUnlocked = 0
|
||||
let unlockedBlocksCommands = [];
|
||||
let payments = {};
|
||||
let totalBlocksUnlocked = 0;
|
||||
blocks.forEach(function (block) {
|
||||
if (block.orphaned) return
|
||||
totalBlocksUnlocked++
|
||||
if (block.orphaned) {
|
||||
return;
|
||||
}
|
||||
totalBlocksUnlocked++;
|
||||
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':scores:solo:round' + block.height])
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':scores:prop:round' + block.height])
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':shares_actual:solo:round' + block.height])
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':shares_actual:prop:round' + block.height])
|
||||
unlockedBlocksCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized])
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':scores:solo:round' + block.height]);
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':scores:prop:round' + block.height]);
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':shares_actual:solo:round' + block.height]);
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':shares_actual:prop:round' + block.height]);
|
||||
unlockedBlocksCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
|
||||
unlockedBlocksCommands.push(['zadd', config.coin + ':blocks:matured', block.height, [
|
||||
block.rewardType,
|
||||
block.login,
|
||||
@@ -221,50 +218,47 @@ function runInterval () {
|
||||
block.shares,
|
||||
block.orphaned,
|
||||
block.reward
|
||||
].join(':')])
|
||||
].join(':')]);
|
||||
|
||||
let feePercent = (config.blockUnlocker.poolFee > 0 ? config.blockUnlocker.poolFee : 0) / 100
|
||||
if (block.rewardType === 'solo')
|
||||
feePercent = (config.blockUnlocker.soloFee >= 0 ? config.blockUnlocker.soloFee : (config.blockUnlocker.poolFee > 0 ? config.blockUnlocker.poolFee : 0)) / 100
|
||||
|
||||
if (Object.keys(donations)
|
||||
.length) {
|
||||
let feePercent = (config.blockUnlocker.poolFee > 0 ? config.blockUnlocker.poolFee : 0) / 100;
|
||||
if (block.rewardType === 'solo') {
|
||||
feePercent = (config.blockUnlocker.soloFee >= 0 ? config.blockUnlocker.soloFee : (config.blockUnlocker.poolFee > 0 ? config.blockUnlocker.poolFee : 0)) / 100;
|
||||
}
|
||||
if (Object.keys(donations).length) {
|
||||
for (let wallet in donations) {
|
||||
let percent = donations[wallet] / 100
|
||||
feePercent += percent
|
||||
payments[wallet] = Math.round(block.reward * percent)
|
||||
log('info', logSystem, 'Block %d donation to %s as %d percent of reward: %d', [block.height, wallet, percent, payments[wallet]])
|
||||
let percent = donations[wallet] / 100;
|
||||
feePercent += percent;
|
||||
payments[wallet] = Math.round(block.reward * percent);
|
||||
log('info', logSystem, 'Block %d donation to %s as %d percent of reward: %d', [block.height, wallet, percent, payments[wallet]]);
|
||||
}
|
||||
}
|
||||
|
||||
let reward = 0
|
||||
let finderReward = 0
|
||||
|
||||
let reward = 0;
|
||||
let finderReward = 0;
|
||||
if (block.rewardType === 'solo') {
|
||||
reward = Math.round(block.reward - (block.reward * feePercent))
|
||||
log('info', logSystem, 'Unlocked SOLO block %d with reward %d and donation fee %d. Miners reward: %d', [block.height, block.reward, feePercent, reward])
|
||||
reward = Math.round(block.reward - (block.reward * feePercent));
|
||||
log('info', logSystem, 'Unlocked SOLO block %d with reward %d and donation fee %d. Miners reward: %d', [block.height, block.reward, feePercent, reward]);
|
||||
} else {
|
||||
let finderPercent = (config.blockUnlocker.finderReward > 0 ? config.blockUnlocker.finderReward : 0) / 100
|
||||
finderReward = Math.round(block.reward * finderPercent)
|
||||
reward = Math.round(block.reward - (block.reward * (feePercent + finderPercent)))
|
||||
log('info', logSystem, 'Unlocked PROP block %d with reward %d, finders fee %d, and donation fee %d. Miners reward: %d Finders Reward: %d', [block.height, block.reward, finderPercent, feePercent, reward, finderReward])
|
||||
let finderPercent = (config.blockUnlocker.finderReward > 0 ? config.blockUnlocker.finderReward : 0) / 100;
|
||||
finderReward = Math.round(block.reward * finderPercent);
|
||||
reward = Math.round(block.reward - (block.reward * (feePercent + finderPercent)));
|
||||
log('info', logSystem, 'Unlocked PROP block %d with reward %d, finders fee %d, and donation fee %d. Miners reward: %d Finders Reward: %d', [block.height, block.reward, finderPercent, feePercent, reward, finderReward]);
|
||||
}
|
||||
|
||||
if (block.workerScores) {
|
||||
let totalScore = parseFloat(block.score)
|
||||
let totalScore = parseFloat(block.score);
|
||||
//deal with solo block
|
||||
if (block.rewardType === 'solo') {
|
||||
let worker = block.login
|
||||
payments[worker] = (payments[worker] || 0) + reward
|
||||
log('info', logSystem, 'SOLO Block %d payment to %s for %d%% of total block score: %d', [block.height, worker, 100, payments[worker]])
|
||||
let worker = block.login;
|
||||
payments[worker] = (payments[worker] || 0) + reward;
|
||||
log('info', logSystem, 'SOLO Block %d payment to %s for %d%% of total block score: %d', [block.height, worker, 100, payments[worker]]);
|
||||
} else {
|
||||
Object.keys(block.workerScores)
|
||||
.forEach(function (worker) {
|
||||
let percent = block.workerScores[worker] / totalScore
|
||||
let workerReward = Math.round(reward * percent)
|
||||
payments[worker] = block.login === worker ? (payments[worker] || 0) + (workerReward + finderReward) : (payments[worker] || 0) + workerReward
|
||||
log('info', logSystem, 'PROP Block %d payment to %s for %d%% of total block score: %d', [block.height, worker, percent * 100, payments[worker]])
|
||||
})
|
||||
Object.keys(block.workerScores).forEach(function (worker) {
|
||||
let percent = block.workerScores[worker] / totalScore;
|
||||
let workerReward = Math.round(reward * percent);
|
||||
payments[worker] = block.login === worker ? (payments[worker] || 0) + (workerReward + finderReward) : (payments[worker] || 0) + workerReward;
|
||||
log('info', logSystem, 'PROP Block %d payment to %s for %d%% of total block score: %d', [block.height, worker, percent * 100, payments[worker]]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,40 +270,39 @@ function runInterval () {
|
||||
'DIFFICULTY': block.difficulty,
|
||||
'SHARES': block.shares,
|
||||
'EFFORT': Math.round(block.shares / block.difficulty * 100) + '%'
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
for (let worker in payments) {
|
||||
let amount = parseInt(payments[worker])
|
||||
let amount = parseInt(payments[worker]);
|
||||
if (amount <= 0) {
|
||||
delete payments[worker]
|
||||
continue
|
||||
delete payments[worker];
|
||||
continue;
|
||||
}
|
||||
unlockedBlocksCommands.push(['hincrby', `${config.coin}:workers:${worker}`, 'balance', amount])
|
||||
unlockedBlocksCommands.push(['hincrby', `${config.coin}:workers:${worker}`, 'balance', amount]);
|
||||
}
|
||||
|
||||
if (unlockedBlocksCommands.length === 0) {
|
||||
log('info', logSystem, 'No unlocked blocks yet (%d pending)', [blocks.length])
|
||||
callback(true)
|
||||
return
|
||||
log('info', logSystem, 'No unlocked blocks yet (%d pending)', [blocks.length]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
redisClient.multi(unlockedBlocksCommands)
|
||||
.exec(function (error, replies) {
|
||||
if (error) {
|
||||
log('error', logSystem, 'Error with unlocking blocks %j', [error])
|
||||
callback(true)
|
||||
return
|
||||
log('error', logSystem, 'Error with unlocking blocks %j', [error]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
log('info', logSystem, 'Unlocked %d blocks and update balances for %d workers', [totalBlocksUnlocked, Object.keys(payments)
|
||||
.length
|
||||
])
|
||||
callback(null)
|
||||
log('info', logSystem, 'Unlocked %d blocks and update balances for %d workers', [totalBlocksUnlocked, Object.keys(payments).length
|
||||
]);
|
||||
callback(null);
|
||||
})
|
||||
}
|
||||
], function (error, result) {
|
||||
setTimeout(runInterval, config.blockUnlocker.interval * 1000)
|
||||
setTimeout(runInterval, config.blockUnlocker.interval * 1000);
|
||||
})
|
||||
}
|
||||
|
||||
runInterval()
|
||||
runInterval();
|
||||
|
||||
Reference in New Issue
Block a user