2022-01-15 11:31:38 -06:00
/ * *
* Cryptonote Node . JS Pool
* https : //github.com/dvandal/cryptonote-nodejs-pool
*
* Pool TCP daemon
* * /
// Load required modules
let fs = require ( 'fs' ) ;
let net = require ( 'net' ) ;
let tls = require ( 'tls' ) ;
let async = require ( 'async' ) ;
let bignum = require ( 'bignum' ) ;
let socketMap = new Set ( )
let apiInterfaces = require ( './apiInterfaces.js' ) ( config . daemon , config . wallet , config . api ) ;
let notifications = require ( './notifications.js' ) ;
let utils = require ( './utils.js' ) ;
2026-04-02 12:41:33 +01:00
// Lethean uses ProgPoWZ via zano-node-util — no separate CryptoNight hashing needed
let cnHashing = null ;
2022-01-15 11:31:38 -06:00
// Set nonce pattern - must exactly be 8 hex chars
let noncePattern = new RegExp ( "^[0-9A-Fa-f]{16}$" ) ;
// Set redis database cleanup interval
let cleanupInterval = config . redis . cleanupInterval && config . redis . cleanupInterval > 0 ? config . redis . cleanupInterval : 15 ;
let fallBackCoin = typeof config . poolServer . fallBackCoin !== 'undefined' && config . poolServer . fallBackCoin ? config . poolServer . fallBackCoin : 0
// Initialize log system
let logSystem = 'pool' ;
require ( './exceptionWriter.js' ) ( logSystem ) ;
let threadId = '(Thread ' + process . env . forkId + ') ' ;
let log = function ( severity , system , text , data ) {
global . log ( severity , system , threadId + text , data ) ;
} ;
// Set cryptonight algorithm
let cnAlgorithm = config . cnAlgorithm || "cryptonight" ;
let cnVariant = config . cnVariant || 0 ;
let cnBlobType = config . cnBlobType || 0 ;
2026-04-02 12:41:33 +01:00
// ProgPoWZ: hashing done through zano-node-util get_pow_hash — no CryptoNight needed
let cryptoNight = null ;
2022-01-15 11:31:38 -06:00
// Set instance id
let instanceId = utils . instanceId ( ) ;
// Pool variables
let poolStarted = false ;
let connectedMiners = { } ;
// Get merged mining tag reseved space size
let POOL _NONCE _SIZE = 16 + 1 ; // +1 for old XMR/new TRTL bugs
let EXTRA _NONCE _TEMPLATE = "02" + POOL _NONCE _SIZE . toString ( 16 ) + "00" . repeat ( POOL _NONCE _SIZE ) ;
let POOL _NONCE _MM _SIZE = POOL _NONCE _SIZE + utils . cnUtil . get _merged _mining _nonce _size ( ) ;
let EXTRA _NONCE _NO _CHILD _TEMPLATE = "02" + POOL _NONCE _MM _SIZE . toString ( 16 ) + "00" . repeat ( POOL _NONCE _MM _SIZE ) ;
let mergedMining = config . poolServer . mergedMining && ( Array . isArray ( config . childPools ) && config . childPools . length > 0 )
function randomIntFromInterval ( min , max ) {
return Math . floor ( Math . random ( ) * ( max - min + 1 ) + min ) ;
}
// Pool settings
let shareTrustEnabled = config . poolServer . shareTrust && config . poolServer . shareTrust . enabled ;
let shareTrustStepFloat = shareTrustEnabled ? config . poolServer . shareTrust . stepDown / 100 : 0 ;
let shareTrustMinFloat = shareTrustEnabled ? config . poolServer . shareTrust . min / 100 : 0 ;
let banningEnabled = config . poolServer . banning && config . poolServer . banning . enabled ;
let bannedIPs = { } ;
let perIPStats = { } ;
let slushMiningEnabled = config . poolServer . slushMining && config . poolServer . slushMining . enabled ;
if ( ! config . poolServer . paymentId ) config . poolServer . paymentId = { } ;
if ( ! config . poolServer . paymentId . addressSeparator ) config . poolServer . paymentId . addressSeparator = "+" ;
if ( config . poolServer . paymentId . validation == null ) config . poolServer . paymentId . validation = true ;
if ( config . poolServer . paymentId . ban == null ) config . poolServer . paymentId . ban = false ;
if ( config . poolServer . paymentId . validations == null ) {
config . poolServer . paymentId . validations = [ ]
config . poolServer . paymentId . validation = false ;
}
config . isRandomX = config . isRandomX || false
let previousOffset = config . previousOffset || 7
let offset = config . offset || 2
config . daemonType = config . daemonType || 'default'
if ( config . daemonType === 'bytecoin' )
{
previousOffset = config . previousOffset || 3
offset = config . offset || 3
}
function Create2DArray ( rows ) {
let arr = [ ] ;
for ( let i = 0 ; i < rows ; i ++ ) {
arr [ i ] = [ ] ;
}
return arr ;
}
if ( mergedMining )
config . childPools = config . childPools . filter ( pool => pool . enabled )
// Block templates
let validBlockTemplates = mergedMining ? Create2DArray ( config . childPools . length ) : Create2DArray ( 1 ) ;
let currentBlockTemplate = [ ] ;
// Child Block templates
let currentChildBlockTemplate = new Array ( mergedMining ? config . childPools . length : 1 ) ;
// Difficulty buffer
let diff1 = bignum ( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' , 16 ) ;
/ * *
* Convert buffer to byte array
* * /
Buffer . prototype . toByteArray = function ( ) {
return Array . prototype . slice . call ( this , 0 ) ;
} ;
/ * *
* Periodical updaters
* * /
// Variable difficulty retarget
setInterval ( function ( ) {
let now = Date . now ( ) / 1000 | 0 ;
for ( let minerId in connectedMiners ) {
let miner = connectedMiners [ minerId ] ;
if ( ! miner . noRetarget ) {
miner . retarget ( now ) ;
}
}
} , config . poolServer . varDiff . retargetTime * 1000 ) ;
// Every 30 seconds clear out timed-out miners and old bans
setInterval ( function ( ) {
let now = Date . now ( ) ;
let timeout = config . poolServer . minerTimeout * 1000 ;
for ( let minerId in connectedMiners ) {
let miner = connectedMiners [ minerId ] ;
if ( now - miner . lastBeat > timeout ) {
log ( 'warn' , logSystem , 'Miner timed out and disconnected %s@%s' , [ miner . login , miner . ip ] ) ;
delete connectedMiners [ minerId ] ;
removeConnectedWorker ( miner , 'timeout' ) ;
}
}
if ( banningEnabled ) {
for ( ip in bannedIPs ) {
let banTime = bannedIPs [ ip ] ;
if ( now - banTime > config . poolServer . banning . time * 1000 ) {
delete bannedIPs [ ip ] ;
delete perIPStats [ ip ] ;
log ( 'info' , logSystem , 'Ban dropped for %s' , [ ip ] ) ;
}
}
}
} , 30000 ) ;
/ * *
* Handle multi - thread messages
* * /
process . on ( 'message' , function ( message ) {
switch ( message . type ) {
case 'banIP' :
bannedIPs [ message . ip ] = Date . now ( ) ;
break ;
case 'BlockTemplate' :
let buffer = Buffer . from ( message . block . blocktemplate _blob , 'hex' ) ;
let new _hash = Buffer . alloc ( 32 ) ;
buffer . copy ( new _hash , 0 , previousOffset , 39 ) ;
try {
log ( 'info' , logSystem , 'New %s block to mine at height %d w/ difficulty of %d (%d transactions)' , [ config . coin , message . block . height , message . block . difficulty , ( message . block . num _transactions || 0 ) ] ) ;
if ( mergedMining ) {
for ( var childPoolIndex = 0 ; childPoolIndex < config . childPools . length ; childPoolIndex ++ ) {
processBlockTemplate ( message . block , childPoolIndex ) ;
}
} else {
processBlockTemplate ( message . block , 0 ) ;
}
return ;
} catch ( e ) { log ( 'error' , logSystem , ` BlockTemplate ${ e } ` ) }
break ;
case 'ChildBlockTemplate' :
let poolIndex = parseInt ( message . poolIndex )
try {
if ( ! currentChildBlockTemplate [ poolIndex ] || message . block . height > currentChildBlockTemplate [ poolIndex ] . height || ( currentChildBlockTemplate [ poolIndex ] . num _transactions == 0 && message . block . num _transactions > 0 ) ) {
log ( 'info' , logSystem , 'New %s child block to mine at height %d w/ difficulty of %d (%d transactions)' , [ config . childPools [ poolIndex ] . coin , message . block . height , message . block . difficulty , ( message . block . num _transactions || 0 ) ] ) ;
processChildBlockTemplate ( poolIndex , message . block ) ;
return ;
} else {
return ;
}
} catch ( e ) { log ( 'error' , logSystem , ` ChildBlockTemplate ${ e } ` ) }
break ;
}
} ) ;
/ * *
* Block template
* * /
function BlockTemplate ( template , parent , indexOfChildPool ) {
this . blob = template . blocktemplate _blob
this . difficulty = template . difficulty
this . height = template . height
this . seed _hash = template . seed
this . reserveOffset = template . reserved _offset ;
// Set this.buffer to the binary decoded version of the BT blob.
this . buffer = Buffer . from ( this . blob , 'hex' ) ;
// Generate a clean, shiny new buffer.
this . previous _hash = Buffer . alloc ( 32 ) ;
// Copy in bytes 9 through 41 to this.previous_hash from the current BT.
this . buffer . copy ( this . previous _hash , 0 , 9 , 41 ) ;
// 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 . bits = template . bits
this . buffer = Buffer . from ( this . blob , 'hex' )
//this.hash = this.idHash = this.prev_hash = template.hash
this . block _version = 0
}
BlockTemplate . prototype = {
nextBlob : function ( idBuffer ) {
return utils . cnUtil . get _hash _from _block _template _with _extra ( this . buffer , idBuffer ) . toString ( 'hex' )
} ,
nextBlobWithChildNonce : function ( ) {
return this . buffer . toString ( 'hex' )
}
}
/ * *
* Process block template
* * /
function processBlockTemplate ( template , indexOfChildPool ) {
let block _template = new BlockTemplate ( template , true , indexOfChildPool )
if ( currentBlockTemplate [ indexOfChildPool ] )
validBlockTemplates [ indexOfChildPool ] . push ( currentBlockTemplate [ indexOfChildPool ] ) ;
while ( validBlockTemplates [ indexOfChildPool ] . length > ( mergedMining ? 6 : 3 ) )
validBlockTemplates [ indexOfChildPool ] . shift ( ) ;
currentBlockTemplate [ indexOfChildPool ] = block _template ;
notifyConnectedMiners ( indexOfChildPool )
}
/ * *
* Process child block template
* * /
function processChildBlockTemplate ( indexOfChildPool , template ) {
let block _template = new BlockTemplate ( template , false ) ;
currentChildBlockTemplate [ indexOfChildPool ] = block _template ;
// Update the parent block template to include this new child
if ( currentBlockTemplate [ indexOfChildPool ] ) {
processBlockTemplate ( currentBlockTemplate [ indexOfChildPool ] , indexOfChildPool ) ;
}
}
function notifyConnectedMiners ( indexOfChildPool ) {
let now = Date . now ( ) / 1000 | 0 ;
for ( let minerId in connectedMiners ) {
let miner = connectedMiners [ minerId ] ;
if ( indexOfChildPool === miner . activeChildPool )
miner . sendReply ( null , miner . getJob ( ) , true )
//miner.pushMessage('job', miner.getJob());
}
}
/ * *
* Variable difficulty
* * /
let VarDiff = ( function ( ) {
let variance = config . poolServer . varDiff . variancePercent / 100 * config . poolServer . varDiff . targetTime ;
return {
variance : variance ,
bufferSize : config . poolServer . varDiff . retargetTime / config . poolServer . varDiff . targetTime * 4 ,
tMin : config . poolServer . varDiff . targetTime - variance ,
tMax : config . poolServer . varDiff . targetTime + variance ,
maxJump : config . poolServer . varDiff . maxJump
} ;
} ) ( ) ;
function GetRewardTypeAsKey ( rewardType ) {
switch ( rewardType ) {
case 'solo' :
return ':solo'
case 'prop' :
return ''
default :
return ''
}
}
/ * *
* Miner
* * /
function Miner ( rewardType , childRewardType , id , childPoolIndex , login , pass , ip , port , agent , childLogin , startingDiff , noRetarget , pushMessage , sendReply ) {
this . rewardType = rewardType
this . childRewardType = childRewardType
this . rewardTypeAsKey = GetRewardTypeAsKey ( rewardType )
this . childRewardTypeAsKey = GetRewardTypeAsKey ( childRewardType )
this . lastChildBlockHeight = 0
this . id = id ;
this . activeChildPool = childPoolIndex || 0 ;
this . login = login ;
this . pass = pass ;
this . ip = ip ;
this . port = port ;
this . proxy = false ;
if ( agent && agent . includes ( 'xmr-node-proxy' ) ) {
this . proxy = true ;
}
this . workerName = 'undefined' ;
this . childLogin = childLogin ;
if ( pass . lastIndexOf ( '@' ) >= 0 && pass . lastIndexOf ( '@' ) < pass . length ) {
passDelimiterPos = pass . lastIndexOf ( '@' ) + 1 ;
this . workerName = pass . substr ( passDelimiterPos , pass . length ) . trim ( ) ;
}
this . pushMessage = pushMessage ;
this . sendReply = sendReply
this . heartbeat ( ) ;
this . noRetarget = noRetarget ;
this . difficulty = startingDiff ;
this . validJobs = [ ] ;
this . workerName2 = pass ;
// Vardiff related variables
this . shareTimeRing = utils . ringBuffer ( 16 ) ;
this . lastShareTime = Date . now ( ) / 1000 | 0 ;
if ( shareTrustEnabled ) {
this . trust = {
threshold : config . poolServer . shareTrust . threshold ,
probability : 1 ,
penalty : 0
} ;
}
}
Miner . prototype = {
retarget : function ( now ) {
let options = config . poolServer . varDiff ;
let sinceLast = now - this . lastShareTime ;
let decreaser = sinceLast > VarDiff . tMax ;
let avg = this . shareTimeRing . avg ( decreaser ? sinceLast : null ) ;
let newDiff ;
let direction ;
if ( avg > VarDiff . tMax && this . difficulty > options . minDiff ) {
newDiff = options . targetTime / avg * this . difficulty ;
newDiff = newDiff > options . minDiff ? newDiff : options . minDiff ;
direction = - 1 ;
}
else if ( avg < VarDiff . tMin && this . difficulty < options . maxDiff ) {
newDiff = options . targetTime / avg * this . difficulty ;
newDiff = newDiff < options . maxDiff ? newDiff : options . maxDiff ;
direction = 1 ;
}
else {
return ;
}
if ( Math . abs ( newDiff - this . difficulty ) / this . difficulty * 100 > options . maxJump ) {
let change = options . maxJump / 100 * this . difficulty * direction ;
newDiff = this . difficulty + change ;
}
this . setNewDiff ( newDiff ) ;
this . shareTimeRing . clear ( ) ;
if ( decreaser ) this . lastShareTime = now ;
} ,
setNewDiff : function ( newDiff ) {
newDiff = Math . round ( newDiff ) ;
if ( this . difficulty === newDiff ) return ;
log ( 'info' , logSystem , 'Retargetting difficulty %d to %d for %s' , [ this . difficulty , newDiff , this . login ] ) ;
this . pendingDifficulty = newDiff ;
this . sendReply ( null , this . getJob ( ) , true )
} ,
heartbeat : function ( ) {
this . lastBeat = Date . now ( ) ;
} ,
getTargetHex : function ( ) {
if ( this . pendingDifficulty ) {
this . lastDifficulty = this . difficulty ;
this . difficulty = this . pendingDifficulty ;
this . pendingDifficulty = null ;
}
let padded = Buffer . alloc ( 32 ) ;
padded . fill ( 0 ) ;
let diffBuff = diff1 . div ( this . difficulty ) . toBuffer ( ) ;
diffBuff . copy ( padded , 32 - diffBuff . length ) ;
let buff = padded . slice ( 0 , 4 ) ;
let buffArray = buff . toByteArray ( ) . reverse ( ) ;
let buffReversed = Buffer . from ( buffArray ) ;
this . target = buffReversed . readUInt32BE ( 0 ) ;
let hex = buffReversed . toString ( 'hex' ) ;
return hex ;
} ,
getJob : function ( ) {
let idBuffer = utils . instanceId ( 4 )
let blockTemplate = currentBlockTemplate [ this . activeChildPool ]
if ( mergedMining ) {
if ( this . lastBlockHeight === blockTemplate . height
&& ( ! currentChildBlockTemplate [ this . activeChildPool ] || this . lastChildBlockHeight === currentChildBlockTemplate [ this . activeChildPool ] . height )
&& ! this . pendingDifficulty
&& this . cachedJob !== null
&& ! config . daemon . alwaysPoll ) {
return this . cachedJob ;
}
this . lastChildBlockHeight = currentChildBlockTemplate ? currentChildBlockTemplate [ this . activeChildPool ] . height : - 1 ;
newJob . activeChildPool = this . activeChildPool
newJob . childHeight = this . lastChildBlockHeight
}
let blob = this . proxy ? blockTemplate . nextBlobWithChildNonce ( ) : blockTemplate . nextBlob ( idBuffer ) ;
this . lastBlockHeight = blockTemplate . height ;
let target = this . getTargetHex ( ) ;
let heightBuffer = Buffer . alloc ( 8 )
heightBuffer . writeUInt32BE ( blockTemplate . height , 4 )
let newJob = {
id : blob ,
unique _id : idBuffer ,
height : blockTemplate . height ,
submissions : [ ] ,
difficulty : miner . difficulty ,
extraNonce : blockTemplate . extraNonce
}
let padded = Buffer . alloc ( 32 )
padded . fill ( 0 )
let diffBuff = diff1 . div ( miner . difficulty ) . toBuffer ( )
diffBuff . copy ( padded , 32 - diffBuff . length )
let buff = padded . slice ( 0 , 32 )
target = buff . toString ( 'hex' )
this . validJobs . push ( newJob ) ;
while ( this . validJobs . length > 4 )
this . validJobs . shift ( ) ;
this . cachedJob = [ ` 0x ${ blob } ` , ` 0x ${ blockTemplate . seed _hash } ` , ` 0x ${ target } ` , ` 0x ${ heightBuffer . toString ( 'hex' ) } ` ]
if ( this . proxy ) {
newJob . clientPoolLocation = blockTemplate . clientPoolLocation
newJob . clientNonceLocation = blockTemplate . clientNonceLocation
}
return this . cachedJob ;
} ,
checkBan : function ( validShare ) {
if ( ! banningEnabled ) return ;
// Init global per-ip shares stats
if ( ! perIPStats [ this . ip ] ) {
perIPStats [ this . ip ] = { validShares : 0 , invalidShares : 0 } ;
}
let stats = perIPStats [ this . ip ] ;
validShare ? stats . validShares ++ : stats . invalidShares ++ ;
if ( stats . validShares + stats . invalidShares >= config . poolServer . banning . checkThreshold ) {
if ( stats . invalidShares / stats . validShares >= config . poolServer . banning . invalidPercent / 100 ) {
validShare ? this . validShares ++ : this . invalidShares ++ ;
log ( 'warn' , logSystem , 'Banned %s@%s' , [ this . login , this . ip ] ) ;
bannedIPs [ this . ip ] = Date . now ( ) ;
delete connectedMiners [ this . id ] ;
process . send ( { type : 'banIP' , ip : this . ip } ) ;
removeConnectedWorker ( this , 'banned' ) ;
}
else {
stats . invalidShares = 0 ;
stats . validShares = 0 ;
}
}
}
} ;
validateMinerPaymentId _difficulty = ( address , ip , poolServerConfig , coin , sendReply ) => {
if ( utils . characterCount ( address , '\\+' ) > 1 ) {
let message = ` Invalid paymentId specified for ${ coin } login, ${ ip } ` ;
if ( poolServerConfig . paymentId . validation ) {
process . send ( { type : 'banIP' , ip : ip } ) ;
message += ` banned for ${ poolServerConfig . banning . time / 60 } minutes `
}
sendReply ( message )
log ( 'warn' , logSystem , message ) ;
return false
}
if ( utils . characterCount ( address , '\\.' ) > 1 ) {
log ( 'warn' , logSystem , ` Invalid difficulty specified for ${ coin } login ` ) ;
sendReply ( ` Invalid difficulty specified for ${ coin } login, ${ ip } ` )
return false
}
return true
}
/ * *
* Handle miner method
* * /
function handleMinerMethod ( socket , method , params , ip , portData , sendReply , pushMessage ) {
// Check for ban here, so preconnected attackers can't continue to screw you
if ( IsBannedIp ( ip ) ) {
sendReply ( 'Your IP is banned' ) ;
return ;
}
switch ( method ) {
case 'eth_submitLogin' :
if ( ! params || ! ( params instanceof Array ) ) {
sendReply ( "No array params specified" )
return
}
params = {
login : params [ 0 ] ,
pass : params [ 1 ]
} ;
case 'login' :
let login = params . login ? params . login . trim ( ) : null ;
if ( ! login ) {
sendReply ( 'Missing login' ) ;
return ;
}
if ( ! validateMinerPaymentId _difficulty ( login , ip , config . poolServer , 'parent ' , sendReply ) )
return
let workerName = params . rigid ? params . rigid . trim ( ) : 'undefined'
if ( login . indexOf ( '@' ) >= 0 && login . lastIndexOf ( '@' ) < login . length ) {
passDelimiterPos = login . lastIndexOf ( '@' ) ;
workerName = login . substr ( passDelimiterPos + 1 , login . length ) . trim ( )
login = login . substr ( 0 , passDelimiterPos ) . trim ( ) ;
}
let calculated = utils . determineRewardData ( login )
login = calculated . address
let rewardType = calculated . rewardType
let address = ''
let paymentid = null
let port = portData . port ;
let pass = params . pass ;
let childLogin = pass . trim ( ) ;
let childPoolIndex = 0 ;
let childRewardType = rewardType
if ( mergedMining ) {
childPoolIndex = - 1
if ( ! validateMinerPaymentId _difficulty ( pass , ip , config . poolServer , 'child ' , sendReply ) )
return
calculated = utils . determineRewardData ( pass )
pass = calculated . address
childRewardType = calculated . rewardType
if ( pass . indexOf ( '@' ) >= 0 && pass . indexOf ( '@' ) >= 0 ) {
passDelimiterPos = pass . lastIndexOf ( '@' ) ;
childLogin = pass . substr ( 0 , passDelimiterPos ) . trim ( ) ;
}
childLogin = childLogin . replace ( /\s/g , '' ) ;
childLogin = utils . cleanupSpecialChars ( childLogin ) ;
let addr = childLogin . split ( config . poolServer . paymentId . addressSeparator ) ;
address = addr [ 0 ] || null ;
paymentId = addr [ 1 ] || null
if ( ! address ) {
log ( 'warn' , logSystem , 'No address specified for login' ) ;
sendReply ( 'No address specified for login' ) ;
}
if ( paymentId && config . poolServer . paymentId . validation ) {
let valid = false ;
config . poolServer . paymentId . validations . forEach ( validation => {
if ( paymentId . match ( ` ^([a-zA-Z0-9]){ ${ validation } } $ ` ) )
{
return valid = true
}
} )
if ( ! valid ) {
log ( 'warn' , logSystem , 'Invalid paymentId specified for login' ) ;
}
if ( config . poolServer . paymentId . ban ) {
process . send ( { type : 'banIP' , ip : ip } ) ;
sendReply ( ` Invalid paymentId specified for login, ${ portData . ip } banned for ${ config . poolServer . banning . time / 60 } minutes ` ) ;
}
}
for ( i = 0 ; i < config . childPools . length ; i ++ ) {
if ( config . childPools [ i ] . pattern ) {
if ( new RegExp ( config . childPools [ i ] . pattern , 'i' ) . test ( address ) )
{
childPoolIndex = i
break
}
}
}
if ( childPoolIndex < 0 )
{
childPoolIndex = fallBackCoin
address = config . childPools [ childPoolIndex ] . poolAddress
childLogin = config . childPools [ childPoolIndex ] . poolAddress
}
if ( ! utils . validateChildMinerAddress ( address , childPoolIndex ) ) {
let addressPrefix = utils . getAddressPrefix ( address ) ;
if ( ! addressPrefix ) addressPrefix = 'N/A' ;
log ( 'warn' , logSystem , 'Invalid address used for childLogin (prefix: %s): %s' , [ addressPrefix , address ] ) ;
sendReply ( 'Invalid address used for childLogin' ) ;
return ;
}
}
let difficulty = portData . difficulty ;
let noRetarget = false ;
if ( config . poolServer . fixedDiff . enabled ) {
let fixedDiffCharPos = login . indexOf ( config . poolServer . fixedDiff . addressSeparator ) ;
if ( fixedDiffCharPos !== - 1 && ( login . length - fixedDiffCharPos < 32 ) ) {
diffValue = login . substr ( fixedDiffCharPos + 1 ) ;
difficulty = parseInt ( diffValue ) ;
login = login . substr ( 0 , fixedDiffCharPos ) ;
if ( ! difficulty || difficulty != diffValue ) {
log ( 'warn' , logSystem , 'Invalid difficulty value "%s" for login: %s' , [ diffValue , login ] ) ;
difficulty = portData . difficulty ;
} else {
noRetarget = true ;
if ( difficulty < config . poolServer . varDiff . minDiff ) {
difficulty = config . poolServer . varDiff . minDiff ;
}
}
}
}
addr = login . split ( config . poolServer . paymentId . addressSeparator ) ;
address = addr [ 0 ] || null ;
paymentId = addr [ 1 ] || null ;
if ( ! address ) {
log ( 'warn' , logSystem , 'No address specified for login' ) ;
sendReply ( 'Invalid address used for login' ) ;
return
}
if ( paymentId && paymentId . match ( '^([a-zA-Z0-9]){0,15}$' ) ) {
if ( config . poolServer . paymentId . validation ) {
process . send ( { type : 'banIP' , ip : ip } ) ;
log ( 'warn' , logSystem , 'Invalid paymentId specified for login' ) ;
} else {
log ( 'warn' , logSystem , 'Invalid paymentId specified for login' ) ;
}
sendReply ( ` Invalid paymentId specified for login, ${ portData . ip } banned for ${ config . poolServer . banning . time / 60 } minutes ` )
return ;
}
if ( ! utils . validateMinerAddress ( address ) ) {
let addressPrefix = utils . getAddressPrefix ( address ) ;
if ( ! addressPrefix ) addressPrefix = 'N/A' ;
log ( 'warn' , logSystem , 'Invalid address used for login (prefix: %s): %s' , [ addressPrefix , address ] ) ;
sendReply ( 'Invalid address used for login' ) ;
return ;
}
let minerId = utils . uid ( ) ;
miner = new Miner ( rewardType , childRewardType , minerId , childPoolIndex , login , pass , ip , port , params . agent , childLogin , difficulty , noRetarget , pushMessage , sendReply ) ;
if ( ! socket . miner _ids )
socket . miner _ids = [ ]
socket . miner _ids . push ( minerId )
connectedMiners [ minerId ] = miner ;
sendReply ( null , true )
newConnectedWorker ( miner ) ;
break ;
case 'eth_getWork' : {
const minerId = params . id ? params . id : ( socket . miner _ids && socket . miner _ids . length == 1 ? socket . miner _ids [ 0 ] : "" ) ;
let miner = connectedMiners [ minerId ] ;
if ( ! miner ) {
sendReply ( 'Unauthenticated' ) ;
return ;
}
miner . heartbeat ( ) ;
sendReply ( null , miner . getJob ( ) ) ;
break
}
case 'getjob' :
if ( ! miner ) {
sendReply ( 'Unauthenticated' ) ;
return ;
}
miner . heartbeat ( ) ;
sendReply ( null , miner . getJob ( ) ) ;
break ;
case 'eth_submitWork' : {
if ( ! params || ! ( params instanceof Array ) ) {
sendReply ( "No array params specified" ) ;
return ;
}
params = {
nonce : params [ 0 ] . substr ( 2 ) ,
job _id : params [ 1 ] . substr ( 2 )
}
}
case 'submit' : {
const minerId = params . id ? params . id : ( socket . miner _ids && socket . miner _ids . length == 1 ? socket . miner _ids [ 0 ] : "" ) ;
let miner = connectedMiners [ minerId ] ;
if ( ! miner ) {
sendReply ( 'Unauthenticated' ) ;
return ;
}
miner . heartbeat ( ) ;
let job = miner . validJobs . filter ( function ( job ) {
return job . id === params . job _id ;
} ) [ 0 ] ;
if ( ! job ) {
sendReply ( 'Invalid job id' ) ;
return ;
}
if ( typeof params . nonce !== 'string' || ! noncePattern . test ( params . nonce ) ) {
let minerText = miner ? ( ' ' + miner . login + '@' + miner . ip ) : '' ;
log ( 'warn' , logSystem , 'Malformed nonce: ' + JSON . stringify ( params ) + ' from ' + minerText ) ;
perIPStats [ miner . ip ] = { validShares : 0 , invalidShares : 999999 } ;
miner . checkBan ( false ) ;
sendReply ( 'Duplicate share1' ) ;
return ;
}
// Force lowercase for further comparison
params . nonce = params . nonce . toLowerCase ( ) ;
if ( ! miner . proxy ) {
if ( job . submissions . indexOf ( params . nonce ) !== - 1 ) {
let minerText = miner ? ( ' ' + miner . login + '@' + miner . ip ) : '' ;
log ( 'warn' , logSystem , 'Duplicate share: ' + JSON . stringify ( params ) + ' from ' + minerText ) ;
perIPStats [ miner . ip ] = { validShares : 0 , invalidShares : 999999 } ;
miner . checkBan ( false ) ;
sendReply ( 'Duplicate share2' ) ;
return ;
}
job . submissions . push ( params . nonce ) ;
} else {
if ( ! Number . isInteger ( params . poolNonce ) || ! Number . isInteger ( params . workerNonce ) ) {
let minerText = miner ? ( ' ' + miner . login + '@' + miner . ip ) : '' ;
log ( 'warn' , logSystem , 'Malformed nonce: ' + JSON . stringify ( params ) + ' from ' + minerText ) ;
perIPStats [ miner . ip ] = { validShares : 0 , invalidShares : 999999 } ;
miner . checkBan ( false ) ;
sendReply ( 'Duplicate share3' ) ;
return ;
}
let nonce _test = ` ${ params . nonce } _ ${ params . poolNonce } _ ${ params . workerNonce } ` ;
if ( job . submissions . indexOf ( nonce _test ) !== - 1 ) {
let minerText = miner ? ( ' ' + miner . login + '@' + miner . ip ) : '' ;
log ( 'warn' , logSystem , 'Duplicate share: ' + JSON . stringify ( params ) + ' from ' + minerText ) ;
perIPStats [ miner . ip ] = { validShares : 0 , invalidShares : 999999 } ;
miner . checkBan ( false ) ;
sendReply ( 'Duplicate share4' ) ;
return ;
}
job . submissions . push ( nonce _test ) ;
}
let isJobBlock = function ( b ) {
return b . height === job . height && job . childHeight === (
b . childBlockTemplate ? b . childBlockTemplate . height : undefined ) ;
} ;
let blockTemplate = currentBlockTemplate [ miner . activeChildPool ]
if ( job . childHeight )
blockTemplate = isJobBlock ( currentBlockTemplate [ miner . activeChildPool ] ) ? currentBlockTemplate [ miner . activeChildPool ] : validBlockTemplates [ miner . activeChildPool ] . filter ( isJobBlock ) [ 0 ] ;
if ( ! blockTemplate ) {
sendReply ( 'Block expired' ) ;
return ;
}
let shareAccepted = processShare ( miner , job , blockTemplate , params ) ;
miner . checkBan ( shareAccepted ) ;
if ( shareTrustEnabled ) {
if ( shareAccepted ) {
miner . trust . probability -= shareTrustStepFloat ;
if ( miner . trust . probability < shareTrustMinFloat )
miner . trust . probability = shareTrustMinFloat ;
miner . trust . penalty -- ;
miner . trust . threshold -- ;
}
else {
log ( 'warn' , logSystem , 'Share trust broken by %s@%s' , [ miner . login , miner . ip ] ) ;
miner . trust . probability = 1 ;
miner . trust . penalty = config . poolServer . shareTrust . penalty ;
}
}
if ( ! shareAccepted ) {
sendReply ( 'Rejected share: invalid result' ) ;
return ;
}
let now = Date . now ( ) / 1000 | 0 ;
miner . shareTimeRing . append ( now - miner . lastShareTime ) ;
miner . lastShareTime = now ;
sendReply ( null , { status : 'OK' } ) ;
break ;
}
case 'keepalived' :
if ( ! miner ) {
sendReply ( 'Unauthenticated' ) ;
return ;
}
miner . heartbeat ( ) ;
sendReply ( null , { status : 'KEEPALIVED' } ) ;
break ;
default :
sendReply ( 'Invalid method' ) ;
let minerText = miner ? ( ' ' + miner . login + '@' + miner . ip ) : '' ;
log ( 'warn' , logSystem , 'Invalid method: %s (%j) from %s' , [ method , params , minerText ] ) ;
break ;
}
}
/ * *
* New connected worker
* * /
function newConnectedWorker ( miner ) {
log ( 'info' , logSystem , 'Miner connected %s@%s on port' , [ miner . login , miner . ip , miner . port ] ) ;
if ( miner . workerName !== 'undefined' ) log ( 'info' , logSystem , 'Worker Name: %s' , [ miner . workerName ] ) ;
if ( miner . difficulty ) log ( 'info' , logSystem , 'Miner difficulty fixed to %s' , [ miner . difficulty ] ) ;
redisClient . sadd ( ` ${ config . coin } :workers_ip: ${ miner . login } ` , miner . ip ) ;
redisClient . hincrby ( ` ${ config . coin } :ports: ${ miner . port } ` , 'users' , 1 ) ;
redisClient . hincrby ( ` ${ config . coin } :active_connections ${ miner . rewardTypeAsKey } ` , ` ${ miner . login } ~ ${ miner . workerName } ` , 1 , function ( error , connectedWorkers ) {
if ( connectedWorkers === 1 ) {
notifications . sendToMiner ( miner . login , 'workerConnected' , {
'LOGIN' : miner . login ,
'MINER' : ` ${ miner . login . substring ( 0 , 7 ) } ... ${ miner . login . substring ( miner . login . length - 7 ) } ` ,
'IP' : miner . ip . replace ( '::ffff:' , '' ) ,
'PORT' : miner . port ,
'WORKER_NAME' : miner . workerName !== 'undefined' ? miner . workerName : ''
} ) ;
}
} ) ;
if ( config . poolServer . mergedMining ) {
redisClient . sadd ( ` ${ config . childPools [ miner . activeChildPool ] . coin } :workers_ip: ${ miner . childLogin } ` , miner . ip ) ;
redisClient . hincrby ( ` ${ config . childPools [ miner . activeChildPool ] . coin } :ports: ${ miner . port } ` , 'users' , 1 ) ;
redisClient . hincrby ( ` ${ config . childPools [ miner . activeChildPool ] . coin } :active_connections ${ miner . childRewardTypeAsKey } ` , ` ${ miner . childLogin } ~ ${ miner . workerName } ` , 1 , function ( error , connectedWorkers ) {
} ) ;
let redisCommands = config . childPools . map ( item => {
return [ 'hdel' , ` ${ config . coin } :workers: ${ miner . login } ` , ` ${ item . coin } ` , ]
} )
redisClient . multi ( redisCommands ) . exec ( function ( error , replies ) {
if ( error ) {
log ( 'error' , logSystem , 'Failed to clear childCoins from parent in redis %j \n %j' , [ err , redisCommands ] ) ;
}
} )
redisClient . hset ( ` ${ config . coin } :workers: ${ miner . login } ` , ` ${ config . childPools [ miner . activeChildPool ] . coin } ` , miner . childLogin )
redisClient . hset ( ` ${ config . childPools [ miner . activeChildPool ] . coin } :workers: ${ miner . childLogin } ` , ` ${ config . coin } ` , miner . login )
}
}
/ * *
* Remove connected worker
* * /
function removeConnectedWorker ( miner , reason ) {
redisClient . hincrby ( ` ${ config . coin } :ports: ${ miner . port } ` , 'users' , '-1' ) ;
if ( mergedMining ) {
redisClient . hincrby ( ` ${ config . childPools [ miner . activeChildPool ] . coin } :ports: ${ miner . port } ` , 'users' , '-1' ) ;
redisClient . hincrby ( ` ${ config . childPools [ miner . activeChildPool ] . coin } :active_connections ${ miner . childRewardTypeAsKey } ` , ` ${ miner . childLogin } ~ ${ miner . workerName } ` , 1 , function ( error , connectedWorkers ) {
} ) ;
}
redisClient . hincrby ( ` ${ config . coin } :active_connections ${ miner . rewardTypeAsKey } ` , ` ${ miner . login } ~ ${ miner . workerName } ` , - 1 , function ( error , connectedWorkers ) {
if ( reason === 'banned' ) {
notifications . sendToMiner ( miner . login , 'workerBanned' , {
'LOGIN' : miner . login ,
'MINER' : ` ${ miner . login . substring ( 0 , 7 ) } ... ${ miner . login . substring ( miner . login . length - 7 ) } ` ,
'IP' : miner . ip . replace ( '::ffff:' , '' ) ,
'PORT' : miner . port ,
'WORKER_NAME' : miner . workerName !== 'undefined' ? miner . workerName : ''
} ) ;
} else if ( ! connectedWorkers || connectedWorkers <= 0 ) {
notifications . sendToMiner ( miner . login , 'workerTimeout' , {
'LOGIN' : miner . login ,
'MINER' : ` ${ miner . login . substring ( 0 , 7 ) } ... ${ miner . login . substring ( miner . login . length - 7 ) } ` ,
'IP' : miner . ip . replace ( '::ffff:' , '' ) ,
'PORT' : miner . port ,
'WORKER_NAME' : miner . workerName !== 'undefined' ? miner . workerName : '' ,
'LAST_HASH' : utils . dateFormat ( new Date ( miner . lastBeat ) , 'yyyy-mm-dd HH:MM:ss Z' )
} ) ;
}
} ) ;
}
/ * *
* Return if IP has been banned
* * /
function IsBannedIp ( ip ) {
if ( ! banningEnabled || ! bannedIPs [ ip ] ) return false ;
let bannedTime = bannedIPs [ ip ] ;
let bannedTimeAgo = Date . now ( ) - bannedTime ;
let timeLeft = config . poolServer . banning . time * 1000 - bannedTimeAgo ;
if ( timeLeft > 0 ) {
return true ;
}
else {
delete bannedIPs [ ip ] ;
log ( 'info' , logSystem , 'Ban dropped for %s' , [ ip ] ) ;
return false ;
}
}
function recordShareData ( miner , job , shareDiff , blockCandidate , hashHex , shareType , blockTemplate , pool ) {
let dateNow = Date . now ( ) ;
let dateNowSeconds = dateNow / 1000 | 0 ;
let coin = pool !== null ? pool . coin : config . coin ;
let login = pool !== null ? miner . childLogin : miner . login ;
let job _height = pool !== null ? job . childHeight : job . height
let workerName = miner . workerName ;
let rewardType = pool !== null ? miner . childRewardType : miner . rewardType
let updateScore ;
// Weighting older shares lower than newer ones to prevent pool hopping
if ( slushMiningEnabled ) {
// We need to do this via an eval script because we need fetching the last block time and
// calculating the score to run in a single transaction (otherwise we could have a race
// condition where a block gets discovered between the time we look up lastBlockFound and
// insert the score, which would give the miner an erroneously huge proportion on the new block)
updateScore = [ 'eval' , `
local age = ( ARGV [ 3 ] - redis . call ( 'hget' , KEYS [ 2 ] , 'lastBlockFound' ) ) / 1000
local score = string . format ( '%.17g' , ARGV [ 2 ] * math . exp ( age / ARGV [ 4 ] ) )
redis . call ( 'hincrbyfloat' , KEYS [ 1 ] , ARGV [ 1 ] , score )
return { score , tostring ( age ) }
` ,
2 /*keys*/ , coin + ':scores:roundCurrent' , coin + ':stats' ,
/* args */ login , job . difficulty , Date . now ( ) , config . poolServer . slushMining . weight ] ;
}
else {
job . score = job . difficulty ;
updateScore = [ 'hincrbyfloat' , ` ${ coin } :scores: ${ rewardType } :roundCurrent ` , login , job . score ]
}
let redisCommands = [
updateScore ,
[ 'hincrby' , ` ${ coin } :shares_actual: ${ rewardType } :roundCurrent ` , login , job . difficulty ] ,
[ 'zadd' , ` ${ coin } :hashrate ` , dateNowSeconds , [ job . difficulty , login , dateNow , rewardType ] . join ( ':' ) ] ,
[ 'hincrby' , ` ${ coin } :workers: ${ login } ` , 'hashes' , job . difficulty ] ,
[ 'hset' , ` ${ coin } :workers: ${ login } ` , 'lastShare' , dateNowSeconds ] ,
[ 'expire' , ` ${ coin } :workers: ${ login } ` , ( 86400 * cleanupInterval ) ] ,
[ 'expire' , ` ${ coin } :payments: ${ login } ` , ( 86400 * cleanupInterval ) ]
] ;
if ( workerName ) {
redisCommands . push ( [ 'zadd' , ` ${ coin } :hashrate ` , dateNowSeconds , [ job . difficulty , login + '~' + workerName , dateNow , rewardType ] . join ( ':' ) ] ) ;
redisCommands . push ( [ 'hincrby' , ` ${ coin } :unique_workers: ${ login } ~ ${ workerName } ` , 'hashes' , job . difficulty ] ) ;
redisCommands . push ( [ 'hset' , ` ${ coin } :unique_workers: ${ login } ~ ${ workerName } ` , 'lastShare' , dateNowSeconds ] ) ;
redisCommands . push ( [ 'expire' , ` ${ coin } :unique_workers: ${ login } ~ ${ workerName } ` , ( 86400 * cleanupInterval ) ] ) ;
}
2022-01-26 09:08:58 -06:00
/ *
if ( blockCandidate ) {
redisCommands . push ( [ 'hset' , ` ${ coin } :stats ` , ` lastBlockFound ${ rewardType } ` , Date . now ( ) ] ) ;
redisCommands . push ( [ 'rename' , ` ${ coin } :scores:prop:roundCurrent ` , coin + ':scores:prop:round' + job _height ] ) ;
redisCommands . push ( [ 'rename' , ` ${ coin } :scores:solo:roundCurrent ` , coin + ':scores:solo:round' + job _height ] ) ;
redisCommands . push ( [ 'rename' , ` ${ coin } :shares_actual:prop:roundCurrent ` , ` ${ coin } :shares_actual:prop:round ${ job _height } ` ] ) ;
redisCommands . push ( [ 'rename' , ` ${ coin } :shares_actual:solo:roundCurrent ` , ` ${ coin } :shares_actual:solo:round ${ job _height } ` ] ) ;
if ( rewardType === 'prop' ) {
redisCommands . push ( [ 'hgetall' , ` ${ coin } :scores:prop:round ${ job _height } ` ] ) ;
redisCommands . push ( [ 'hgetall' , ` ${ coin } :shares_actual:prop:round ${ job _height } ` ] ) ;
}
if ( rewardType === 'solo' ) {
redisCommands . push ( [ 'hget' , ` ${ coin } :scores:solo:round ${ job _height } ` , login ] ) ;
redisCommands . push ( [ 'hget' , ` ${ coin } :shares_actual:solo:round ${ job _height } ` , login ] ) ;
}
}
* /
2022-01-15 11:31:38 -06:00
if ( blockCandidate ) {
redisCommands . push ( [ 'hset' , ` ${ coin } :stats ` , ` lastBlockFound ${ rewardType } ` , Date . now ( ) ] ) ;
if ( rewardType === 'prop' ) {
2022-01-23 08:32:42 -06:00
redisCommands . push ( [ 'rename' , ` ${ coin } :scores:prop:roundCurrent ` , coin + ':scores:prop:round' + job _height ] ) ;
redisCommands . push ( [ 'rename' , ` ${ coin } :shares_actual:prop:roundCurrent ` , ` ${ coin } :shares_actual:prop:round ${ job _height } ` ] ) ;
2022-01-15 11:31:38 -06:00
redisCommands . push ( [ 'hgetall' , ` ${ coin } :scores:prop:round ${ job _height } ` ] ) ;
redisCommands . push ( [ 'hgetall' , ` ${ coin } :shares_actual:prop:round ${ job _height } ` ] ) ;
}
if ( rewardType === 'solo' ) {
2022-01-23 08:32:42 -06:00
redisCommands . push ( [ 'rename' , ` ${ coin } :scores:solo:roundCurrent ` , coin + ':scores:solo:round' + job _height ] ) ;
redisCommands . push ( [ 'rename' , ` ${ coin } :shares_actual:solo:roundCurrent ` , ` ${ coin } :shares_actual:solo:round ${ job _height } ` ] ) ;
2022-01-15 11:31:38 -06:00
redisCommands . push ( [ 'hget' , ` ${ coin } :scores:solo:round ${ job _height } ` , login ] ) ;
redisCommands . push ( [ 'hget' , ` ${ coin } :shares_actual:solo:round ${ job _height } ` , login ] ) ;
}
}
redisClient . multi ( redisCommands ) . exec ( function ( err , replies ) {
if ( err ) {
log ( 'error' , logSystem , 'Failed to insert share data into redis %j \n %j' , [ err , redisCommands ] ) ;
return ;
}
if ( slushMiningEnabled ) {
job . score = parseFloat ( replies [ 0 ] [ 0 ] ) ;
let age = parseFloat ( replies [ 0 ] [ 1 ] ) ;
log ( 'info' , logSystem , 'Submitted score ' + job . score + ' for difficulty ' + job . difficulty + ' and round age ' + age + 's' ) ;
}
if ( blockCandidate ) {
let workerScores = replies [ replies . length - 2 ] ;
let workerShares = replies [ replies . length - 1 ] ;
let totalScore = 0 ;
let totalShares = 0 ;
if ( rewardType === 'solo' ) {
totalScore = workerScores
totalShares = workerShares
}
if ( rewardType === 'prop' ) {
totalScore = Object . keys ( workerScores ) . reduce ( function ( p , c ) {
return p + parseFloat ( workerScores [ c ] )
} , 0 ) ;
totalShares = Object . keys ( workerShares ) . reduce ( function ( p , c ) {
return p + parseInt ( workerShares [ c ] )
} , 0 ) ;
}
redisClient . zadd ( coin + ':blocks:candidates' , job _height , [
rewardType ,
login ,
hashHex ,
Date . now ( ) / 1000 | 0 ,
blockTemplate . difficulty ,
totalShares ,
totalScore
] . join ( ':' ) , function ( err , result ) {
if ( err ) {
log ( 'error' , logSystem , 'Failed inserting block candidate %s \n %j' , [ hashHex , err ] ) ;
}
} ) ;
notifications . sendToAll ( 'blockFound' , {
'HEIGHT' : job _height ,
'HASH' : hashHex ,
'DIFFICULTY' : blockTemplate . difficulty ,
'SHARES' : totalShares ,
'MINER' : login . substring ( 0 , 7 ) + '...' + login . substring ( login . length - 7 )
} ) ;
}
} ) ;
log ( 'info' , logSystem , 'Accepted %s share at difficulty %d/%d from %s@%s' , [ shareType , job . difficulty , shareDiff , login , miner . ip ] ) ;
}
function getShareBuffer ( miner , job , blockTemplate , params ) {
if ( miner . proxy ) {
template . writeUInt32BE ( params . poolNonce , job . clientPoolLocation ) ;
template . writeUInt32BE ( params . workerNonce , job . clientNonceLocation ) ;
}
try {
let nonceBuffer = Buffer . from ( params . nonce , 'hex' )
let nonceBufferReversed = nonceBuffer . reverse ( )
return utils . cnUtil . get _blob _from _block _template ( blockTemplate . buffer , job . unique _id , nonceBufferReversed )
} catch ( e ) {
log ( 'error' , logSystem , "Can't get share buffer with nonce %s from %s@%s: %s" , [ params . nonce , miner . login , miner . ip , e ] ) ;
return null ;
}
}
/ * *
* Process miner share data
* * /
function processShare ( miner , job , blockTemplate , params ) {
let shareBuffer = getShareBuffer ( miner , job , blockTemplate , params )
if ( ! shareBuffer ) {
return false
}
let shareType ;
let convertedBlob = shareBuffer
let resultBuffer = Buffer . from ( params . job _id , 'hex' )
let nonceBuffer = Buffer . from ( params . nonce , 'hex' )
let nonceBufferReversed = nonceBuffer . reverse ( )
let heightBuffer = Buffer . alloc ( 8 )
heightBuffer . writeUInt32LE ( job . height , 0 )
let hash = utils . cnUtil . get _pow _hash ( resultBuffer , nonceBufferReversed , heightBuffer )
log ( 'info' , logSystem , 'Mining pool algorithm: %s' , [ cnAlgorithm ] ) ;
shareType = 'valid'
let hashDiff = diff1 . div ( bignum . fromBuffer ( hash ) )
console . log ( 'hashDiff ' , hashDiff . toNumber ( ) )
console . log ( 'blocDiff ' , blockTemplate . difficulty )
console . log ( 'job Diff ' , job . difficulty )
if ( hashDiff . ge ( blockTemplate . difficulty ) ) {
apiInterfaces . rpcDaemon ( 'submitblock' , [ shareBuffer . toString ( 'hex' ) ] , function ( error , result ) {
if ( error ) {
log ( 'error' , logSystem , 'Error submitting block at height %d from %s@%s, share type: "%s" - %j' , [ job . height , miner . login , miner . ip , shareType , error ] ) ;
}
else {
let blockFastHash = utils . cnUtil . get _id _hash ( shareBuffer ) . toString ( 'hex' ) ;
log ( 'info' , logSystem ,
'Block %s found at height %d by miner %s@%s - submit result: %j' ,
[ blockFastHash . substr ( 0 , 6 ) , job . height , miner . login , miner . ip , result ]
) ;
recordShareData ( miner , job , hashDiff . toString ( ) , true , blockFastHash , shareType , blockTemplate , null ) ;
}
} ) ;
}
else if ( hashDiff . lt ( job . difficulty ) ) {
log ( 'warn' , logSystem , 'Rejected low difficulty share of %s from %s@%s' , [ hashDiff . toString ( ) , miner . login , miner . ip ] ) ;
return false ;
}
else {
recordShareData ( miner , job , hashDiff . toString ( ) , false , null , shareType , null , null ) ;
}
if ( ! job . childHeight )
return true
var childBlockTemplate = blockTemplate . childBlockTemplate ;
if ( childBlockTemplate ) {
if ( mergedMining ) {
let pool = config . childPools [ miner . activeChildPool ]
if ( hashDiff . ge ( childBlockTemplate . difficulty ) ) {
let mergedBuffer = null
try {
mergedBuffer = utils . cnUtil . construct _mm _child _block _blob ( shareBuffer , cnBlobType , childBlockTemplate . buffer ) ;
} catch ( e ) {
log ( 'error' , logSystem , "Failed to construct MM child block: " + e ) ;
}
if ( mergedBuffer === null ) {
recordShareStatusMerged ( miner , 'invalid' ) ;
} else {
let onChildSuccess = ( result ) => {
let blockFastHash = utils . cnUtil . get _block _id ( mergedBuffer , 2 ) . toString ( 'hex' )
log ( 'info' , logSystem ,
'Child Block %s found at height %d by miner %s@%s - submit result: %j' ,
[ blockFastHash . substr ( 0 , 6 ) , job . childHeight , miner . workerName , miner . ip , result ] ) ;
recordShareData ( miner , job , hashDiff . toString ( ) , true , blockFastHash , shareType , childBlockTemplate , pool ) ;
}
apiInterfaces . rpcDaemon ( 'submitblock' , [ mergedBuffer . toString ( 'hex' ) ] , function ( error , result ) {
if ( error ) {
log ( 'error' , logSystem , 'Error submitting child block at height %d from %s@%s, share type: "%s" - %j' , [ job . childHeight , miner . login , miner . ip , shareType , error ] ) ;
}
else {
onChildSuccess ( result )
}
} , pool . childDaemon ) ;
}
}
else if ( hashDiff . lt ( job . difficulty ) ) {
log ( 'warn' , logSystem , 'Rejected low difficulty share of %s from %s@%s' , [ hashDiff . toString ( ) , miner . workerName , miner . ip ] ) ;
return false ;
}
else {
recordShareData ( miner , job , hashDiff . toString ( ) , false , null , shareType , null , pool ) ;
}
}
return true ;
}
return true ;
}
/ * *
* Start pool server on TCP ports
* * /
let httpResponse = ' 200 OK\nContent-Type: text/plain\nContent-Length: 20\n\nMining server online' ;
function startPoolServerTcp ( callback ) {
log ( 'info' , logSystem , 'Clear values for connected workers in redis database.' ) ;
redisClient . del ( config . coin + ':active_connections' ) ;
async . each ( config . poolServer . ports , function ( portData , cback ) {
let handleMessage = function ( socket , jsonData , pushMessage ) {
if ( ! jsonData . id ) {
log ( 'warn' , logSystem , 'Miner RPC request missing RPC id' ) ;
return ;
}
else if ( ! jsonData . method ) {
log ( 'warn' , logSystem , 'Miner RPC request missing RPC method' ) ;
return ;
}
else if ( ! jsonData . params ) {
log ( 'warn' , logSystem , 'Miner RPC request missing RPC params' ) ;
return ;
}
let sendReply = function ( error , result , excludeId = false ) {
if ( ! socket . writable ) return ;
let data = {
jsonrpc : "2.0" ,
result : result
}
if ( ! excludeId )
data . id = jsonData . id
if ( error )
data . error = { code : - 1 , message : error }
let sendData = JSON . stringify (
data
) + "\n" ;
socket . write ( sendData ) ;
} ;
handleMinerMethod ( socket , jsonData . method , jsonData . params , socket . remoteAddress , portData , sendReply , pushMessage ) ;
} ;
let socketResponder = function ( socket ) {
socket . setKeepAlive ( true ) ;
socket . setEncoding ( 'utf8' ) ;
socketMap . add ( socket )
let dataBuffer = '' ;
let pushMessage = function ( method , params ) {
if ( ! socket . writable ) return ;
let sendData = JSON . stringify ( {
jsonrpc : "2.0" ,
method : method ,
params : params
} ) + "\n" ;
socket . write ( sendData ) ;
} ;
socket . on ( 'data' , function ( d ) {
dataBuffer += d ;
if ( Buffer . byteLength ( dataBuffer , 'utf8' ) > 10240 ) { //10KB
dataBuffer = null ;
log ( 'warn' , logSystem , 'Socket flooding detected and prevented from %s' , [ socket . remoteAddress ] ) ;
socket . destroy ( ) ;
return ;
}
if ( dataBuffer . indexOf ( '\n' ) !== - 1 ) {
let messages = dataBuffer . split ( '\n' ) ;
let incomplete = dataBuffer . slice ( - 1 ) === '\n' ? '' : messages . pop ( ) ;
for ( let i = 0 ; i < messages . length ; i ++ ) {
let message = messages [ i ] ;
if ( message . trim ( ) === '' ) continue ;
let jsonData ;
try {
jsonData = JSON . parse ( message ) ;
}
catch ( e ) {
if ( message . indexOf ( 'GET /' ) === 0 ) {
if ( message . indexOf ( 'HTTP/1.1' ) !== - 1 ) {
socket . end ( 'HTTP/1.1' + httpResponse ) ;
break ;
}
else if ( message . indexOf ( 'HTTP/1.0' ) !== - 1 ) {
socket . end ( 'HTTP/1.0' + httpResponse ) ;
break ;
}
}
log ( 'warn' , logSystem , 'Malformed message from %s: %s' , [ socket . remoteAddress , message ] ) ;
socket . destroy ( ) ;
break ;
}
try {
handleMessage ( socket , jsonData , pushMessage ) ;
} catch ( e ) {
log ( 'warn' , logSystem , 'Malformed message from ' + socket . remoteAddress + ' generated an exception. Message: ' + message ) ;
if ( e . message ) log ( 'warn' , logSystem , 'Exception: ' + e . message ) ;
}
}
dataBuffer = incomplete ;
}
} ) . on ( 'error' , function ( err ) {
if ( err . code !== 'ECONNRESET' )
log ( 'warn' , logSystem , 'Socket error from %s %j' , [ socket . remoteAddress , err ] ) ;
socketMap . delete [ socket ]
socket . destroy ( )
} ) . on ( 'close' , function ( ) {
pushMessage = function ( ) { } ;
2022-01-29 15:56:23 -06:00
if ( socket . miner _ids ) {
socket . miner _ids . forEach ( miner _id => {
let miner = connectedMiners [ miner _id ]
if ( ! ! miner ) {
log ( 'warn' , logSystem , 'Miner timed out and disconnected %s@%s' , [ miner . login , miner . ip ] ) ;
2022-01-29 16:12:19 -06:00
removeConnectedWorker ( miner , 'timeout' )
delete connectedMiners [ miner _id ]
2022-01-29 15:56:23 -06:00
}
} ) ;
}
socketMap . delete [ socket ]
2022-01-15 11:31:38 -06:00
} ) ;
} ;
if ( portData . ssl ) {
if ( ! config . poolServer . sslCert ) {
log ( 'error' , logSystem , 'Could not start server listening on port %d (SSL): SSL certificate not configured' , [ portData . port ] ) ;
cback ( true ) ;
} else if ( ! config . poolServer . sslKey ) {
log ( 'error' , logSystem , 'Could not start server listening on port %d (SSL): SSL key not configured' , [ portData . port ] ) ;
cback ( true ) ;
} else if ( ! fs . existsSync ( config . poolServer . sslCert ) ) {
log ( 'error' , logSystem , 'Could not start server listening on port %d (SSL): SSL certificate file not found (configuration error)' , [ portData . port ] ) ;
cback ( true ) ;
} else if ( ! fs . existsSync ( config . poolServer . sslKey ) ) {
log ( 'error' , logSystem , 'Could not start server listening on port %d (SSL): SSL key file not found (configuration error)' , [ portData . port ] ) ;
cback ( true ) ;
} else {
let options = {
key : fs . readFileSync ( config . poolServer . sslKey ) ,
cert : fs . readFileSync ( config . poolServer . sslCert ) ,
} ;
if ( config . poolServer . sslCA && fs . existsSync ( config . poolServer . sslCA ) ) {
options . ca = fs . readFileSync ( config . poolServer . sslCA )
}
tls . createServer ( options , socketResponder ) . listen ( portData . port , function ( error , result ) {
if ( error ) {
log ( 'error' , logSystem , 'Could not start server listening on port %d (SSL), error: $j' , [ portData . port , error ] ) ;
cback ( true ) ;
return ;
}
log ( 'info' , logSystem , 'Clear values for SSL port %d in redis database.' , [ portData . port ] ) ;
redisClient . del ( config . coin + ':ports:' + portData . port ) ;
redisClient . hset ( config . coin + ':ports:' + portData . port , 'port' , portData . port ) ;
log ( 'info' , logSystem , 'Started server listening on port %d (SSL)' , [ portData . port ] ) ;
cback ( ) ;
} ) ;
}
}
else {
net . createServer ( socketResponder ) . listen ( portData . port , function ( error , result ) {
if ( error ) {
log ( 'error' , logSystem , 'Could not start server listening on port %d, error: $j' , [ portData . port , error ] ) ;
cback ( true ) ;
return ;
}
log ( 'info' , logSystem , 'Clear values for port %d in redis database.' , [ portData . port ] ) ;
redisClient . del ( config . coin + ':ports:' + portData . port ) ;
redisClient . hset ( config . coin + ':ports:' + portData . port , 'port' , portData . port ) ;
log ( 'info' , logSystem , 'Started server listening on port %d' , [ portData . port ] ) ;
cback ( ) ;
} ) ;
}
} , function ( err ) {
if ( err )
callback ( false ) ;
else
callback ( true ) ;
} ) ;
}
/ * *
* Initialize pool server
* * /
( function init ( loop ) {
async . waterfall ( [
function ( callback ) {
if ( ! poolStarted ) {
startPoolServerTcp ( function ( successful ) { poolStarted = true } ) ;
setTimeout ( init , 1000 , loop ) ;
return ;
}
callback ( true )
}
] ,
function ( err ) {
if ( loop === true ) {
setTimeout ( function ( ) {
init ( true ) ;
} , config . poolServer . blockRefreshInterval ) ;
}
}
) ;
} ) ( ) ;