-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathjobManager.js
393 lines (320 loc) · 13.2 KB
/
jobManager.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
var events = require('events');
var crypto = require('crypto');
var SHA3 = require('sha3');
var async = require('async');
var http = require('http');
var bignum = require('bignum');
var BigInt = require('big-integer');
var util = require('./util.js');
var daemon = require('./daemon.js');
var blockTemplate = require('./blockTemplate.js');
// Unique extranonce per subscriber
var ExtraNonceCounter = function () {
this.next = function () {
return(crypto.randomBytes(3).toString('hex'));
};
};
//Unique job per new block template
var JobCounter = function () {
var counter = 0x0000cccc;
this.next = function () {
counter++;
if (counter % 0xffffffffff === 0) counter = 1;
return this.cur();
};
this.cur = function () {
var counter_buf = new Buffer(32);
counter_buf.writeUIntBE('000000000000000000000000', 0, 24);
counter_buf.writeUIntBE(counter, 24, 8);
return counter_buf.toString('hex');
};
};
function isHexString(s) {
var check = String(s).toLowerCase();
if(check.length % 2) {
return false;
}
for (i = 0; i < check.length; i=i+2) {
var c = check[i] + check[i+1];
if (!isHex(c))
return false;
}
return true;
}
function isHex(c) {
var a = parseInt(c,16);
var b = a.toString(16).toLowerCase();
if(b.length % 2) { b = '0' + b; }
if (b !== c) { return false; }
return true;
}
/**
* Emits:
* - newBlock(blockTemplate) - When a new block (previously unknown to the JobManager) is added, use this event to broadcast new jobs
* - share(shareData, blockHex) - When a worker submits a share. It will have blockHex if a block was found
**/
var JobManager = module.exports = function JobManager(options) {
var emitLog = function (text) { _this.emit('log', 'debug', text); };
var emitWarningLog = function (text) { _this.emit('log', 'warning', text); };
var emitErrorLog = function (text) { _this.emit('log', 'error', text); };
var emitSpecialLog = function (text) { _this.emit('log', 'special', text); };
//private members
var _this = this;
var jobCounter = new JobCounter();
function SetupJobDaemonInterface(finishedCallback) {
if (!Array.isArray(options.daemons) || options.daemons.length < 1) {
emitErrorLog('No daemons have been configured - pool cannot start');
return;
}
_this.daemon = new daemon.interface(options.daemons, function (severity, message) {
_this.emit('log', severity, message);
});
_this.daemon.once('online', function () {
// console.log("The util daemon is alive.");
finishedCallback();
}).on('connectionFailed', function (error) {
emitErrorLog('Failed to connect daemon(s): ' + JSON.stringify(error));
}).on('error', function (message) {
emitErrorLog(message);
});
_this.daemon.init();
}
SetupJobDaemonInterface(function () {});
var shareMultiplier = algos[options.coin.algorithm].multiplier;
//public members
this.extraNonceCounter = new ExtraNonceCounter();
this.currentJob;
this.validJobs = {};
var hashDigest = algos[options.coin.algorithm].hash(options.coin);
var coinbaseHasher = (function () {
switch (options.coin.algorithm) {
default:
return util.sha256d;
}
})();
var blockHasher = (function () {
switch (options.coin.algorithm) {
case 'sha1':
return function (d) {
return util.reverseBuffer(util.sha256d(d));
};
default:
return function (d) {
return util.reverseBuffer(util.sha256(d));
};
}
})();
this.updateCurrentJob = function (rpcData) {
var tmpBlockTemplate = new blockTemplate(
jobCounter.next(),
rpcData,
options.coin.reward,
options.recipients,
options.address
);
_this.currentJob = tmpBlockTemplate;
_this.emit('updatedBlock', tmpBlockTemplate, true);
_this.validJobs[tmpBlockTemplate.jobId] = tmpBlockTemplate;
};
//returns true if processed a new block
this.processTemplate = function (rpcData) {
/* Block is new if A) its the first block we have seen so far or B) the blockhash is different and the
block height is greater than the one we have */
var isNewBlock = typeof(_this.currentJob) === 'undefined';
if (!isNewBlock && _this.currentJob.rpcData.previousblockhash !== rpcData.previousblockhash) {
isNewBlock = true;
//If new block is outdated/out-of-sync than return
if (rpcData.height < _this.currentJob.rpcData.height) return false;
}
if (!isNewBlock) return false;
var tmpBlockTemplate = new blockTemplate(
jobCounter.next(),
rpcData,
options.coin.reward,
options.recipients,
options.address
);
this.currentJob = tmpBlockTemplate;
this.validJobs = {};
_this.emit('newBlock', tmpBlockTemplate);
this.validJobs[tmpBlockTemplate.jobId] = tmpBlockTemplate;
return true;
};
this.processShare = function (miner_given_jobId, previousDifficulty, difficulty, miner_given_nonce, ipAddress, port, workerName, miner_given_header, miner_given_mixhash, extraNonce1, callback_parent) {
var submitTime = Date.now() / 1000 | 0;
var shareError = function (error) {
_this.emit('share', {
job: miner_given_jobId,
ip: ipAddress,
worker: workerName,
difficulty: difficulty,
error: error[1]
});
callback_parent( {error: error, result: null});
return;
};
var job = this.validJobs[miner_given_jobId];
console.log("JOB: "+JSON.stringify(job));
if (typeof job === 'undefined' || job.jobId != miner_given_jobId)
return shareError([20, 'job not found']);
//calculate our own header hash, do not trust miner-given value
var headerBuffer = job.serializeHeader(); // 140 bytes, doesn't contain nonce or mixhash/solution
var header_hash = util.reverseBuffer(util.sha256d(headerBuffer)).toString('hex');
if (job.curTime < (submitTime - 600))
return shareError([20, 'job is too old']);
if (!isHexString(miner_given_header))
return shareError([20, 'invalid header hash, must be hex']);
if (header_hash != miner_given_header)
return shareError([20, 'invalid header hash']);
if (!isHexString(miner_given_nonce))
return shareError([20, 'invalid nonce, must be hex']);
if (!isHexString(miner_given_mixhash))
return shareError([20, 'invalid mixhash, must be hex']);
if (miner_given_nonce.length !== 16)
return shareError([20, 'incorrect size of nonce, must be 8 bytes']);
if (miner_given_mixhash.length !== 64)
return shareError([20, 'incorrect size of mixhash, must be 32 bytes']);
if (miner_given_nonce.indexOf(extraNonce1.substring(0,4)) !== 0)
return shareError([24, 'nonce out of worker range']);
if (!job.registerSubmit(header_hash.toLowerCase(), miner_given_nonce.toLowerCase()))
return shareError([22, 'duplicate share']);
var powLimit = algos.kawpow.diff; // TODO: Get algos object from argument
var adjPow = powLimit / difficulty;
if ((64 - adjPow.toString(16).length) === 0) {
var zeroPad = '';
}
else {
var zeroPad = '0';
zeroPad = zeroPad.repeat((64 - (adjPow.toString(16).length)));
}
var target_share_hex = (zeroPad + adjPow.toString(16)).substr(0,64);
var blockHashInvalid;
var blockHash;
var blockHex;
console.log("Using "+options.kawpow_validator+" for validation.");
if (options.kawpow_validator == "kawpowd") {
async.series([
function(callback) {
var kawpowd_url = 'http://'+options.kawpow_wrapper_host+":"+options.kawpow_wrapper_port+'/'+'?header_hash='+header_hash+'&mix_hash='+miner_given_mixhash+'&nonce='+miner_given_nonce+'&height='+job.rpcData.height+'&share_boundary='+target_share_hex+'&block_boundary='+job.target_hex;
http.get(kawpowd_url, function (res) {
res.setEncoding("utf8");
let body = "";
res.on("data", data => {
body += data;
});
res.on("end", () => {
body = JSON.parse(body);
// console.log("JSON RESULT FROM KAWPOWD: "+JSON.stringify(body));
console.log("********** INCOMING SHARE FROM WORKER ************");
console.log("header_hash = " + header_hash);
console.log("miner_sent_header_hash = " + miner_given_header);
console.log("miner_sent_mixhash = " + miner_given_mixhash);
console.log("miner_sent_nonce = " + miner_given_nonce);
console.log("height = " + job.rpcData.height);
console.log("job.difficulty = " + job.difficulty);
console.log("BLOCK.target = " + job.target_hex);
console.log('SHARE.target = ' + target_share_hex);
console.log('digest = ' + body.digest);
console.log("miner_sent_jobid = " + miner_given_jobId);
console.log('job = ' + miner_given_jobId);
console.log('worker = ' + workerName);
console.log('height = ' + job.rpcData.height);
console.log('difficulty = ' + difficulty);
console.log('kawpowd_url = ' + kawpowd_url);
console.log("********** END INCOMING SHARE FROM WORKER ************");
if (body.share == false) {
if (body.block == false) {
// It didn't meet either requirement.
callback('kawpow share didn\'t meet job or block difficulty level', false);
return shareError([20, 'kawpow validation failed']);
}
}
// At this point, either share or block is true (or both)
if (body.block == true) {
// Good block.
blockHex = job.serializeBlock(new Buffer(header_hash, 'hex'), new Buffer(miner_given_nonce, 'hex'), new Buffer(miner_given_mixhash, 'hex')).toString('hex');
blockHash = body.digest;
}
callback(null, true);
return;
});
});
},
function(callback) {
var blockDiffAdjusted = job.difficulty * shareMultiplier
var shareDiffFixed = undefined;
if (blockHash !== undefined) {
var headerBigNum = bignum.fromBuffer(blockHash, {endian: 'little', size: 32});
var shareDiff = diff1 / headerBigNum.toNumber() * shareMultiplier;
shareDiffFixed = shareDiff.toFixed(8);
}
_this.emit('share', {
job: miner_given_jobId,
ip: ipAddress,
port: port,
worker: workerName,
height: job.rpcData.height,
blockReward: job.rpcData.coinbasevalue,
difficulty: difficulty,
shareDiff: shareDiffFixed,
blockDiff: blockDiffAdjusted,
blockDiffActual: job.difficulty,
blockHash: blockHash,
blockHashInvalid: blockHashInvalid
}, blockHex);
callback_parent({result: true, error: null, blockHash: blockHash});
callback(null, true);
return;
}
], function(err, results) {
if (err != null) {
emitErrorLog("kawpow verify failed, ERRORS: "+err);
return;
}
});
} else {
_this.daemon.cmd('getkawpowhash', [ header_hash, miner_given_mixhash, miner_given_nonce, job.rpcData.height, job.target_hex ], function (results) {
var digest = results[0].response.digest;
var result = results[0].response.result;
var mix_hash = results[0].response.mix_hash;
var meets_target = results[0].response.meets_target;
if (result == 'true') {
// console.log("SHARE IS VALID");
let headerBigNum = BigInt(result, 32);
if (job.target.ge(headerBigNum)) {
// console.log("BLOCK CANDIDATE");
var blockHex = job.serializeBlock(new Buffer(header_hash, 'hex'), new Buffer(miner_given_nonce, 'hex'), new Buffer(mix_hash, 'hex')).toString('hex');
var blockHash = digest;
}
var blockDiffAdjusted = job.difficulty * shareMultiplier
var shareDiffFixed = undefined;
if (blockHash !== undefined) {
var shareDiff = diff1 / headerBigNum * shareMultiplier;
shareDiffFixed = shareDiff.toFixed(8);
}
_this.emit('share', {
job: miner_given_jobId,
ip: ipAddress,
port: port,
worker: workerName,
height: job.rpcData.height,
blockReward: job.rpcData.coinbasevalue,
difficulty: difficulty,
shareDiff: shareDiffFixed,
blockDiff: blockDiffAdjusted,
blockDiffActual: job.difficulty,
blockHash: blockHash,
blockHashInvalid: blockHashInvalid
}, blockHex);
// return {result: true, error: null, blockHash: blockHash};
// callback_parent( {error: error, result: null});
callback_parent({result: true, error: null, blockHash: blockHash});
} else {
// console.log("SHARE FAILED");
return shareError([20, 'bad share: invalid hash']);
}
});
}
}
};
JobManager.prototype.__proto__ = events.EventEmitter.prototype;