This commit is contained in:
manfromafar 2021-04-30 17:45:04 -06:00
parent 8d38611b7c
commit 4a37c92f11
70 changed files with 12999 additions and 0 deletions

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

25
config.json Normal file
View file

@ -0,0 +1,25 @@
{
"loglevel": -1,
"miners": [
{
"cryptoname": "hns",
"minername": ["Goldshell-HS1", "Goldshell-HS1-Plus"],
"pool": {
"host": "hns.ss.dxpool.com",
"port": 3008,
"user": "peterfu.bb",
"pass": "x"
}
},
{
"cryptoname": "lbc",
"minername": ["Goldshell-LB1"],
"pool": {
"host": "lbc.viabtc.com",
"port": 3002,
"user": "sfgaqw.1",
"pass": "x"
}
}
]
}

84
dashboard.js Normal file
View file

@ -0,0 +1,84 @@
var blessed = require('blessed')
, contrib = require('blessed-contrib')
var version = require('./package.json').version
var platform = require('os').platform()
class CliDraw {
constructor() {
this.screen = null
this.grid = null
this.totalTable = null
this.detailsTable = null
this.initBlessed()
}
initBlessed() {
this.screen = blessed.screen()
this.grid = new contrib.grid({rows: 12, cols: 12, screen: this.screen})
this.screen.key(['escape', 'q', 'C-c', 'C-z'], function(ch, key) {
this.screen.destroy();
return process.exit(0);
});
this.initSurface()
}
initSurface() {
this.totalTable = this.grid.set(0, 0, 12, 2, contrib.table, {
keys: true,
fg: 'white',
selectedFg: 'white',
selectedBg: 'blue',
interactive: false,
label: 'Goldshell Miner Total',
width: '100%',
height: '100%',
border: {
type: "line",
fg: "cyan"
},
columnSpacing: 2, //in chars
columnWidth: [18, 10] /*in chars*/
})
var localcolumnWidth = null;
if (platform === 'darwin') {
localcolumnWidth = [8, 18, 8, 8, 8, 36, 14, 14, 10, 10, 10, 16, 12]
} else if (platform === 'win32') {
localcolumnWidth = [8, 18, 8, 8, 8, 8, 14, 14, 10, 10, 10, 16, 12]
} else {
localcolumnWidth = [8, 18, 8, 8, 8, 12, 14, 14, 10, 10, 10, 16, 12]
}
this.detailsTable = this.grid.set(0, 2, 12, 10, contrib.table, {
keys: true,
fg: 'white',
selectedFg: 'white',
selectedBg: 'blue',
interactive: true,
label: 'Goldshell Miner Details' + ' (' + version + ')',
width: '100%',
height: '100%',
border: {
type: "line",
fg: "cyan"
},
columnSpacing: 2, //in chars
columnWidth: localcolumnWidth
})
this.detailsTable.focus()
this.screen.render()
}
updateTotalTable(headers, stats) {
this.totalTable.setData({ headers: headers, data: stats})
this.screen.render()
}
updateDetailsTable(headers, stats) {
this.detailsTable.setData({ headers: headers, data: stats})
this.screen.render()
}
}
module.exports = CliDraw;

250
entrance.js Normal file
View file

@ -0,0 +1,250 @@
const IntMiner = require('./src');
const CFonts = require('cfonts');
const Debug = require('./src/log')();
const argv = require('minimist')(process.argv.slice(2));
const fs = require('fs');
const CliDraw = require("./dashboard");
const supports = [
{
algoname: 'blake2bsha3',
minername: ['Goldshell-HS1', 'Goldshell-HS1-Plus'],
cryptoname: 'hns',
protocolname: 'stratum',
pool: null
},
{
algoname: 'lbry',
minername: ['Goldshell-LB1'],
cryptoname: 'lbc',
protocolname: 'stratum',
pool: null
}
]
function help() {
const text = require('fs').createReadStream(`${__dirname}/help`);
text.pipe(process.stderr);
text.on('close', () => process.exit(1));
}
function isSubSet(Arr_A, Arr_B) {
if(!(Arr_A instanceof Array)||!(Arr_B instanceof Array)||((Arr_A.length > Arr_B.length))) {
return false;
}
return Arr_A.every(function(A) {
return Arr_B.includes(A);
})
}
function isJson(obj){
var isjson = typeof(obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && !obj.length;
return isjson;
}
function existParameter(obj, name) {
if (isJson(obj) && name && obj.hasOwnProperty(name)) {
return true;
}
return false;
}
function getMinerParameters(config) {
var Miners = [];
for (var i = 0; i < supports.length; i++) {
for (var j = 0; j < config.length; j++) {
if (supports[i].cryptoname === config[j].cryptoname && isSubSet(config[j].minername, supports[i].minername)) {
Miners.push(supports[i]);
}
}
}
return Miners;
}
const shows = [
{ h: "miningName", v: " Model"},
{ h: "state", v: "Status"},
{ h: "miningType", v: "Type"},
{ h: "version", v: "Version"},
// { h: "miningSN", v: " MinerID"},
{ h: "comm", v: " Port"},
{ h: "hashrate", v: "Hashrate 20s"},
{ h: "avHashrate", v: "Hashrate avg"},
{ h: "hardwareErr", v: "HW Error" },
{ h: "rejected", v: "Rejected"},
{ h: "accepted", v: "Accepted"},
{ h: "temperatue", v: "Temperature ℃"},
{ h: "elapsed", v: " Elapsed"}
];
const sumHeader = [
" Model",
"Count"
];
function convertHeaders() {
var showHeads = [];
for (var i = 0; i < shows.length; i++) {
if (i === 0)
showHeads.push("Index");
showHeads.push(shows[i].v);
}
return showHeads;
}
function convertContent(miners) {
var showMinerParameters = [];
for (var j = 0; j < miners.length; j++) {
var showMinerParameter = [];
for (var i = 0; i < shows.length; i++) {
if (i === 0)
showMinerParameter.push(j);
showMinerParameter.push(miners[j][shows[i].h]);
}
showMinerParameters.push(showMinerParameter);
}
return showMinerParameters;
}
function sumup(miners) {
var sums = [];
var active = 0;
sums.push(["Active Sum", 0]);
sums.push(["Inactive Sum", 0]);
sums.push(['', ''])
for (var j = 0; j < miners.length; j++) {
if (miners[j].state === 'on')
active++;
for (var i = 0; i < sums.length; i++) {
if (miners[j].miningName === sums[i][0]) {
sums[i][1]++;
break;
}
}
if (i === sums.length) {
sums.push([miners[j].miningName, 1]);
}
}
if (miners.length) {
sums[0][1] = active;
sums[1][1] = miners.length - active;
}
return sums;
}
(async () => {
var devState = []
var MinerParameters = null;
var debugScreen = false;
var configFile = null;
var loglevel = 1;
var config = argv.config || './config.json';
var dashboard = null;
var headers = null;
if (argv.help || argv.h) {
help();
return;
}
if (fs.existsSync(config)) {
try {
configFile = JSON.parse(fs.readFileSync(config, 'utf-8'));
} catch(e) {
help();
return;
}
} else {
console.log('Config file is not exist:', config);
return;
}
loglevel = argv.loglevel ? argv.loglevel : (existParameter(configFile, 'loglevel') ? configFile.loglevel : 1);
if (loglevel !== 1) {
Debug.IbctSetLogLevel(loglevel);
if (loglevel < 0)
debugScreen = true;
}
for (i = 0; i < configFile.miners.length; i++) {
if (!existParameter(configFile.miners[i], 'cryptoname')) {
console.log('Parameter cryptoname is not exist in config.json');
help();
return null
}
if (!existParameter(configFile.miners[i], 'minername')) {
console.log('Parameter minername is not exist in config.json');
help();
return null
}
if (!existParameter(configFile.miners[i], 'pool')) {
console.log('Parameter pool is not exist in config.json');
help();
return null
}
}
MinerParameters = getMinerParameters(configFile.miners);
if (!MinerParameters.length) {
help();
return null
}
if (debugScreen === true) {
dashboard = new CliDraw();
headers = convertHeaders();
}
const miner = await IntMiner({MinerParameters: MinerParameters});
// init Mining
await miner.initMining();
// set pool
for (i = 0; i < configFile.miners.length; i++) {
miner.setMiningConfig('pool', configFile.miners[i].cryptoname, configFile.miners[i].pool);
}
// start Mining
await miner.connectMining();
await miner.startMining(null);
miner.on('plug-in', async (data) => {
Debug.IbctLogDbg('plug-in: ', data.devID);
await miner.connectMining({'devID': data.devID});
miner.startMining({
'devID': data.devID
});
});
miner.on('plug-out', data => {
Debug.IbctLogDbg('plug-out: ', data.devID);
// miner.stopMining({ 'devId': data.devID });
});
miner.on("error", function (devID, data) {
if (devID)
Debug.IbctLogErr('Miner' + devID + ':', data);
else
Debug.IbctLogErr(data);
});
miner.on("warning", function (devID, data) {
if (devID)
Debug.IbctLogDbg('Miner' + devID + ':', data);
else
Debug.IbctLogDbg(data);
});
setInterval(function () {
devState = miner.getMiningStatus();
if (debugScreen === true) {
dashboard.updateTotalTable(sumHeader, sumup(devState))
dashboard.updateDetailsTable(headers, convertContent(devState))
} else
console.log(JSON.stringify(devState));
}, 1000);
})();

25
help Normal file
View file

@ -0,0 +1,25 @@
Usage:
goldshellminer-win || goldshellminer-linux || goldshellminer-macos
Exe cmd:
goldshellminer-* --config [YOUR-CONFIG-FILE]
Options:
--config Config file
--loglevel -1 (dashboard mode), 0 ~ 3 (debug mode: debug level from err to info, 3 show all log)
--help
Json settings:
loglevel: -1 (dashboard mode), 0 ~ 3 (debug mode: debug level from err to info, 3 show all log)
miners:
cryptoname: support hns
minername: support Goldshell-HS1, Goldshell-HS1-Plus
pool: (pool settings, like this)
{
host: `hns.ss.dxpool.com`,
port: 3008,
user: `user.worker`,
pass: 'x'
}

103
package.json Normal file
View file

@ -0,0 +1,103 @@
{
"_from": "goldshellminer",
"_id": "goldshellminer@2.2.4",
"_inBundle": false,
"_integrity": "sha512-ezVtXyC8NONvXKiGMRYYbLXl1ZfuJLs2n2q4XGeLYVnRGYB4n8TlplM/Y8JHsDbdodGv8POxfz3PvrdYuctkmw==",
"_location": "/goldshellminer",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "goldshellminer",
"name": "goldshellminer",
"escapedName": "goldshellminer",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"bin": "entrance.js",
"pkg": {
"assets": [
"./help",
"node_modules/blessed/**/*"
]
},
"scripts": {
"build": "pkg . --out-path ./bin",
"dashboard": "node entrance.js"
},
"bundleDependencies": false,
"dependencies": {
"@types/node": "^8.0.53",
"@types/ws": "^3.2.0",
"axios": "^0.16.1",
"basic-auth": "^2.0.0",
"bindings": "^1.3.0",
"blessed": "^0.1.81",
"blessed-contrib": "^4.8.21",
"blessed-xterm": "^1.4.1",
"cfonts": "^2.9.1",
"chacha20": "^0.1.4",
"chalk": "^4.1.0",
"crc32": "^0.2.2",
"elegant-spinner": "^1.0.1",
"express": "^4.15.4",
"i18n": "^0.10.0",
"ibctminerscrypt": "^1.0.12",
"locks": "^0.2.2",
"log-update": "^2.1.0",
"minimist": "^1.2.0",
"moment": "^2.19.1",
"nan": "^2.13.2",
"net-ping": "^1.2.3",
"node-getopt": "^0.3.2",
"pmx": "^1.5.5",
"prebuild-install": "^5.1.0",
"serialport": "^7.1.4",
"sha1": "^1.1.1",
"stratum": "^0.2.4",
"tty-table": "^2.5.5",
"typescript": "^2.6.1",
"usb-detection": "^4.3.0",
"uuid": "^3.1.0",
"wait-until": "0.0.2",
"watch": "^1.0.2",
"word-table": "^1.0.3",
"ws": "^3.2.0"
},
"deprecated": false,
"devDependencies": {
"expect": "^21.1.0",
"mocha": "^3.5.3",
"node-gyp": "^3.8.0",
"prebuild": "^8.0.1"
},
"engines": {
"node": ">=8.0",
"npm": ">=5.0"
},
"keywords": [
"monero miner",
"xmr miner",
"monero",
"electroneum",
"xmr",
"etn",
"crypto",
"cryptocurrency",
"cryptocurrencies",
"mining",
"miner",
"bitcoin",
"stratum",
"pool"
],
"license": "MIT",
"main": "src/index.js",
"name": "goldshellminer",
"version": "2.2.5"
}

52
src/algo/algo.js Normal file
View file

@ -0,0 +1,52 @@
const blake2bsha3Api = require('./blake2bsha3Api');
const lbryApi = require('./lbryApi');
const EventEmitter = require('events');
const Debug = require('../log')();
const COMP = '[algo]';
const algorithms = [
{
name: 'lbry',
api: lbryApi
},
{
name: 'blake2bsha3',
api: blake2bsha3Api
}
];
class algoApi extends EventEmitter {
constructor({
name
}) {
super();
var _this = this;
_this.name = name;
_this.algorithm = this.GetAlgorithmApi(_this.name);
if (!_this.algorithm) {
_this.emit('error', __('不支持此种算法:'), _this.name);
}
return _this;
}
GetAlgorithmApi(name) {
var algo = null;
algorithms.forEach(function (algorithm, index) {
if (algorithm.name === name) {
algo = algorithm;
}
})
return algo ? algo.api() : null;
}
getAlgoName() {
return this.algorithm ? this.algorithm.getAlgoName() : null;
}
genHash(data, length, varity) {
return this.algorithm ? this.algorithm.genHash(data, length, varity) : null;
}
};
module.exports = function RunAlgoApi(options = {}) {
return new algoApi(options);
};

28
src/algo/base/assert.js Normal file
View file

@ -0,0 +1,28 @@
/*!
* assert.js - assert for bcrypto
* Copyright (c) 2020, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcrypto
*/
'use strict';
/*
* Assert
*/
function assert(val, msg) {
if (!val) {
const err = new Error(msg || 'Assertion failed');
if (Error.captureStackTrace)
Error.captureStackTrace(err, assert);
throw err;
}
}
/*
* Expose
*/
module.exports = assert;

356
src/algo/base/blake2b.js Normal file
View file

@ -0,0 +1,356 @@
/*!
* blake2b.js - BLAKE2b implementation for bcrypto
* Copyright (c) 2017-2019, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcrypto
*
* Parts of this software are based on dcposch/blakejs:
* Daniel Clemens Posch (CC0)
* https://github.com/dcposch/blakejs/blob/master/blake2b.js
*
* Resources:
* https://en.wikipedia.org/wiki/BLAKE_(hash_function)
* https://tools.ietf.org/html/rfc7693
*/
'use strict';
const assert = require('./assert');
const HMAC = require('./hmac');
/*
* Constants
*/
const FINALIZED = 0x80000000;
const IV = new Uint32Array([
0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85,
0xfe94f82b, 0x3c6ef372, 0x5f1d36f1, 0xa54ff53a,
0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c,
0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19
]);
const SIGMA = new Uint8Array([
0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e,
0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
0x1c, 0x14, 0x08, 0x10, 0x12, 0x1e, 0x1a, 0x0c,
0x02, 0x18, 0x00, 0x04, 0x16, 0x0e, 0x0a, 0x06,
0x16, 0x10, 0x18, 0x00, 0x0a, 0x04, 0x1e, 0x1a,
0x14, 0x1c, 0x06, 0x0c, 0x0e, 0x02, 0x12, 0x08,
0x0e, 0x12, 0x06, 0x02, 0x1a, 0x18, 0x16, 0x1c,
0x04, 0x0c, 0x0a, 0x14, 0x08, 0x00, 0x1e, 0x10,
0x12, 0x00, 0x0a, 0x0e, 0x04, 0x08, 0x14, 0x1e,
0x1c, 0x02, 0x16, 0x18, 0x0c, 0x10, 0x06, 0x1a,
0x04, 0x18, 0x0c, 0x14, 0x00, 0x16, 0x10, 0x06,
0x08, 0x1a, 0x0e, 0x0a, 0x1e, 0x1c, 0x02, 0x12,
0x18, 0x0a, 0x02, 0x1e, 0x1c, 0x1a, 0x08, 0x14,
0x00, 0x0e, 0x0c, 0x06, 0x12, 0x04, 0x10, 0x16,
0x1a, 0x16, 0x0e, 0x1c, 0x18, 0x02, 0x06, 0x12,
0x0a, 0x00, 0x1e, 0x08, 0x10, 0x0c, 0x04, 0x14,
0x0c, 0x1e, 0x1c, 0x12, 0x16, 0x06, 0x00, 0x10,
0x18, 0x04, 0x1a, 0x0e, 0x02, 0x08, 0x14, 0x0a,
0x14, 0x04, 0x10, 0x08, 0x0e, 0x0c, 0x02, 0x0a,
0x1e, 0x16, 0x12, 0x1c, 0x06, 0x18, 0x1a, 0x00,
0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e,
0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
0x1c, 0x14, 0x08, 0x10, 0x12, 0x1e, 0x1a, 0x0c,
0x02, 0x18, 0x00, 0x04, 0x16, 0x0e, 0x0a, 0x06
]);
/**
* BLAKE2b
*/
class BLAKE2b {
constructor() {
this.state = new Uint32Array(16);
this.V = new Uint32Array(32);
this.M = new Uint32Array(32);
this.block = Buffer.allocUnsafe(128);
this.size = 32;
this.count = 0;
this.pos = FINALIZED;
}
init(size, key) {
if (size == null)
size = 32;
assert((size >>> 0) === size);
assert(key == null || Buffer.isBuffer(key));
if (size === 0 || size > 64)
throw new Error('Bad output length.');
if (key && key.length > 64)
throw new Error('Bad key length.');
const klen = key ? key.length : 0;
for (let i = 0; i < 16; i++)
this.state[i] = IV[i];
this.size = size;
this.count = 0;
this.pos = 0;
this.state[0] ^= 0x01010000 ^ (klen << 8) ^ this.size;
if (klen > 0) {
const block = Buffer.alloc(128, 0x00);
key.copy(block, 0);
this.update(block);
}
return this;
}
update(data) {
//assert(Buffer.isBuffer(data));
//assert(!(this.pos & FINALIZED), 'Context is not initialized.');
let off = 0;
let len = data.length;
if (len > 0) {
const left = this.pos;
const fill = 128 - left;
if (len > fill) {
this.pos = 0;
data.copy(this.block, left, off, off + fill);
this.count += 128;
this._compress(this.block, 0, false);
off += fill;
len -= fill;
while (len > 128) {
this.count += 128;
this._compress(data, off, false);
off += 128;
len -= 128;
}
}
data.copy(this.block, this.pos, off, off + len);
this.pos += len;
}
return this;
}
final() {
//assert(!(this.pos & FINALIZED), 'Context is not initialized.');
this.count += this.pos;
this.block.fill(0, this.pos, 128);
this._compress(this.block, 0, true);
this.pos = FINALIZED;
const out = Buffer.allocUnsafe(this.size);
for (let i = 0; i < this.size; i++)
out[i] = this.state[i >>> 2] >>> (8 * (i & 3));
for (let i = 0; i < 16; i++)
this.state[i] = 0;
for (let i = 0; i < 32; i++) {
this.V[i] = 0;
this.M[i] = 0;
}
for (let i = 0; i < 128; i++)
this.block[i] = 0;
return out;
}
_compress(block, off, last) {
const { V, M } = this;
for (let i = 0; i < 16; i++) {
V[i] = this.state[i];
V[i + 16] = IV[i];
}
// uint128
V[24] ^= this.count;
V[25] ^= this.count * (1 / 0x100000000);
V[26] ^= 0;
V[27] ^= 0;
if (last) {
// last block
V[28] ^= -1;
V[29] ^= -1;
// last node
V[29] ^= 0;
V[30] ^= 0;
}
for (let i = 0; i < 32; i++) {
M[i] = readU32(block, off);
off += 4;
}
for (let i = 0; i < 12; i++) {
G(V, M, 0, 8, 16, 24, SIGMA[i * 16 + 0], SIGMA[i * 16 + 1]);
G(V, M, 2, 10, 18, 26, SIGMA[i * 16 + 2], SIGMA[i * 16 + 3]);
G(V, M, 4, 12, 20, 28, SIGMA[i * 16 + 4], SIGMA[i * 16 + 5]);
G(V, M, 6, 14, 22, 30, SIGMA[i * 16 + 6], SIGMA[i * 16 + 7]);
G(V, M, 0, 10, 20, 30, SIGMA[i * 16 + 8], SIGMA[i * 16 + 9]);
G(V, M, 2, 12, 22, 24, SIGMA[i * 16 + 10], SIGMA[i * 16 + 11]);
G(V, M, 4, 14, 16, 26, SIGMA[i * 16 + 12], SIGMA[i * 16 + 13]);
G(V, M, 6, 8, 18, 28, SIGMA[i * 16 + 14], SIGMA[i * 16 + 15]);
}
for (let i = 0; i < 16; i++)
this.state[i] ^= V[i] ^ V[i + 16];
}
static hash() {
return new BLAKE2b();
}
static hmac(size) {
return new HMAC(BLAKE2b, 128, [size]);
}
static digest(data, size, key) {
const { ctx } = BLAKE2b;
ctx.init(size, key);
ctx.update(data);
return ctx.final();
}
static root(left, right, size, key) {
if (size == null)
size = 32;
//assert(Buffer.isBuffer(left) && left.length === size);
//assert(Buffer.isBuffer(right) && right.length === size);
const { ctx } = BLAKE2b;
ctx.init(size, key);
ctx.update(left);
ctx.update(right);
return ctx.final();
}
static multi(x, y, z, size, key) {
const { ctx } = BLAKE2b;
ctx.init(size, key);
ctx.update(x);
ctx.update(y);
if (z)
ctx.update(z);
return ctx.final();
}
static mac(data, key, size) {
return BLAKE2b.hmac(size).init(key).update(data).final();
}
}
/*
* Static
*/
BLAKE2b.native = 0;
BLAKE2b.id = 'BLAKE2B256';
BLAKE2b.size = 32;
BLAKE2b.bits = 256;
BLAKE2b.blockSize = 128;
BLAKE2b.zero = Buffer.alloc(32, 0x00);
BLAKE2b.ctx = new BLAKE2b();
/*
* Helpers
*/
function sum64i(v, a, b) {
const o0 = v[a + 0] + v[b + 0];
const o1 = v[a + 1] + v[b + 1];
const c = (o0 >= 0x100000000) | 0;
v[a + 0] = o0;
v[a + 1] = o1 + c;
}
function sum64w(v, a, b0, b1) {
const o0 = v[a + 0] + b0;
const o1 = v[a + 1] + b1;
const c = (o0 >= 0x100000000) | 0;
v[a + 0] = o0;
v[a + 1] = o1 + c;
}
function G(v, m, a, b, c, d, ix, iy) {
const x0 = m[ix + 0];
const x1 = m[ix + 1];
const y0 = m[iy + 0];
const y1 = m[iy + 1];
sum64i(v, a, b);
sum64w(v, a, x0, x1);
const xor0 = v[d + 0] ^ v[a + 0];
const xor1 = v[d + 1] ^ v[a + 1];
v[d + 0] = xor1;
v[d + 1] = xor0;
sum64i(v, c, d);
const xor2 = v[b + 0] ^ v[c + 0];
const xor3 = v[b + 1] ^ v[c + 1];
v[b + 0] = (xor2 >>> 24) ^ (xor3 << 8);
v[b + 1] = (xor3 >>> 24) ^ (xor2 << 8);
sum64i(v, a, b);
sum64w(v, a, y0, y1);
const xor4 = v[d + 0] ^ v[a + 0];
const xor5 = v[d + 1] ^ v[a + 1];
v[d + 0] = (xor4 >>> 16) ^ (xor5 << 16);
v[d + 1] = (xor5 >>> 16) ^ (xor4 << 16);
sum64i(v, c, d);
const xor6 = v[b + 0] ^ v[c + 0];
const xor7 = v[b + 1] ^ v[c + 1];
v[b + 0] = (xor7 >>> 31) ^ (xor6 << 1);
v[b + 1] = (xor6 >>> 31) ^ (xor7 << 1);
}
function readU32(data, off) {
return (data[off++]
+ data[off++] * 0x100
+ data[off++] * 0x10000
+ data[off] * 0x1000000);
}
/*
* Expose
*/
module.exports = BLAKE2b;

118
src/algo/base/hmac.js Normal file
View file

@ -0,0 +1,118 @@
/*!
* hmac.js - hmac for bcrypto
* Copyright (c) 2016-2019, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcrypto
*
* Parts of this software are based on indutny/hash.js:
* Copyright (c) 2014, Fedor Indutny (MIT License).
* https://github.com/indutny/hash.js
*
* Resources:
* https://en.wikipedia.org/wiki/HMAC
* https://tools.ietf.org/html/rfc2104
* https://github.com/indutny/hash.js/blob/master/lib/hash/hmac.js
*/
'use strict';
const assert = require('./assert');
/**
* HMAC
*/
class HMAC {
/**
* Create an HMAC.
* @param {Function} Hash
* @param {Number} size
* @param {Array} [x=[]]
* @param {Array} [y=[]]
*/
constructor(Hash, size, x = [], y = []) {
assert(typeof Hash === 'function');
assert((size >>> 0) === size);
assert(Array.isArray(x));
assert(Array.isArray(y));
this.hash = Hash;
this.size = size;
this.x = x;
this.y = y;
this.inner = new Hash();
this.outer = new Hash();
}
/**
* Initialize HMAC context.
* @param {Buffer} data
*/
init(key) {
assert(Buffer.isBuffer(key));
// Shorten key
if (key.length > this.size) {
const Hash = this.hash;
const h = new Hash();
h.init(...this.x);
h.update(key);
key = h.final(...this.y);
assert(key.length <= this.size);
}
// Pad key
const pad = Buffer.allocUnsafe(this.size);
for (let i = 0; i < key.length; i++)
pad[i] = key[i] ^ 0x36;
for (let i = key.length; i < pad.length; i++)
pad[i] = 0x36;
this.inner.init(...this.x);
this.inner.update(pad);
for (let i = 0; i < key.length; i++)
pad[i] = key[i] ^ 0x5c;
for (let i = key.length; i < pad.length; i++)
pad[i] = 0x5c;
this.outer.init(...this.x);
this.outer.update(pad);
return this;
}
/**
* Update HMAC context.
* @param {Buffer} data
*/
update(data) {
this.inner.update(data);
return this;
}
/**
* Finalize HMAC context.
* @returns {Buffer}
*/
final() {
this.outer.update(this.inner.final(...this.y));
return this.outer.final(...this.y);
}
}
/*
* Expose
*/
module.exports = HMAC;

427
src/algo/base/keccak.js Normal file
View file

@ -0,0 +1,427 @@
/*!
* keccak.js - Keccak/SHA3 implementation for bcrypto
* Copyright (c) 2017-2019, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcrypto
*
* Parts of this software are based on emn178/js-sha3:
* Copyright (c) 2015-2017, Chen, Yi-Cyuan (MIT License).
* https://github.com/emn178/js-sha3
*
* Parts of this software are based on rhash/RHash:
* Copyright (c) 2005-2014, Aleksey Kravchenko
* https://github.com/rhash/RHash
*
* Resources:
* https://en.wikipedia.org/wiki/SHA-3
* https://keccak.team/specifications.html
* https://csrc.nist.gov/projects/hash-functions/sha-3-project/sha-3-standardization
* http://dx.doi.org/10.6028/NIST.FIPS.202
* https://github.com/rhash/RHash/blob/master/librhash/sha3.c
* https://github.com/emn178/js-sha3/blob/master/src/sha3.js
*/
'use strict';
const assert = require('./assert');
const HMAC = require('./hmac');
/*
* Constants
*/
const FINALIZED = 0x80000000;
const ROUND_CONST = new Uint32Array([
0x00000001, 0x00000000, 0x00008082, 0x00000000,
0x0000808a, 0x80000000, 0x80008000, 0x80000000,
0x0000808b, 0x00000000, 0x80000001, 0x00000000,
0x80008081, 0x80000000, 0x00008009, 0x80000000,
0x0000008a, 0x00000000, 0x00000088, 0x00000000,
0x80008009, 0x00000000, 0x8000000a, 0x00000000,
0x8000808b, 0x00000000, 0x0000008b, 0x80000000,
0x00008089, 0x80000000, 0x00008003, 0x80000000,
0x00008002, 0x80000000, 0x00000080, 0x80000000,
0x0000800a, 0x00000000, 0x8000000a, 0x80000000,
0x80008081, 0x80000000, 0x00008080, 0x80000000,
0x80000001, 0x00000000, 0x80008008, 0x80000000
]);
/**
* Keccak
*/
class Keccak {
constructor() {
this.state = new Uint32Array(50);
this.block = Buffer.allocUnsafe(168);
this.bs = 136;
this.pos = FINALIZED;
}
init(bits) {
if (bits == null)
bits = 256;
assert((bits & 0xffff) === bits);
assert(bits >= 128);
assert(bits <= 512);
const rate = 1600 - bits * 2;
assert(rate >= 0 && (rate & 63) === 0);
this.bs = rate / 8;
this.pos = 0;
return this;
}
update(data) {
assert(Buffer.isBuffer(data));
assert(!(this.pos & FINALIZED), 'Context is not initialized.');
let len = data.length;
let pos = this.pos;
let off = 0;
this.pos = (this.pos + len) % this.bs;
if (pos > 0) {
let want = this.bs - pos;
if (want > len)
want = len;
data.copy(this.block, pos, off, off + want);
pos += want;
len -= want;
off += want;
if (pos < this.bs)
return this;
this._transform(this.block, 0);
}
while (len >= this.bs) {
this._transform(data, off);
off += this.bs;
len -= this.bs;
}
if (len > 0)
data.copy(this.block, 0, off, off + len);
return this;
}
final(pad, len) {
if (pad == null)
pad = 0x01;
if (len == null || len === 0)
len = 100 - this.bs / 2;
assert((pad & 0xff) === pad);
assert((len >>> 0) === len);
assert(!(this.pos & FINALIZED), 'Context is not initialized.');
this.block.fill(0, this.pos, this.bs);
this.block[this.pos] |= pad;
this.block[this.bs - 1] |= 0x80;
this._transform(this.block, 0);
this.pos = FINALIZED;
assert(len < this.bs);
const out = Buffer.allocUnsafe(len);
for (let i = 0; i < len; i++)
out[i] = this.state[i >>> 2] >>> (8 * (i & 3));
for (let i = 0; i < 50; i++)
this.state[i] = 0;
for (let i = 0; i < this.bs; i++)
this.block[i] = 0;
return out;
}
_transform(block, off) {
const count = this.bs / 4;
const s = this.state;
for (let i = 0; i < count; i++)
s[i] ^= readU32(block, off + i * 4);
for (let n = 0; n < 48; n += 2) {
const c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
const c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
const c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
const c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
const c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
const c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
const c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
const c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
const c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
const c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
const h0 = c8 ^ ((c2 << 1) | (c3 >>> 31));
const l0 = c9 ^ ((c3 << 1) | (c2 >>> 31));
const h1 = c0 ^ ((c4 << 1) | (c5 >>> 31));
const l1 = c1 ^ ((c5 << 1) | (c4 >>> 31));
const h2 = c2 ^ ((c6 << 1) | (c7 >>> 31));
const l2 = c3 ^ ((c7 << 1) | (c6 >>> 31));
const h3 = c4 ^ ((c8 << 1) | (c9 >>> 31));
const l3 = c5 ^ ((c9 << 1) | (c8 >>> 31));
const h4 = c6 ^ ((c0 << 1) | (c1 >>> 31));
const l4 = c7 ^ ((c1 << 1) | (c0 >>> 31));
s[0] ^= h0;
s[1] ^= l0;
s[10] ^= h0;
s[11] ^= l0;
s[20] ^= h0;
s[21] ^= l0;
s[30] ^= h0;
s[31] ^= l0;
s[40] ^= h0;
s[41] ^= l0;
s[2] ^= h1;
s[3] ^= l1;
s[12] ^= h1;
s[13] ^= l1;
s[22] ^= h1;
s[23] ^= l1;
s[32] ^= h1;
s[33] ^= l1;
s[42] ^= h1;
s[43] ^= l1;
s[4] ^= h2;
s[5] ^= l2;
s[14] ^= h2;
s[15] ^= l2;
s[24] ^= h2;
s[25] ^= l2;
s[34] ^= h2;
s[35] ^= l2;
s[44] ^= h2;
s[45] ^= l2;
s[6] ^= h3;
s[7] ^= l3;
s[16] ^= h3;
s[17] ^= l3;
s[26] ^= h3;
s[27] ^= l3;
s[36] ^= h3;
s[37] ^= l3;
s[46] ^= h3;
s[47] ^= l3;
s[8] ^= h4;
s[9] ^= l4;
s[18] ^= h4;
s[19] ^= l4;
s[28] ^= h4;
s[29] ^= l4;
s[38] ^= h4;
s[39] ^= l4;
s[48] ^= h4;
s[49] ^= l4;
const b0 = s[0];
const b1 = s[1];
const b32 = (s[11] << 4) | (s[10] >>> 28);
const b33 = (s[10] << 4) | (s[11] >>> 28);
const b14 = (s[20] << 3) | (s[21] >>> 29);
const b15 = (s[21] << 3) | (s[20] >>> 29);
const b46 = (s[31] << 9) | (s[30] >>> 23);
const b47 = (s[30] << 9) | (s[31] >>> 23);
const b28 = (s[40] << 18) | (s[41] >>> 14);
const b29 = (s[41] << 18) | (s[40] >>> 14);
const b20 = (s[2] << 1) | (s[3] >>> 31);
const b21 = (s[3] << 1) | (s[2] >>> 31);
const b2 = (s[13] << 12) | (s[12] >>> 20);
const b3 = (s[12] << 12) | (s[13] >>> 20);
const b34 = (s[22] << 10) | (s[23] >>> 22);
const b35 = (s[23] << 10) | (s[22] >>> 22);
const b16 = (s[33] << 13) | (s[32] >>> 19);
const b17 = (s[32] << 13) | (s[33] >>> 19);
const b48 = (s[42] << 2) | (s[43] >>> 30);
const b49 = (s[43] << 2) | (s[42] >>> 30);
const b40 = (s[5] << 30) | (s[4] >>> 2);
const b41 = (s[4] << 30) | (s[5] >>> 2);
const b22 = (s[14] << 6) | (s[15] >>> 26);
const b23 = (s[15] << 6) | (s[14] >>> 26);
const b4 = (s[25] << 11) | (s[24] >>> 21);
const b5 = (s[24] << 11) | (s[25] >>> 21);
const b36 = (s[34] << 15) | (s[35] >>> 17);
const b37 = (s[35] << 15) | (s[34] >>> 17);
const b18 = (s[45] << 29) | (s[44] >>> 3);
const b19 = (s[44] << 29) | (s[45] >>> 3);
const b10 = (s[6] << 28) | (s[7] >>> 4);
const b11 = (s[7] << 28) | (s[6] >>> 4);
const b42 = (s[17] << 23) | (s[16] >>> 9);
const b43 = (s[16] << 23) | (s[17] >>> 9);
const b24 = (s[26] << 25) | (s[27] >>> 7);
const b25 = (s[27] << 25) | (s[26] >>> 7);
const b6 = (s[36] << 21) | (s[37] >>> 11);
const b7 = (s[37] << 21) | (s[36] >>> 11);
const b38 = (s[47] << 24) | (s[46] >>> 8);
const b39 = (s[46] << 24) | (s[47] >>> 8);
const b30 = (s[8] << 27) | (s[9] >>> 5);
const b31 = (s[9] << 27) | (s[8] >>> 5);
const b12 = (s[18] << 20) | (s[19] >>> 12);
const b13 = (s[19] << 20) | (s[18] >>> 12);
const b44 = (s[29] << 7) | (s[28] >>> 25);
const b45 = (s[28] << 7) | (s[29] >>> 25);
const b26 = (s[38] << 8) | (s[39] >>> 24);
const b27 = (s[39] << 8) | (s[38] >>> 24);
const b8 = (s[48] << 14) | (s[49] >>> 18);
const b9 = (s[49] << 14) | (s[48] >>> 18);
s[0] = b0 ^ (~b2 & b4);
s[1] = b1 ^ (~b3 & b5);
s[10] = b10 ^ (~b12 & b14);
s[11] = b11 ^ (~b13 & b15);
s[20] = b20 ^ (~b22 & b24);
s[21] = b21 ^ (~b23 & b25);
s[30] = b30 ^ (~b32 & b34);
s[31] = b31 ^ (~b33 & b35);
s[40] = b40 ^ (~b42 & b44);
s[41] = b41 ^ (~b43 & b45);
s[2] = b2 ^ (~b4 & b6);
s[3] = b3 ^ (~b5 & b7);
s[12] = b12 ^ (~b14 & b16);
s[13] = b13 ^ (~b15 & b17);
s[22] = b22 ^ (~b24 & b26);
s[23] = b23 ^ (~b25 & b27);
s[32] = b32 ^ (~b34 & b36);
s[33] = b33 ^ (~b35 & b37);
s[42] = b42 ^ (~b44 & b46);
s[43] = b43 ^ (~b45 & b47);
s[4] = b4 ^ (~b6 & b8);
s[5] = b5 ^ (~b7 & b9);
s[14] = b14 ^ (~b16 & b18);
s[15] = b15 ^ (~b17 & b19);
s[24] = b24 ^ (~b26 & b28);
s[25] = b25 ^ (~b27 & b29);
s[34] = b34 ^ (~b36 & b38);
s[35] = b35 ^ (~b37 & b39);
s[44] = b44 ^ (~b46 & b48);
s[45] = b45 ^ (~b47 & b49);
s[6] = b6 ^ (~b8 & b0);
s[7] = b7 ^ (~b9 & b1);
s[16] = b16 ^ (~b18 & b10);
s[17] = b17 ^ (~b19 & b11);
s[26] = b26 ^ (~b28 & b20);
s[27] = b27 ^ (~b29 & b21);
s[36] = b36 ^ (~b38 & b30);
s[37] = b37 ^ (~b39 & b31);
s[46] = b46 ^ (~b48 & b40);
s[47] = b47 ^ (~b49 & b41);
s[8] = b8 ^ (~b0 & b2);
s[9] = b9 ^ (~b1 & b3);
s[18] = b18 ^ (~b10 & b12);
s[19] = b19 ^ (~b11 & b13);
s[28] = b28 ^ (~b20 & b22);
s[29] = b29 ^ (~b21 & b23);
s[38] = b38 ^ (~b30 & b32);
s[39] = b39 ^ (~b31 & b33);
s[48] = b48 ^ (~b40 & b42);
s[49] = b49 ^ (~b41 & b43);
s[0] ^= ROUND_CONST[n + 0];
s[1] ^= ROUND_CONST[n + 1];
}
}
static hash() {
return new Keccak();
}
static hmac(bits, pad, len) {
if (bits == null)
bits = 256;
assert((bits >>> 0) === bits);
const rate = 1600 - bits * 2;
return new HMAC(Keccak, rate / 8, [bits], [pad, len]);
}
static digest(data, bits, pad, len) {
return Keccak.ctx.init(bits).update(data).final(pad, len);
}
static root(left, right, bits, pad, len) {
if (bits == null)
bits = 256;
if (len == null)
len = 0;
if (len === 0) {
assert((bits >>> 0) === bits);
len = bits >>> 3;
}
assert((len >>> 0) === len);
assert(Buffer.isBuffer(left) && left.length === len);
assert(Buffer.isBuffer(right) && right.length === len);
return Keccak.ctx.init(bits).update(left).update(right).final(pad, len);
}
static multi(x, y, z, bits, pad, len) {
const { ctx } = Keccak;
ctx.init(bits);
ctx.update(x);
ctx.update(y);
if (z)
ctx.update(z);
return ctx.final(pad, len);
}
static mac(data, key, bits, pad, len) {
return Keccak.hmac(bits, pad, len).init(key).update(data).final();
}
}
/*
* Static
*/
Keccak.native = 0;
Keccak.id = 'KECCAK256';
Keccak.size = 32;
Keccak.bits = 256;
Keccak.blockSize = 136;
Keccak.zero = Buffer.alloc(32, 0x00);
Keccak.ctx = new Keccak();
/*
* Helpers
*/
function readU32(data, off) {
return (data[off++]
+ data[off++] * 0x100
+ data[off++] * 0x10000
+ data[off] * 0x1000000);
}
/*
* Expose
*/
module.exports = Keccak;

71
src/algo/base/sha3.js Normal file
View file

@ -0,0 +1,71 @@
/*!
* sha3.js - SHA3 implementation for bcrypto
* Copyright (c) 2017-2019, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcrypto
*
* Resources:
* https://en.wikipedia.org/wiki/SHA-3
* https://keccak.team/specifications.html
* https://csrc.nist.gov/projects/hash-functions/sha-3-project/sha-3-standardization
* http://dx.doi.org/10.6028/NIST.FIPS.202
*/
'use strict';
const Keccak = require('./keccak');
/**
* SHA3
*/
class SHA3 extends Keccak {
constructor() {
super();
}
final() {
return super.final(0x06, null);
}
static hash() {
return new SHA3();
}
static hmac(bits) {
return super.hmac(bits, 0x06, null);
}
static digest(data, bits) {
return super.digest(data, bits, 0x06, null);
}
static root(left, right, bits) {
return super.root(left, right, bits, 0x06, null);
}
static multi(x, y, z, bits) {
return super.multi(x, y, z, bits, 0x06, null);
}
static mac(data, key, bits) {
return super.mac(data, key, bits, 0x06, null);
}
}
/*
* Static
*/
SHA3.native = 0;
SHA3.id = 'SHA3_256';
SHA3.size = 32;
SHA3.bits = 256;
SHA3.blockSize = 136;
SHA3.zero = Buffer.alloc(32, 0x00);
SHA3.ctx = new SHA3();
/*
* Expose
*/
module.exports = SHA3;

227
src/algo/base/sha3hns.js Normal file
View file

@ -0,0 +1,227 @@
'use strict';
const ROUND_CONST = new Uint32Array([
0x00000001, 0x00000000, 0x00008082, 0x00000000,
0x0000808a, 0x80000000, 0x80008000, 0x80000000,
0x0000808b, 0x00000000, 0x80000001, 0x00000000,
0x80008081, 0x80000000, 0x00008009, 0x80000000,
0x0000008a, 0x00000000, 0x00000088, 0x00000000,
0x80008009, 0x00000000, 0x8000000a, 0x00000000,
0x8000808b, 0x00000000, 0x0000008b, 0x80000000,
0x00008089, 0x80000000, 0x00008003, 0x80000000,
0x00008002, 0x80000000, 0x00000080, 0x80000000,
0x0000800a, 0x00000000, 0x8000000a, 0x80000000,
0x80008081, 0x80000000, 0x00008080, 0x80000000,
0x80000001, 0x00000000, 0x80008008, 0x80000000
]);
function update(data, s) {
const count = 34;
for (let i = 0; i < count; i++)
s[i] ^= data.readUInt32LE(i * 4);
for (let n = 0; n < 48; n += 2) {
const c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
const c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
const c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
const c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
const c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
const c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
const c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
const c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
const c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
const c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
const h0 = c8 ^ ((c2 << 1) | (c3 >>> 31));
const l0 = c9 ^ ((c3 << 1) | (c2 >>> 31));
const h1 = c0 ^ ((c4 << 1) | (c5 >>> 31));
const l1 = c1 ^ ((c5 << 1) | (c4 >>> 31));
const h2 = c2 ^ ((c6 << 1) | (c7 >>> 31));
const l2 = c3 ^ ((c7 << 1) | (c6 >>> 31));
const h3 = c4 ^ ((c8 << 1) | (c9 >>> 31));
const l3 = c5 ^ ((c9 << 1) | (c8 >>> 31));
const h4 = c6 ^ ((c0 << 1) | (c1 >>> 31));
const l4 = c7 ^ ((c1 << 1) | (c0 >>> 31));
s[0] ^= h0;
s[1] ^= l0;
s[10] ^= h0;
s[11] ^= l0;
s[20] ^= h0;
s[21] ^= l0;
s[30] ^= h0;
s[31] ^= l0;
s[40] ^= h0;
s[41] ^= l0;
s[2] ^= h1;
s[3] ^= l1;
s[12] ^= h1;
s[13] ^= l1;
s[22] ^= h1;
s[23] ^= l1;
s[32] ^= h1;
s[33] ^= l1;
s[42] ^= h1;
s[43] ^= l1;
s[4] ^= h2;
s[5] ^= l2;
s[14] ^= h2;
s[15] ^= l2;
s[24] ^= h2;
s[25] ^= l2;
s[34] ^= h2;
s[35] ^= l2;
s[44] ^= h2;
s[45] ^= l2;
s[6] ^= h3;
s[7] ^= l3;
s[16] ^= h3;
s[17] ^= l3;
s[26] ^= h3;
s[27] ^= l3;
s[36] ^= h3;
s[37] ^= l3;
s[46] ^= h3;
s[47] ^= l3;
s[8] ^= h4;
s[9] ^= l4;
s[18] ^= h4;
s[19] ^= l4;
s[28] ^= h4;
s[29] ^= l4;
s[38] ^= h4;
s[39] ^= l4;
s[48] ^= h4;
s[49] ^= l4;
const b0 = s[0];
const b1 = s[1];
const b32 = (s[11] << 4) | (s[10] >>> 28);
const b33 = (s[10] << 4) | (s[11] >>> 28);
const b14 = (s[20] << 3) | (s[21] >>> 29);
const b15 = (s[21] << 3) | (s[20] >>> 29);
const b46 = (s[31] << 9) | (s[30] >>> 23);
const b47 = (s[30] << 9) | (s[31] >>> 23);
const b28 = (s[40] << 18) | (s[41] >>> 14);
const b29 = (s[41] << 18) | (s[40] >>> 14);
const b20 = (s[2] << 1) | (s[3] >>> 31);
const b21 = (s[3] << 1) | (s[2] >>> 31);
const b2 = (s[13] << 12) | (s[12] >>> 20);
const b3 = (s[12] << 12) | (s[13] >>> 20);
const b34 = (s[22] << 10) | (s[23] >>> 22);
const b35 = (s[23] << 10) | (s[22] >>> 22);
const b16 = (s[33] << 13) | (s[32] >>> 19);
const b17 = (s[32] << 13) | (s[33] >>> 19);
const b48 = (s[42] << 2) | (s[43] >>> 30);
const b49 = (s[43] << 2) | (s[42] >>> 30);
const b40 = (s[5] << 30) | (s[4] >>> 2);
const b41 = (s[4] << 30) | (s[5] >>> 2);
const b22 = (s[14] << 6) | (s[15] >>> 26);
const b23 = (s[15] << 6) | (s[14] >>> 26);
const b4 = (s[25] << 11) | (s[24] >>> 21);
const b5 = (s[24] << 11) | (s[25] >>> 21);
const b36 = (s[34] << 15) | (s[35] >>> 17);
const b37 = (s[35] << 15) | (s[34] >>> 17);
const b18 = (s[45] << 29) | (s[44] >>> 3);
const b19 = (s[44] << 29) | (s[45] >>> 3);
const b10 = (s[6] << 28) | (s[7] >>> 4);
const b11 = (s[7] << 28) | (s[6] >>> 4);
const b42 = (s[17] << 23) | (s[16] >>> 9);
const b43 = (s[16] << 23) | (s[17] >>> 9);
const b24 = (s[26] << 25) | (s[27] >>> 7);
const b25 = (s[27] << 25) | (s[26] >>> 7);
const b6 = (s[36] << 21) | (s[37] >>> 11);
const b7 = (s[37] << 21) | (s[36] >>> 11);
const b38 = (s[47] << 24) | (s[46] >>> 8);
const b39 = (s[46] << 24) | (s[47] >>> 8);
const b30 = (s[8] << 27) | (s[9] >>> 5);
const b31 = (s[9] << 27) | (s[8] >>> 5);
const b12 = (s[18] << 20) | (s[19] >>> 12);
const b13 = (s[19] << 20) | (s[18] >>> 12);
const b44 = (s[29] << 7) | (s[28] >>> 25);
const b45 = (s[28] << 7) | (s[29] >>> 25);
const b26 = (s[38] << 8) | (s[39] >>> 24);
const b27 = (s[39] << 8) | (s[38] >>> 24);
const b8 = (s[48] << 14) | (s[49] >>> 18);
const b9 = (s[49] << 14) | (s[48] >>> 18);
s[0] = b0 ^ (~b2 & b4);
s[1] = b1 ^ (~b3 & b5);
s[10] = b10 ^ (~b12 & b14);
s[11] = b11 ^ (~b13 & b15);
s[20] = b20 ^ (~b22 & b24);
s[21] = b21 ^ (~b23 & b25);
s[30] = b30 ^ (~b32 & b34);
s[31] = b31 ^ (~b33 & b35);
s[40] = b40 ^ (~b42 & b44);
s[41] = b41 ^ (~b43 & b45);
s[2] = b2 ^ (~b4 & b6);
s[3] = b3 ^ (~b5 & b7);
s[12] = b12 ^ (~b14 & b16);
s[13] = b13 ^ (~b15 & b17);
s[22] = b22 ^ (~b24 & b26);
s[23] = b23 ^ (~b25 & b27);
s[32] = b32 ^ (~b34 & b36);
s[33] = b33 ^ (~b35 & b37);
s[42] = b42 ^ (~b44 & b46);
s[43] = b43 ^ (~b45 & b47);
s[4] = b4 ^ (~b6 & b8);
s[5] = b5 ^ (~b7 & b9);
s[14] = b14 ^ (~b16 & b18);
s[15] = b15 ^ (~b17 & b19);
s[24] = b24 ^ (~b26 & b28);
s[25] = b25 ^ (~b27 & b29);
s[34] = b34 ^ (~b36 & b38);
s[35] = b35 ^ (~b37 & b39);
s[44] = b44 ^ (~b46 & b48);
s[45] = b45 ^ (~b47 & b49);
s[6] = b6 ^ (~b8 & b0);
s[7] = b7 ^ (~b9 & b1);
s[16] = b16 ^ (~b18 & b10);
s[17] = b17 ^ (~b19 & b11);
s[26] = b26 ^ (~b28 & b20);
s[27] = b27 ^ (~b29 & b21);
s[36] = b36 ^ (~b38 & b30);
s[37] = b37 ^ (~b39 & b31);
s[46] = b46 ^ (~b48 & b40);
s[47] = b47 ^ (~b49 & b41);
s[8] = b8 ^ (~b0 & b2);
s[9] = b9 ^ (~b1 & b3);
s[18] = b18 ^ (~b10 & b12);
s[19] = b19 ^ (~b11 & b13);
s[28] = b28 ^ (~b20 & b22);
s[29] = b29 ^ (~b21 & b23);
s[38] = b38 ^ (~b30 & b32);
s[39] = b39 ^ (~b31 & b33);
s[48] = b48 ^ (~b40 & b42);
s[49] = b49 ^ (~b41 & b43);
s[0] ^= ROUND_CONST[n + 0];
s[1] ^= ROUND_CONST[n + 1];
//dump(s, `s:n=${n}`, 4)
}
}
function sha3hns(data) {
const state = new Uint32Array(50);
update(data, state);
const final = Buffer.alloc(136, 0);
final[0] |= 6;
final[135] |= 0x80;
update(final, state);
const out = Buffer.alloc(32);
for (let i = 0; i < 32; i++)
out[i] = state[i >> 2] >> (8 * (i & 3));
return out;
}
module.exports = sha3hns;

View file

@ -0,0 +1,39 @@
const EventEmitter = require('events');
const Debug = require('../log')();
const BLAKE2B = require('./base/blake2b')
const sha3hns = require('./base/sha3hns');
const COMP = '[blake2bsha3Api]';
class blake2bsha3Api extends EventEmitter {
constructor({}) {
super();
var _this = this;
}
getAlgoName() {
return 'blake2bsha3';
}
genHash(data, length, varity) {
//const data = Buffer.from('40ad0f00d163845e00000000962b1c413636e85858fdf91d152b42056662b7fc000000000000005b20195afe280a27276d4517c7f80f5a61843eac78b1af2c82962b1c413636e80378e4a3e33d2165220b27a03b7f0eb30bb515b160961657af5b9f8af4996c2566621ecd0f71c6a77cf80c2e6b6e816aae44d07d91830caa07', 'hex');
//Debug.IbctLogDbg(COMP, 'genhash++++', data.toString('hex'));
const pad8 = Buffer.alloc(8);
const pad32 = Buffer.alloc(32);
for (let i = 0; i < 32; i++) {
if (i < 8)
pad8.writeUInt8(data.readUInt8(i + 32) ^ data.readUInt8(i + 64), i);
pad32.writeUInt8(data.readUInt8(i + 32) ^ data.readUInt8(i + 64), i);
}
const left = BLAKE2B.digest(data, 64);
//const rxx = SHA3.multi(data, pad8);
const right = sha3hns(Buffer.concat([data, pad8]));
const hash = BLAKE2B.multi(left, pad32, right);
//Debug.IbctLogDbg(COMP, 'HASH IS', hash.toString('hex'));
return hash.toString('hex');
}
};
module.exports = function Runblake2bsha3Api(options = {}) {
return new blake2bsha3Api(options);
};

22
src/algo/lbryApi.js Normal file
View file

@ -0,0 +1,22 @@
const EventEmitter = require('events');
var multiHashing = require('ibctminerscrypt');
const Debug = require('../log')();
const COMP = '[lbryApi]';
class lbryApi extends EventEmitter {
constructor({}) {
super();
var _this = this;
_this.genhashFunc = multiHashing['lbry'];
}
getAlgoName() {
return 'lbry';
}
genHash(data) {
return this.genhashFunc(data);
}
};
module.exports = function RunlbryApi(options = {}) {
return new lbryApi(options);
};

View file

@ -0,0 +1,99 @@
const EventEmitter = require('events');
const hns = require('./hns');
const lbc = require('./lbc');
const Debug = require('../log')();
const COMP = '[cryptoCurrencys]';
var cryptoCurrencys = [
{
name: 'hns',
api: hns
},
{
name: 'lbc',
api: lbc
}
];
class CryptoCurrency extends EventEmitter {
constructor({
name
}) {
super();
var _this = this;
Debug.IbctLogDbg(COMP, name);
_this.name = name;
_this.cryptoApi = _this.GetCryptoApi(_this.name);
if (!_this.cryptoApi) {
_this.emit('error', __('不支持此币种:'), _this.name);
}
_this.on('error', function (error) {
Debug.IbctLogErr(COMP, "error:", error);
});
return _this;
}
GetCryptoApi(name) {
var crypto = null;
cryptoCurrencys.forEach(function (cryptoCurrency, index) {
if (cryptoCurrency.name === name) {
crypto = cryptoCurrency;
}
})
return crypto ? crypto.api() : null;
}
getCryptoName() {
return this.cryptoApi ? this.cryptoApi.getCryptoName() : null;
}
stratum_subscribe(data) {
return this.cryptoApi ? this.cryptoApi.stratum_subscribe(data) : null;
}
jsonrpc_subscibe(data) {
return this.cryptoApi ? this.cryptoApi.jsonrpc_subscibe(data) : null;
}
stratum_diff(data) {
return this.cryptoApi ? this.cryptoApi.stratum_diff(data) : null;
}
jsonrpc_diff(data) {
return this.cryptoApi ? this.cryptoApi.jsonrpc_diff(data) : null;
}
stratum_notify(data) {
return this.cryptoApi ? this.cryptoApi.stratum_notify(data) : null;
}
jsonrpc_notify(data) {
return this.cryptoApi ? this.cryptoApi.jsonrpc_notify(data) : null;
}
JobtoWork(Job, Work) {
return this.cryptoApi ? this.cryptoApi.JobtoWork(Job, Work) : null;
}
setWorkData(work, mode, data) {
return this.cryptoApi ? this.cryptoApi.setWorkData(work, mode, data) : null;
}
checkHash(target, hash) {
return this.cryptoApi ? this.cryptoApi.checkHash(target, hash) : null;
}
calHash(Device, nonce) {
return this.cryptoApi ? this.cryptoApi.calHash(Device, nonce) : null;
}
getSubmitParams(Device, nonce, hash) {
return this.cryptoApi ? this.cryptoApi.getSubmitParams(Device, nonce, hash) : null;
}
targetToDiff(target) {
return this.cryptoApi ? this.cryptoApi.targetToDiff(target) : null;
}
diffToTarget(difficulty) {
return this.cryptoApi ? this.cryptoApi.diffToTarget(difficulty) : null;
}
reverseBuffer(src) {
return this.cryptoApi ? this.cryptoApi.reverseBuffer(src) : null;
}
};
module.exports = function GetCryptoCurrency(options = {}) {
return new CryptoCurrency(options);
};

226
src/cryptocurrency/hns.js Normal file
View file

@ -0,0 +1,226 @@
const EventEmitter = require('events');
const Debug = require('../log')();
const BLAKE2B = require('../algo/base/blake2b');
const COMP = '[hns]';
class Hns extends EventEmitter {
constructor({ }) {
super();
var _this = this;
_this.nce2sz = 24;
_this.nce1hex = null;
_this.curdiff = 1;
}
stratum_subscribe(data) {
//Debug.IbctLogDbg(COMP, 'subscribe ++++++++');
var _this = this;
_this.nce1hex = data[1];
//_this.nce2sz = data[2];
Debug.IbctLogDbg(COMP, 'subscribe',_this.nce1hex, _this.nce2sz);
return 0;
}
stratum_diff(data) {
var _this = this;
_this.curdiff = data[0];
Debug.IbctLogDbg(COMP, 'setdiff to', _this.curdiff);
return 0;
}
stratum_notify(data) {
var packet = {
job_id: null,
preHash: null,
merkleRoot: null,
witnessRoot: null,
treeRoot: null,
reserveRoot: null,
bbVersion: null,
nbit: null,
ntime: null,
clean: null
};
//Debug.IbctLogDbg(COMP, 'hns', JSON.stringify(packet));
packet.job_id = data[0];
packet.preHash = data[1];
packet.merkleRoot = data[2];
packet.witnessRoot = data[3];
packet.treeRoot = data[4];
packet.reserveRoot = data[5];
packet.bbVersion = data[6];
packet.nbit = data[7];
packet.ntime = data[8];
packet.nonce2 = 0;
packet.clean = true;
//Debug.IbctLogDbg(COMP, 'stratum_notify', JSON.stringify(packet));
return packet;
}
getCryptoName() {
return 'hns';
}
padding(size, prevBlock, treeRoot) {
const pad = Buffer.alloc(size);
for (let i = 0; i < size; i++)
pad[i] = prevBlock[i % 32] ^ treeRoot[i % 32];
return pad;
}
targetToDiff(target) {
var Ntarget = parseInt('0x' + target, 16);
var difficulty = Math.round(26959946667150639794667015087019630673637144422540572481103610249215.0 / Ntarget * 0xffffffff);
return difficulty;
}
diffToTarget(difficulty) {
var temp = Math.floor(0xffffffff / difficulty).toString(16);
var target = Buffer.from('0'.repeat(16 - temp.length) + temp + 'f'.repeat(48), 'hex');
return target;
}
JobtoWork(Job, Work) {
if (!Job || !Work) {
return false;
}
var _this = this;
Debug.IbctLogInfo(COMP, 'JobtoWork+++++', JSON.stringify(Job));
const preHash = Buffer.from(Job.preHash, 'hex');
const treeRoot = Buffer.from(Job.treeRoot, 'hex');
const reserveRoot = Buffer.from(Job.reserveRoot, 'hex');
const witnessRoot = Buffer.from(Job.witnessRoot, 'hex');
const merkleRoot = Buffer.from(Job.merkleRoot, 'hex');
const ntime = parseInt('0x' + Job.ntime, 16);
const nbit = parseInt('0x' + Job.nbit, 16);
const bbVersion = parseInt('0x' + Job.bbVersion, 16);
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// For Stratum test
// const preHash = Buffer.from('000000000000025a108f1cb72ba240f4ba7a328335df544b51576de46f32a1d2', 'hex');
// const treeRoot = Buffer.from('6818edbdbf9d45e6ee969f190fea65f282c35704a2ca9de702e732dba1331853', 'hex');
// const reserveRoot = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex');
// const witnessRoot = Buffer.from('51cb87c5c422ad753accd7743d6cd9cc6dd54e88c57419b802e1dd88fbac86ae', 'hex');
// const merkleRoot = Buffer.from('39aeb10e8a858e53c81a7af2b44abc7dffd5b24227d092148b8480f28332ddef', 'hex');
// const ntime = 0x5ed7427a;
// const nbit = 0x1a0350df
// _this.nce1hex = '02cea75b';
// Job.nonce2 = 1;
//////////////////////////////////////////////////////////////////////////////////////////////////////////
const mask = Buffer.alloc(32);
const subHead = Buffer.alloc(128);
const extraNonce = Buffer.alloc(_this.nce2sz);
const pad20 = _this.padding(20, preHash, treeRoot);
const maskHash = BLAKE2B.multi(preHash, mask, 32);
extraNonce.writeUInt32BE(parseInt(_this.nce1hex, 16), 0);
extraNonce.writeUInt32BE(Job.nonce2, 4);
extraNonce.copy(subHead, 0);
reserveRoot.copy(subHead, 24);
witnessRoot.copy(subHead, 56);
merkleRoot.copy(subHead, 88);
subHead.writeUInt32LE(bbVersion, 120);
subHead.writeUInt32LE(nbit, 124);
//Debug.IbctLogDbg(COMP, 'JobtoWork+++++ extraNonce', extraNonce.toString('hex'));
//Debug.IbctLogDbg(COMP, 'JobtoWork+++++ subHead', subHead.toString('hex'));
//Debug.IbctLogDbg(COMP, 'JobtoWork+++++ pad20', pad20.toString('hex'));
const subHeader = BLAKE2B.digest(subHead, 32);
const commitHash = BLAKE2B.multi(subHeader, maskHash, 32);
Work.job_id = Job.job_id;
Work.snonce = ntime * 0x100000000;
Work.enonce = 0;
Work.nonce2 = Job.nonce2++;
Work.difficulty = _this.curdiff;
Work.target = _this.diffToTarget(Work.difficulty);
Work.ntime = ntime;
Work.clean = Job.clean;
////////////////////////////////
//make work data //
Work.data = Buffer.alloc(128);
Work.data.writeUInt32LE(0, 0);
Work.data.writeUInt32LE(Work.ntime, 4);
pad20.copy(Work.data, 12);
preHash.copy(Work.data, 32);
treeRoot.copy(Work.data, 64);
commitHash.copy(Work.data,96);
//Debug.IbctLogDbg(COMP, 'JobtoWork+++++ WorkNonce2:', Work.nonce2, 'WorkData', Work.data.toString('hex'));
return true;
}
setWorkData(work, mode, data) {
//Debug.IbctLogInfo(COMP, 'setWorkData: ', data.toString('hex'));
if (mode === 'start nonce') {
// Debug.IbctLogDbg(COMP, ((data & 0xffffffff) >>> 0).toString('16'));
// work.data.writeUIntLE((data & 0xffffffff), 0, 4);
// Debug.IbctLogDbg(COMP, (Math.floor(data / 0x100000000) >>> 0).toString('16'));
// work.data.writeUIntLE(Math.floor(data / 0x100000000), 4, 4);
data.copy(work.data, 0);
}
}
checkHash(target, hash) {
//Debug.IbctLogDbg(COMP, 'checkHash', 'target', target.toString('hex'), hash.toString('hex'));
return Buffer.compare(target, hash) > 0;
}
reverseBuffer(src) {
const buffer = Buffer.alloc(src.length);
for (let i = 0, j = src.length - 1; i <= j; ++i, --j) {
buffer[i] = src[j];
buffer[j] = src[i];
}
return buffer;
}
calHash(Device, nonce) {
var work = Device.dev.work;
var data = Buffer.from(work.data);
var algo = Device.dev.algorithm;
//var data = Buffer.from(work.data);
var result = null;
if (!algo) {
return false;
}
//Debug.IbctLogDbg(COMP, 'calHash++ Worknonce2', work.nonce2, 'data', data.toString('hex'));
nonce.copy(data, 0);
result = algo.genHash(data, data.length, 0);
return Buffer.from(result, 'hex');
}
getSubmitParams(Device, nonce, hash) {
var nce2hex = Buffer.alloc(4);
var ntime1 = this.reverseBuffer(Buffer.from(nonce.slice(8,16), 'hex'));
var nce = this.reverseBuffer(Buffer.from(nonce.slice(0,8), 'hex'));
nce2hex.writeUInt32BE(Device.dev.work.nonce2, 0);
var submit = {
job_id: null,
params: {
id: null,
submit: []
}
};
//var ntime = new Date();
submit.job_id = Device.dev.work.job_id;
submit.params = {
id: Device.dev.poolId,
submit: [
// Device.dev.pool.user, submit.job_id, "0", Math.floor(ntime.getTime() / 1000).toString('16'), nonce.toString('16')
Device.dev.pool.user, submit.job_id, nce2hex.toString('hex'), ntime1.toString('hex'), nce.toString('hex'), Buffer.alloc(32).toString('hex')
]
}
Debug.IbctLogDbg(COMP, 'getSubmitParams+++', JSON.stringify(submit));
return submit;
}
};
module.exports = function GetHns(options = {}) {
return new Hns(options);
};

280
src/cryptocurrency/lbc.js Normal file
View file

@ -0,0 +1,280 @@
const EventEmitter = require('events');
var multiHashing = require('ibctminerscrypt');
const Debug = require('../log')();
const COMP = '[lbc]';
class Lbc extends EventEmitter {
constructor({}) {
super();
var _this = this;
_this.nce2sz = 4;
_this.nce1hex = null;
_this.curdiff = 1;
_this.cnt = 0;
}
stratum_subscribe(data) {
//console.log(COMP, 'subscribe ++++++++', data);
var _this = this;
_this.nce1hex = data[1];
_this.nce2sz = data[2];
Debug.IbctLogDbg(COMP, 'subscribe',_this.nce1hex, _this.nce2sz);
return 0;
}
stratum_diff(data) {
var _this = this;
_this.curdiff = data[0];
Debug.IbctLogDbg(COMP, 'setdiff to', _this.curdiff);
_this.curdiff /= 256.0;
return 0;
}
stratum_notify(data) {
var packet = {
job_id: null,
preHash: null,
claimHash: null,
coinb1: null,
coinb2: null,
merkleRoot: null,
bbVersion: null,
nbit: null,
ntime: null,
clean: null
};
//Debug.IbctLogDbg(COMP, JSON.stringify(packet));
//console.log("*****stratum data", data);
packet.job_id = data[0];
packet.preHash = data[1];
packet.claimHash = data[2];
packet.coinb1 = data[3];
packet.coinb2 = data[4];
packet.merkleRoot = data[5];
packet.bbVersion = data[6];
packet.nbit = data[7];
packet.ntime = data[8];
packet.clean = true;
packet.nonce2 = 0;
//Debug.IbctLogDbg(COMP, 'stratum_notify', JSON.stringify(packet));
return packet;
}
getCryptoName() {
return 'lbc';
}
targetToDiff(target) {
var Ntarget = parseInt('0x' + target, 16);
var difficulty = Math.round(26959946667150639794667015087019630673637144422540572481103610249215.0 / Ntarget * 0xffffffff);
return difficulty;
}
diffToTarget(difficulty) {
var temp = Math.floor(0xffffffff / difficulty).toString(16);
var target = Buffer.from('0'.repeat(16 - temp.length) + temp + 'f'.repeat(48), 'hex');
return target;
}
JobtoWork(Job, Work) {
if (!Job || !Work) {
return false;
}
var _this = this;
var s = [];
//console.log(COMP, 'JobtoWork+++++', Job);
const preHash = Buffer.from(Job.preHash, 'hex');
const claimHash = Buffer.from(Job.claimHash, 'hex');
const coinb1 = Buffer.from(Job.coinb1, 'hex');
const coinb2 = Buffer.from(Job.coinb2, 'hex');
const merkleCount = Job.merkleRoot.length;
const merkleRoot = Job.merkleRoot;
//const bbVersion = parseInt('0x' + Job.bbVersion, 16);
const ntime = Buffer.from(Job.ntime, 'hex');
const nbit = Buffer.from(Job.nbit, 'hex');
const bbVersion = Buffer.from(Job.bbVersion, 'hex');
for (var i = 0; i < merkleCount; i++)
{
s[i] = Buffer.from(merkleRoot[i], 'hex');
}
//console.log("preHash", preHash.toString('hex'));
//console.log("claimHash", claimHash.toString('hex'));
//console.log("coinb1", coinb1.toString('hex'));
//console.log("coinb2", coinb2.toString('hex'));
//console.log("merklecount", merkleCount);
//console.log("merkleroot", s);
//console.log("ntime", ntime.toString('hex'));
//console.log("nbit", nbit.toString('hex'));
//console.log("bbversion", bbVersion.toString('hex'));
var coinb1_size = coinb1.length;
var coinb2_size = coinb2.length;
var nonce1 = Buffer.from(_this.nce1hex, 'hex');
//console.log("nonce1", nonce1.toString('hex'));
var coinbase_size = coinb1_size + coinb2_size + nonce1.length + _this.nce2sz;
//console.log("coinbase_size", coinbase_size);
var coinbase = Buffer.alloc(coinbase_size, 0);
coinb1.copy(coinbase, 0);
nonce1.copy(coinbase, coinb1_size);
var nonce2 = Buffer.alloc(4, 0);
nonce2.writeUInt32BE(Job.nonce2);
nonce2.copy(coinbase, coinb1_size + nonce1.length);
coinb2.copy(coinbase, coinb1_size + nonce1.length + _this.nce2sz);
//console.log("coinbase", coinbase.toString('hex'));
var sha256d = multiHashing['sha256d'];
var merkle_root = Buffer.alloc(64, 0);
var m1 = sha256d(coinbase);
//console.log("m 1", m1.toString('hex'));
m1.copy(merkle_root, 0);
//console.log("m 2", merkle_root.toString('hex'));
var m2;
for (var i = 0; i < merkleCount; i++)
{
s[i].copy(merkle_root, 32);
//console.log("m", i, merkle_root.toString('hex'));
m2 = sha256d(merkle_root);
//console.log("m 2", m2.toString('hex'));
m2.copy(merkle_root, 0);
//console.log("*m", i, merkle_root.toString('hex'));
}
//console.log("merkleroot", merkle_root.toString('hex'));
var merkle = Buffer.alloc(32);
for(var i = 0; i < 8; i++)
{
merkle.writeUInt32BE(merkle_root.readUInt32LE(i*4), i*4);
}
//console.log("merkle ", merkle.toString('hex'));
//console.log("***Work", Work);
Work.job_id = Job.job_id;
//Work.snonce = ntime * 0x100000000;
Work.snonce = 0;
Work.enonce = 0xffffffff;
Work.nonce2 = Job.nonce2++;
//console.log("nnnnnnonce2", Work.nonce2);
Work.difficulty = _this.curdiff;
Work.target = _this.diffToTarget(Work.difficulty);
//Work.ntime = parseInt('0x' + Job.ntime, 16);
Work.ntime = ntime;
Work.clean = Job.clean;
var data1 = Buffer.alloc(112, 0);
bbVersion.copy(data1, 0);
preHash.copy(data1, 4);
merkle.copy(data1, 36);
claimHash.copy(data1, 68);
ntime.copy(data1, 100);
nbit.copy(data1, 104);
//console.log("data1", data1.toString('hex'));
var data2 = Buffer.alloc(112, 0);
for(var i = 0; i < 28; i++)
{
data2.writeUInt32BE(data1.readUInt32LE(i*4), i*4);
}
//console.log("data2", data2.toString('hex'));
var genhash = multiHashing['lbcgenhash'];
var prehash1 = genhash(data2);
//console.log("prehash1", prehash1.toString('hex'));
Work.data = Buffer.alloc(136, 0);
Work.sdata = Buffer.alloc(112, 0);
data2.copy(Work.sdata);
var prehash2 = Buffer.alloc(32, 0);
for(var i = 0; i < 8; i++)
{
prehash2.writeUInt32BE(prehash1.readUInt32LE(i*4), i*4);
}
//console.log("prehash2", prehash2.toString('hex'));
prehash2.copy(Work.data, 0);
data2.copy(Work.data, 32, 64);
//console.log(COMP, 'JobtoWork+++++ WorkNonce2:', Work.nonce2, 'WorkData', Work.data.toString('hex'));
return true;
}
setWorkData(work, mode, data) {
//Debug.IbctLogInfo(COMP, 'setWorkData: ', data.toString('hex'));
if (mode === 'start nonce') {
// Debug.IbctLogDbg(COMP, ((data & 0xffffffff) >>> 0).toString('16'));
// work.data.writeUIntLE((data & 0xffffffff), 0, 4);
// Debug.IbctLogDbg(COMP, (Math.floor(data / 0x100000000) >>> 0).toString('16'));
// work.data.writeUIntLE(Math.floor(data / 0x100000000), 4, 4);
data.copy(work.data, 0);
}
}
checkHash(target, hash) {
//Debug.IbctLogDbg(COMP, 'checkHash', 'target', target.toString('hex'), hash.toString('hex'));
this.cnt++;
if (this.cnt === 1) {
if(Buffer.compare(target, hash) > 0) {
return true;
} else {
this.cnt = 0;
return false;
}
} else if (this.cnt === 2) {
this.cnt = 0;
return Buffer.compare(this.diffToTarget(this.curdiff), hash) > 0;
}
}
reverseBuffer(src) {
const buffer = Buffer.alloc(src.length);
for (let i = 0, j = src.length - 1; i <= j; ++i, --j) {
buffer[i] = src[j];
buffer[j] = src[i];
}
return buffer;
}
calHash(Device, nonce) {
var work = Device.dev.work;
var data = Buffer.from(work.sdata);
var algo = Device.dev.algorithm;
var result = null;
//console.log("$$$$$$$$$$$$$", data.toString('hex'));
if (!algo) return false;
//Debug.IbctLogDbg(COMP, 'calHash++ Worknonce2', work.nonce2, 'data', data.toString('hex'));
nonce.copy(data, 108);
var tt = nonce.readUInt32LE(4);
var ntime = data.readUInt32LE(100);
ntime += tt;
data.writeUInt32LE(ntime, 100);
//console.log("(((((((((((((", data.toString('hex'));
result = algo.genHash(data);
//console.log("result", result.toString('hex'));
return this.reverseBuffer(result);
}
getSubmitParams(Device, nonce, hash) {
var nce2hex = Buffer.alloc(4);
var nce = this.reverseBuffer(Buffer.from(nonce.slice(0,8), 'hex'));
var ntime = Device.dev.work.ntime.readUInt32BE(0);
var tt = this.reverseBuffer(Buffer.from(nonce.slice(8,16), 'hex'));
var tt1 = tt.readUInt32BE(0);
ntime += tt1;
var ntime1 = Buffer.alloc(4);
ntime1.writeUInt32BE(ntime, 0)
nce2hex.writeUInt32BE(Device.dev.work.nonce2, 0);
var submit = [Device.dev.pool.user, Device.dev.work.job_id, nce2hex.toString('hex'), ntime1.toString('hex'), nce.toString('hex')];
//console.log(COMP, 'getSubmitParams+++', JSON.stringify(submit));
return submit;
}
};
module.exports = function GetLbc(options = {}) {
return new Lbc(options);
};

171
src/detect.js Normal file
View file

@ -0,0 +1,171 @@
const EventEmitter = require('events');
const UsbDetect = require('usb-detection');
const SerialPort = require('serialport');
const Fs = require('fs')
const Debug = require('./log')();
const locks = require('locks');
const COMP = '[Detect]';
class UsbMiner extends EventEmitter {
constructor({}) {
super();
var _this = this
_this.devices = [];
_this.runDevices = [];
_this.detectMutexs = [];
_this.id = 0;
// UsbDevice Vid&Pid
_this.vendorId = '1155';
_this.productId = '22336';
UsbDetect.startMonitoring();
_this.on('error', function (error) {
Debug.IbctLogErr(COMP, "error:", error);
});
var listener = 'add' + ':' + _this.vendorId + ':' + _this.productId
UsbDetect.on(listener, function (Device) {
Debug.IbctLogErr(COMP, 'add:', JSON.stringify(Device));
_this.AddUsbMiner(Device);
});
listener = 'remove' + ':' + _this.vendorId + ':' + _this.productId
UsbDetect.on(listener, function (Device) {
if (_this.hasExistUsbMiner(Device)) {
Debug.IbctLogErr(COMP, 'remove:', JSON.stringify(Device));
_this.RemoveUsbMiner(Device);
}
});
}
async ExitUsbMiner() {
await UsbDetect.stopMonitoring();
}
GetUsbMiner() {
return this.devices;
}
getDetectMutex(port) {
var mutex = null;
for (var i = 0; i < this.detectMutexs.length; i++) {
if (this.detectMutexs[i].port === port) {
mutex = this.detectMutexs[i].mutex;
break;
}
}
if (!mutex) {
mutex = locks.createMutex();
this.detectMutexs.push({
port: port,
mutex: mutex
});
}
return mutex;
}
async ListUsbMiner() {
var _this = this;
var UsbMiner = [];
await UsbDetect.find(parseInt(_this.vendorId), parseInt(_this.productId), function(err, Devices) {
if (err) {
Debug.IbctLogErr(COMP, err);
return;
}
UsbMiner = Devices;
})
for (var i = 0; i < UsbMiner.length; i++) {
await _this.getUsbMinerPort(UsbMiner[i], 0, function (err, port) {
if (err) {
return;
}
_this.devices.push({
devID: _this.id++,
port: port,
mutex: _this.getDetectMutex(port),
miner: UsbMiner[i]
});
});
}
}
async getSerialPort(Device, callback) {
var portName = null;
await SerialPort.list(function (err, ports) {
if (err) {
callback(err);
return;
}
ports.forEach(function (port) {
if (Device.serialNumber === port.serialNumber) {
portName = port.comName;
callback(null, portName);
return;
}
})
callback('Cannot Find Port');
})
}
async getUsbMinerPort(Device, Timeout, callback) {
var _this = this;
if (!Device) {
callback('Device Error')
return;
}
if (process.platform !== 'darwin') {
await this.getSerialPort(Device, callback);
} else {
// In darwin, serialport have some bug
if (!Timeout)
await this.getSerialPort(Device, callback);
else {
setTimeout(function() {
_this.getSerialPort(Device, callback);
}, Timeout);
}
}
}
hasExistUsbMiner(Device) {
return this.devices.some(function (dev) {
return JSON.stringify(dev.miner) === JSON.stringify(Device);
});
}
hasUsbMiner() {
return ((this.devices.length > 0) ? true : false);
}
AddUsbMiner(Device) {
var _this = this;
var device = {
devID: _this.id++,
port: null,
mutex: null,
miner: Device
};
_this.getUsbMinerPort(Device, 3000, function (err, port) {
if (err) {
return;
}
device.port = port;
device.mutex = _this.getDetectMutex(port);
_this.devices.push(device);
_this.emit('plug-in', device);
})
}
RemoveUsbMiner(Device) {
var delDevice = {}
this.devices = this.devices.filter(function (dev) {
if (JSON.stringify(dev.miner) !== JSON.stringify(Device)) {
return true;
} else {
delDevice = dev;
return false;
}
});
this.emit('plug-out', delDevice);
}
}
module.exports = function DectectUsbMiner(options = {}) {
return new UsbMiner(options);
};

113
src/index.js Normal file
View file

@ -0,0 +1,113 @@
const EventEmitter = require('events');
const DetectUsbMiner = require('./detect');
const Miner = require('./miner');
const Debug = require('./log')();
const I18n = require('i18n');
const COMP = '[index]'
class IbctMiner extends EventEmitter {
constructor({
MinerParameters
}) {
super();
var _this = this
_this.initMiningLanguage();
_this.detectUsbMiner = DetectUsbMiner({});
_this.Miner = Miner({MinerParameters: MinerParameters});
_this.on("error", function(ID, data) {
Debug.IbctLogDbg(COMP, (ID !== undefined) ? "Miner " + ID + ":" + data : data)
})
_this.on("warning", function(ID, data) {
Debug.IbctLogDbg(COMP, (ID !== undefined) ? "Miner " + ID + ":" + data : data)
})
if (_this.Miner) {
_this.Miner.on("error", function (data, Device, ID) {
return _this.emit("error", ID, data);
});
_this.Miner.on("warning", function (data, Device, ID) {
return _this.emit("warning", ID, data);
});
}
_this.detectUsbMiner.on('plug-in', async (Device) => {
await _this.addMining(Device);
return _this.emit('plug-in', Device);
})
_this.detectUsbMiner.on('plug-out', async (Device) => {
await _this.removeMining(Device);
return _this.emit('plug-out', Device);
})
}
async exitMining() {
await this.detectUsbMiner.ExitUsbMiner();
this.detectUsbMiner.removeAllListeners('plug-in');
this.detectUsbMiner.removeAllListeners('plug-out');
await this.stopMining(null);
await this.Miner.ExitMiner();
this.Miner.removeAllListeners('error');
this.Miner.removeAllListeners('warning');
}
initMiningLanguage() {
I18n.configure({
locales: ['en', 'zh'],
staticCatalog: {
en: require('./translate/en.json'),
zh: require('./translate/zh.json')
},
register: global
});
I18n.setLocale('zh');
}
setMiningLanguage(m) {
I18n.setLocale(m);
}
async initMining() {
await this.detectUsbMiner.ListUsbMiner();
await this.addMining();
}
async startMining(Device) {
return (Device ? await this.Miner.EnableMiner(Device) : await this.Miner.EnableMiners(this.listDevices()));
}
async stopMining(Device) {
return (Device ? await this.Miner.DisableMiner(Device) : await this.Miner.DisableMiners(this.listDevices()));
}
async addMining(Device) {
return (Device ? await this.Miner.AddMiner(Device) : await this.Miner.AddMiners(this.listDevices()));
}
async connectMining(Device) {
return (Device ? await this.Miner.connectMiner(Device) : await this.Miner.connectMiners(this.listDevices()));
}
async removeMining(Device) {
return (Device ? await this.Miner.RemoveMiner(Device) : await this.Miner.RemoveMiners(this.listDevices()));
}
setMiningConfig(setName, cryptoname, settings) {
if (!setName || !cryptoname || !settings)
return
return this.Miner.SetMinerConfig(setName, cryptoname, settings)
}
getMiningStatus(Device) {
return (Device ? this.Miner.GetMinerStatus(Device) : this.Miner.GetMinersStatus(this.listDevices()));
}
RebootMining(Device) {
return (Device ? this.Miner.RebootMiner(Device) : this.Miner.RebootMiners(this.listDevices()));
}
SetMiningLed(Device, Enable) {
return (Device ? this.Miner.SetMinerLed(Device, Enable) : this.Miner.SetMinersLed(this.listDevices(), Enable));
}
async BurnMiningFirmware(Device, Image, Callback) {
return (Device ? await this.Miner.BurnMinerFirmware(Device, Image, Callback) : await this.Miner.BurnMinersFirmware(this.listDevices(), Image, Callback));
}
listDevices() {
return this.detectUsbMiner.GetUsbMiner();
}
}
module.exports = function GetIbctMiner(options = {}) {
return new IbctMiner(options);
};

111
src/log.js Normal file
View file

@ -0,0 +1,111 @@
const EventEmitter = require('events');
const fs = require('fs');
const IBCT_NONE = -1;
const IBCT_ERR = 0;
const IBCT_DBG = 1;
const IBCT_INFO = 2;
const IBCT_ALL = 3;
const USER_HOME = process.env.HOME || process.env.USERPROFILE;
class Log extends EventEmitter {
constructor({}) {
super();
var _this = this;
global.loglevel = IBCT_DBG;
_this.SaveLevel = IBCT_ERR;
_this.timeEn = true;
_this.curTime = (new Date()).getDate();
_this.logSavePath = this.GetFileName();
// _this.logSavePath = null;
}
IbctSetLogLevel(level) {
global.loglevel = level
}
GetFileName() {
return USER_HOME+'/usb_log_' + (new Date()).toLocaleDateString().replace(/\//g, "-") + '.txt';
}
IbctOpen(Path) {
return fs.openSync(Path, 'a');
}
IbctWrite(fd, str) {
return fs.writeSync(fd, str);
}
IbctClose(fd) {
fs.closeSync(fd);
}
IbctSaveLog(Path, str) {
var fd;
var now;
if (!Path)
return;
now = (new Date()).getDate();
if (now !== this.curTime) {
Path = this.GetFileName();
this.curTime = now;
this.logSavePath = Path;
}
fd = this.IbctOpen(Path);
if (!fd)
return;
this.IbctWrite(fd, str);
this.IbctClose(fd);
}
GetString(strs) {
var buf = this.timeEn ? (new Date()).toLocaleString() : '';
for (var i = 0; i < strs.length; i++) {
buf += ' ' + strs[i];
}
return buf;
}
IbctLogDbg() {
var buf = null;
if (global.loglevel >= IBCT_DBG) {
buf = this.GetString(arguments);
console.log(buf);
}
if (this.SaveLevel >= IBCT_DBG) {
if (buf === null) {
buf = this.GetString(arguments);
}
this.IbctSaveLog(this.logSavePath, buf + '\n');
}
}
IbctLogErr() {
var buf = null;
if (global.loglevel >= IBCT_ERR) {
buf = this.GetString(arguments);
console.log(buf);
}
if (this.SaveLevel >= IBCT_ERR) {
if (buf === null) {
buf = this.GetString(arguments);
}
this.IbctSaveLog(this.logSavePath, buf + '\n');
}
}
IbctLogInfo() {
var buf = null;
if (global.loglevel >= IBCT_INFO) {
buf = this.GetString(arguments);
console.log(buf);
}
if (this.SaveLevel >= IBCT_INFO) {
if (buf === null) {
buf = this.GetString(arguments);
}
this.IbctSaveLog(this.logSavePath, buf + '\n');
}
}
}
module.exports = function GetLog(options = {}) {
return new Log(options);
};

1352
src/miner.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
export const minerconfig = {
model: "simplenode",
algo: "scrypt",
varity: 0x30,
powerplan: [
{
plan: "HashRate",
voltage: 810,
freq: 850,
},
{
plan: "Balance",
voltage: 720,
freq: 725,
},
{
plan: "LowePower",
voltage: 670,
freq: 600,
}
],
powerdefault: "HashRate",
temptarget: 65,
tempwarn: 70,
tempcutoff: 90
};

129
src/miner/cpu.js Normal file
View file

@ -0,0 +1,129 @@
const EventEmitter = require('events');
const Debug = require('../log')();
const COMP = '[cpu]';
class cpu extends EventEmitter {
constructor({
devPath,
algo,
varity,
crypto
}) {
super();
var _this = this;
_this.devPath = devPath;
_this.algo = algo;
_this.varity = varity;
_this.crypto = crypto;
_this.MinerShouldStop = false;
_this.info = {
firmwareVer: 'V0.0.1',
modelName: 'cpu',
sn:'CPU666666',
hashRation: 0,
workDepth:2,
};
_this.status = {
chips:1,
temp: 40,
votage:1,
freq:1000,
varity:0x0,
cores:1,
goodcores:1,
scanbits:0,
scantime:0,
tempwarn:80
};
Debug.IbctLogDbg(COMP, 'DevPath: ', _this.devPath, '; MinerCPU; Algo', _this.algo.getAlgoName(), '; Crypto: ', _this.crypto.getCryptoName());
}
async init(params) {
/*
初始化硬件
*/
Debug.IbctLogDbg(COMP, 'CPU init');
}
async detect(modelName) {
var _this = this;
Debug.IbctLogErr(COMP, 'API: detect...', modelName);
return 0;
}
getInfo() {
var _this = this;
return _this.info;
}
async setDevice() {
/*
设置Miner参数电压, 频率目标温度报警温度
*/
Debug.IbctLogDbg(COMP, 'setDevice');
}
async stopScanWork() {
this.MinerShouldStop = true;
}
async scanWork(workQueue, Callback) {
/*
更新Work
*/
var _this = this;
var result;
var nonce = Buffer.alloc(8);
//var highNonce = parseInt((Job.snonce) / 0x100000000) >>> 0;
if (!_this.algo || !_this.crypto) {
Callback('Set Algo or Crypto First');
return
}
_this.MinerShouldStop = false;
for(let i=0; i<_this.info.workDepth;i++) {
Debug.IbctLogDbg(COMP, 'work', i, JSON.stringify(workQueue.pop()));
}
// _this.work = Job;
// for (let lowNonce = 0; lowNonce < 0x5; lowNonce++) {
// //Debug.IbctLogDbg(COMP, 'algo', _this.algo.name.toString('hex'), 'scanWork ', i.toString('16'), '12: ', (Job.snonce + i).toString('16'));
// //nonce.writeUIntLE(((Job.snonce + i) & 0xffffffff), 0, 4);
// //nonce.writeUIntLE(Math.floor((Job.snonce + i) / 0x100000000), 4, 4);
// nonce.writeUInt32LE(highNonce, 4);
// nonce.writeUInt32LE(lowNonce, 0);
// if (_this.crypto.setWorkData) {
// _this.crypto.setWorkData(_this.work, 'start nonce', nonce);
// }
// if (_this.algo.genHash) {
// result = _this.algo.genHash(_this.work.data, _this.work.data.length, 0);
// Debug.IbctLogDbg(COMP, result, _this.crypto.checkHash(_this.work.target, Buffer.from(result, 'hex')));
// if (_this.crypto.checkHash && _this.crypto.checkHash(_this.work.target, Buffer.from(result, 'hex'))) {
// Debug.IbctLogDbg(COMP, 'find Nonce', (Job.snonce + i).toString('16'));
// Callback(null, nonce, work);
// }
// }
// }
Callback(null, null,work);
}
async stop() {
/*
停止硬件工作并关闭硬件
*/
Debug.IbctLogDbg(COMP, 'stop');
}
getState() {
/*
获取当前设备状态 温度电压频率功耗等
*/
var _this = this;
Debug.IbctLogDbg(COMP, 'getState');
return _this.status;
}
}
module.exports = function Getcpu(options = {}) {
return new cpu(options);
};

873
src/miner/hs1.js Normal file
View file

@ -0,0 +1,873 @@
const Delimiter = require('@serialport/parser-delimiter');
const Debug = require('../log')();
var SerialPort = require('serialport');
const EventEmitter = require('events');
// var waitUntil = require('wait-until');
var waitUntil = require('../waitUntil');
var crc32 = require('crc32');
const COMP = '[HS1]:';
const hwTarget = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
const PV = 0x10;
//const PV20 = 0x20;
const TypeSetBootMode = 0xA3;
const TypeUpdateFW = 0xAA;
const TypeQueryInfo = 0xA4;
const TypeProductTest = 0xAB;
const TypeSetLED = 0xA6;
const TypeSendWork = 0xA1;
const TypeSetHWParams = 0xA2;
const TypeReboot = 0xAC;
const TypeRecvNonce = 0x51;
const TypeRecvJob = 0x55;
const TypeRecvState = 0X52;
const TypeRecvBootMode = 0x53;
const TypeRecvInfo = 0x54;
const TypeRecvFWState = 0x5A;
const TypeRecvTestResult = 0x5B;
const pktHeader = Buffer.from([0xA5, 0x3C, 0x96]);
const pktEnder = Buffer.from([0x69, 0xC3, 0x5A]);
const typeOffset = 0;
const hs1cfg = {
model: "Goldshell-HS1",
algo: "blake2bsha3",
varity: 0x4,
targetFreq: 650, //MHz
targetVoltage: 410, //mv
targetTemp: 65,
warnTemp: 115,
offTemp: 125
};
class hs1 extends EventEmitter {
constructor({devPath, algo, varity, crypto}) {
super();
var _this = this;
_this.devPath = devPath;
_this.algo = algo;
_this.crypto = crypto;
_this.MinerShouldStop = false;
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.work = [{ jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }];
_this.Job = [];
_this.fakeJob = { overScan:false };
_this.job_id = null;
_this.info = {
firmwareVer: 'V0.0.1',
modelName: 'Goldshell-HS1',
sn:'unknown',
snAbbr: 'H10',
//hashRation:0,
workDepth:4
};
_this.firmware = {
retryCnt : 2,
FWPageSize : 256,
curState : 0,
curID : 0
}
_this.submitNonce = null;
_this.curJobid = 4;
_this.jobsDone = 0;
_this.status = {
chips:0,
temp:0,
voltage:0,
freq:0,
varity:0x4,
cores:0,
goodcores:0,
scanbits:0,
scantime:0,
tempwarn:0,
fanwarn:0,
powerwarn:0,
rpm:0
};
_this.port = new SerialPort(_this.devPath, {
baudRate: 115200,
dataBits:8,
stopBits:1,
parity:'none',
rtscts:false
}, function (err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, '打开串口失败: ', err.message);
return;
} else
Debug.IbctLogErr(COMP, _this.devPath, '打开串口成功');
});
const parser = _this.port.pipe(new Delimiter({ delimiter: pktEnder}));
parser.on('data', function(data) {
_this.hs1ParseNotify(_this, data);
});
_this.on("error", function(err) {
Debug.IbctLogDbg(COMP, err);
})
_this.on("warning", function(err) {
Debug.IbctLogDbg(COMP, err);
})
}
hs1ParseNotify(_this, data) {
//Debug.IbctLogInfo(COMP, _this.devPath, 'Recv Pkt', data.toString('hex'));
var location = data.indexOf(pktHeader);
if(location === -1) {
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Invalid PKT', data);
return;
}
var typeLocation = location + pktHeader.length + typeOffset;
switch(data[typeLocation]) {
case TypeRecvNonce:
var jobID = data[9];
//var chipID = data[10];
//var coreID = data[11];
var nonce_l = data.readUInt32LE(12);
var nonce_h = data.readUInt32LE(16);
var nonce = Buffer.alloc(8);
nonce.writeUInt32LE(nonce_l, 0);
nonce.writeUInt32LE(nonce_h, 4);
if(jobID !== _this.work[0].jobID && jobID !== _this.work[1].jobID && jobID !== _this.work[2].jobID && jobID !== _this.work[3].jobID) {
Debug.IbctLogDbg(COMP, _this.devPath, 'Find Stale ', jobID, _this.work[0].jobID, _this.work[1].jobID, nonce.toString('hex'));
} else {
//Debug.IbctLogDbg(COMP, _this.devPath, 'Find Nonce ', nonce.toString('hex'));
let i;
if (jobID === _this.work[0].jobID) i = 0;
else if (jobID === _this.work[1].jobID) i = 1;
else if (jobID === _this.work[2].jobID) i = 2;
else i = 3;
if(_this.submitNonce)
_this.submitNonce(null, nonce, _this.Job[i]);
}
break;
case TypeRecvInfo:
_this.recvInfoPkt = true;
//Debug.IbctLogDbg(COMP, _this.devPath, 'RecvInfoPkt:', data.toString('hex'));
_this.info.modelName = data.toString('utf8', 10, 10 + data[9]);
_this.info.firmwareVer = data.toString('utf8', 27, 27 + data[26]);
_this.info.sn = data.toString('utf8', 36, 36 + data[35]);
_this.info.snAbbr = 'H10';
//if (data[typeLocation+1] === PV20) {
//_this.info.workDepth = data[53];
//} else {
//_this.info.hashRation = data.readUInt16LE(69);
//_this.info.workDepth = data[101];
//}
Debug.IbctLogDbg(_this.info);
break;
case TypeRecvFWState:
_this.recvFWStatePkt = true;
_this.firmware.curID = data.readUInt32LE(9);
_this.firmware.curState = data[13];
break;
case TypeRecvJob:
_this.recvJobPkt = true;
break;
case TypeRecvBootMode:
_this.recvBootModePkt = true;
break;
case TypeRecvState:
_this.recvStatePkt = true;
_this.recvQueryStatePkt = true;
//Debug.IbctLogInfo(COMP, _this.devPath, 'RecvState pkt:', data.toString('hex'));
_this.status.chips = data[9];
_this.status.cores = data[10];
_this.status.goodcores = data[11];
_this.status.scanbits = data[12];
_this.status.scantime = data.readUInt16LE(13) * 100; //ms
_this.status.voltage = data.readUInt16LE(15); //mV
_this.status.freq = data.readUInt16LE(17); //MHz
_this.status.varity = data.readUInt32LE(19);
_this.status.temp = data[23];
_this.status.hwreboot = data[24];
_this.status.tempwarn = data[25];
_this.status.fanwarn = data[26];
_this.status.powerwarn = data[27];
_this.status.rpm = data.readUInt16LE(28);
break;
case TypeRecvTestResult:
_this.recvPTInfoPkt = true;
break;
default:
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Unsupported PKT Type', data[location + typeOffset])
break;
}
return;
}
hs1SendPkt (pkt) {
var _this = this;
var offset = 0;
var length = 0;
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
length += pkt[key].length;
}
});
var msg = Buffer.alloc(length);
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
pkt[key].copy(msg, offset);
offset += pkt[key].length;
}
});
//Debug.IbctLogDbg(COMP, _this.devPath, 'Send pkt', msg.toString('hex'));
_this.port.write(msg, function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on write: ', err.message)
}
_this.port.drain(function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on Drain: ', _this.devPath, err.message)
}
})
})
}
hs1GetState(_this) {
var pktQueryStatus = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x7, 0x0 ,0x0 ,0x0]),
flag:Buffer.from([0x52]),
ender:Buffer.from(pktEnder)
};
_this.recvQueryStatePkt = false;
_this.hs1SendPkt(pktQueryStatus);
/*TODO Wait response here*/
return true;
}
async hs1GetStaticInfo(modelName) {
var _this = this;
var pktQueryInfo = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeQueryInfo]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvInfoPkt = false;
_this.hs1SendPkt(pktQueryInfo);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(50)
.times(10)
.condition(function() {
return _this.recvInfoPkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '获取矿机信息出错');
resolve(2);
} else {
if(modelName === _this.info.modelName) {
resolve(0);
} else {
resolve(1);
}
}
})
});
}
hs1SetBootMode() {
var _this = this;
var pktSetBootMode = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetBootMode]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x7, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvBootModePkt = false;
_this.hs1SendPkt(pktSetBootMode);
}
hs1BurnFWInit() {
var _this = this;
var pktSetBootMode = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetBootMode]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvBootModePkt = false;
_this.hs1SendPkt(pktSetBootMode);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvBootModePkt
})
.done(function(result) {
if(result === false) {
resolve(1);
} else {
resolve(0);
}
})
})
}
hs1SetHWParams(varity, freq, voltage) {
var _this = this;
var pktSetParam = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x10, 0x00, 0x00, 0x00]),
flag:Buffer.from([0xA2]),
voltage: Buffer.alloc(2),
freq:Buffer.alloc(2),
varity:Buffer.alloc(4),
targettemp:Buffer.from([80]),
ender:Buffer.from(pktEnder)
};
pktSetParam.varity.writeUInt32LE(varity, 0);
pktSetParam.freq.writeUInt16LE(freq, 0);
pktSetParam.voltage.writeUInt16LE(voltage, 0);
_this.recvStatePkt = false;
_this.hs1SendPkt(pktSetParam);
/*TODO Wait response here*/
return true;
}
hs1SetHWParamsAndWait(varity, freq, voltage) {
var _this = this;
var pktSetParam = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x10, 0x00, 0x00, 0x00]),
flag:Buffer.from([0xA2]),
voltage: Buffer.alloc(2),
freq:Buffer.alloc(2),
varity:Buffer.alloc(4),
targettemp:Buffer.from([80]),
ender:Buffer.from(pktEnder)
};
pktSetParam.varity.writeUInt32LE(varity, 0);
pktSetParam.freq.writeUInt16LE(freq, 0);
pktSetParam.voltage.writeUInt16LE(voltage, 0);
_this.recvStatePkt = false;
_this.hs1SendPkt(pktSetParam);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvStatePkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '设置矿机参数出错');
resolve(1);
} else {
resolve(0);
}
})
})
}
async hs1WriteJob(jobID, snonce, enonce, target, data) {
var _this = this;
_this.recvJobPkt = false;
var pktSendJob = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSendWork]),
version: Buffer.from([PV]),
pktlen: Buffer.alloc(4),
target:Buffer.alloc(8),
startNonce: Buffer.alloc(8),
endNonce:Buffer.alloc(8),
jobNum: Buffer.from([1]),
jobID: Buffer.alloc(1),
jobData:Buffer.alloc(128),
ender:Buffer.from(pktEnder)
};
pktSendJob.pktlen.writeUInt32LE(160, 0); //pktlen
target.copy(pktSendJob.target, 0);
snonce.copy(pktSendJob.startNonce, 0);
enonce.copy(pktSendJob.endNonce, 0);
pktSendJob.jobID[0] = jobID;
data.copy(pktSendJob.jobData, 0);
_this.hs1SendPkt(pktSendJob);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(40)
.times(50)
.condition(function() {
return _this.recvJobPkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '发送Work失败');
reject(new Error(__('发送Work失败')).message);
} else {
resolve(0);
}
})
})
}
hs1SendFWPktAndWait(cnt, FWPKT) {
var _this = this;
_this.recvFWStatePkt = false;
_this.firmware.curState = 0;
_this.firmware.curID = 0;
_this.hs1SendPkt(FWPKT);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvFWStatePkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '烧录连接超时');
reject(new Error(__('烧录连接超时')).message);
} else if(_this.firmware.curState === 0x02) {
if(cnt < _this.firmware.retryCnt){
resolve(1);
} else {
Debug.IbctLogErr(COMP, _this.devPath, '串口出错');
reject(new Error(__('串口出错')).message);
}
} else {
resolve(0);
}
})
})
}
async stopScanWork() {
var _this = this;
Debug.IbctLogDbg(COMP, _this.devPath, 'stopScanWork ...');
//_this.curJobid = 0;
_this.MinerShouldStop = true;
}
async scanWork(workQueue, callback) {
var _this = this;
Debug.IbctLogInfo(COMP, _this.devPath, 'scanWork Begin ...');
if (!_this.inited) {
callback(null, null, null);
return;
}
if(_this.status.scantime === 0) {
_this.status.scantime = 7000; //10s default
}
if (_this.MinerShouldStop) {
_this.MinerShouldStop = false;
if (_this.job_id === workQueue[0].job_id) {
// new job arrived
_this.fakeJob.overScan = false;
// clean all workQueue
workQueue.splice(0, workQueue.length);
callback(null, null, _this.fakeJob);
Debug.IbctLogDbg("new job arrived, scanWork again");
return;
}
}
_this.job_id = workQueue[0].job_id;
for(let i = 0; i < _this.info.workDepth; i++) {
_this.Job[i] = workQueue.pop();
_this.work[i].target = Buffer.alloc(8);
var target = _this.Job[i].target;
if(Buffer.compare(hwTarget, target) > 0) {
hwTarget.copy(_this.work[i].target, 0);
_this.Job[i].hwTarget = hwTarget;
} else {
target.copy(_this.work[i].target, 0);
_this.Job[i].hwTarget = target;
}
_this.work[i].target = _this.work[i].target.swap64();
_this.work[i].highNonce = parseInt((_this.Job[i].snonce) / 0x100000000) >>> 0;
//_this.work[i].payload = Buffer.alloc(128);
_this.work[i].data = Buffer.from(_this.Job[i].data);
_this.work[i].snonce = Buffer.alloc(8);
_this.work[i].snonce.writeUInt32LE(0, 0);
_this.work[i].snonce.writeUInt32LE(_this.work[i].highNonce, 4);
//Job.data.copy(_this.work[i].payload, 0);
//Debug.IbctLogDbg(COMP, _this.work[i].payload.toString('hex'));
_this.work[i].enonce = Buffer.alloc(8);
_this.work[i].enonce.writeUInt32LE(0xffffffff, 0);
_this.work[i].enonce.writeUInt32LE(_this.work[i].highNonce + 32, 4);
_this.submitNonce = callback;
// _this.MinerShouldStop = false;
_this.jobsDone++;
_this.work[i].jobID = _this.curJobid;
_this.curJobid++;
if (_this.curJobid === 16) _this.curJobid = 4;
/*
Debug.IbctLogInfo(COMP, _this.devPath, 'Work Target:', _this.work[i].target.toString('hex'),
'Work highNonce:', _this.work[i].highNonce.toString(16),
'Work jobID:', _this.work[i].jobID);
*/
await _this.hs1WriteJob(_this.work[i].jobID, _this.work[i].snonce, _this.work[i].enonce, _this.work[i].target, _this.work[i].data);
}
var interval = 50; //ms
var times = 7000 / interval;
waitUntil()
.interval(interval)
.times(times)
.condition(function() {
return _this.MinerShouldStop
})
.done(function(result) {
Debug.IbctLogDbg(COMP, _this.devPath, 'ScanWork Exit', result ? "(NewJob)...": "(ScanTime Out)...");
if (result === false)
_this.fakeJob.overScan = true;
else
_this.fakeJob.overScan = false;
callback(null, null, _this.fakeJob);
});
}
async setDevice(varity, freq, voltage) {
var _this = this;
_this.hs1SetHWParams(varity, freq, voltage);
}
getInfo() {
var _this = this;
return _this.info;
}
getState() {
var _this = this;
return _this.status;
}
async detect(modelName) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1 detect...');
return await _this.hs1GetStaticInfo(modelName);
}
async init(params) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1 init...');
if(_this.firmware.updating === true){
Debug.IbctLogErr(COMP, _this.devPath, 'Still Updating');
return 1;
}
if( _this.inited === true){
Debug.IbctLogErr(COMP, _this.devPath, 'Already Inited. return now');
return 0;
}
//_this.hs1SetHWParams(hs1cfg.varity, hs1cfg.targetFreq, hs1cfg.targetVoltage);
var ret = await _this.hs1SetHWParamsAndWait(_this.varity, hs1cfg.targetFreq, hs1cfg.targetVoltage);
if (ret)
return ret;
if (_this.intervalObj) {
clearInterval(_this.intervalObj);
_this.intervalObj = null;
}
_this.intervalObj = setInterval(function() {
Debug.IbctLogErr(COMP, _this.devPath, 'Temp', _this.status.temp, 'RPM', _this.status.rpm, 'Tempwarn', _this.status.tempwarn, 'Fanwarn', _this.status.fanwarn, 'Powerwarn', _this.status.powerwarn,'Freq', _this.status.freq, 'Jobs', _this.jobsDone);
_this.hs1GetState(_this);
waitUntil()
.interval(50)
.times(10)
.condition(function() {
return _this.recvQueryStatePkt
})
.done(function(result) {
if(result === false) {
_this.txTimeoutCnt++;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1GetState Timeout ', _this.txTimeoutCnt);
if(_this.txTimeoutCnt > 10) {
_this.emit("error", __('获得矿机状态超时'));
_this.txTimeoutCnt = 0;
}
} else {
_this.txTimeoutCnt = 0;
if(_this.status.fanwarn)
_this.emit("error", __('矿机风扇异常'));
if(_this.status.powerwarn)
_this.emit("error", __('矿机电源异常'));
if((_this.status.temp > hs1cfg.offTemp) || (_this.status.tempwarn)) {
_this.emit("error", __('矿机高温关机'));
} else if(_this.status.temp > hs1cfg.warnTemp && _this.status.temp < hs1cfg.offTemp) {
_this.emit("warning", __('矿机高温警报'));
}
}
});
}, 5000);
_this.inited = true;
return 0;
}
async stop(enable, wait) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1', enable ? 'remove...' : 'stop...');
if (!enable) {
await _this.hs1SetHWParamsAndWait(0, 0, 0);
}
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.freq = 0;
_this.voltage = 0;
_this.work.jobID = 0;
_this.jobsDone = 0;
_this.MinerShouldStop = true;
if (_this.intervalObj) {
clearInterval(_this.intervalObj);
_this.intervalObj = null;
}
if (enable) {
if (wait) {
await _this.port.flush(function (err) {
_this.port.close(function(err) {
if(err)
Debug.IbctLogDbg(COMP, err.message);
});
});
} else {
await this.port.flush()
await this.port.close(function(err) {
if(err)
Debug.IbctLogDbg(COMP, err.message);
});
return new Promise(function (resolve) {
waitUntil()
.interval(10)
.times(50)
.condition(function() {
return !_this.port.isOpen;
})
.done(function() {
resolve(0);
})
})
}
}
}
async burnFirmware(firmware, callback) {
var _this = this;
if(_this.firmware.updating === true) {
callback(__('升级中,请等待'));
return;
}
if(_this.inited === true) {
callback(__('挖矿中,请先暂停挖矿'));
return;
}
var ret = await _this.hs1GetStaticInfo(_this.info.modelName);
if (ret === 1) {
callback(__('无法获取当前版本号'));
return;
}
var init = await _this.hs1BurnFWInit();
if(init === 0) {
_this.firmware.updating = true;
} else {
_this.firmware.updating = false;
callback(__('烧入固件初始化失败'));
return;
}
var fwLen = firmware.length;
var totalPktNum = Math.ceil(firmware.length / _this.firmware.FWPageSize)
var id = 0;
Debug.IbctLogErr(COMP, _this.devPath, 'BurnFW, CurVersion:', _this.info.firmwareVer, 'FW lenth' , fwLen , "PKTnum", totalPktNum);
try {
while(fwLen > 0) {
var currentLen = (fwLen > _this.firmware.FWPageSize) ? _this.firmware.FWPageSize : fwLen;
var curStart = firmware.length - fwLen;
var curEnd = curStart + currentLen;
var pktUpdateFW = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeUpdateFW]),
version: Buffer.from([PV]),
pktLen: Buffer.alloc(4),
pktID:Buffer.alloc(4),
pageSize:Buffer.alloc(4),
curLen:Buffer.alloc(4),
flag:Buffer.alloc(1),
crc32: Buffer.alloc(4),
fwData:Buffer.alloc(currentLen),
ender:Buffer.from(pktEnder)
};
pktUpdateFW.curLen.writeUInt32LE(currentLen, 0);
pktUpdateFW.pktID.writeUInt32LE(id, 0);
pktUpdateFW.pageSize.writeUInt32LE(_this.firmware.FWPageSize, 0);
pktUpdateFW.pktLen.writeUInt32LE(23 + currentLen);
firmware.copy(pktUpdateFW.fwData, 0, curStart, curEnd);
fwLen -= currentLen;
if(fwLen) {
pktUpdateFW.flag[0] = 0x00;
} else {
pktUpdateFW.flag[0] = 0x01;//last
}
var msg = Buffer.alloc(23 + currentLen);
//console.log('3====>', curStart, curEnd, currentLen, fwLen);
pktUpdateFW.type.copy(msg, 0);
pktUpdateFW.version.copy(msg, 1);
pktUpdateFW.pktLen.copy(msg, 2);
pktUpdateFW.pktID.copy(msg, 6);
pktUpdateFW.pageSize.copy(msg, 10);
pktUpdateFW.curLen.copy(msg, 14);
pktUpdateFW.flag.copy(msg, 18);
pktUpdateFW.crc32.copy(msg, 19);
pktUpdateFW.fwData.copy(msg, 23);
var crcValue = parseInt(crc32(msg), 16);
pktUpdateFW.crc32.writeUInt32LE(crcValue, 0);
for(var i = 0; i < _this.firmware.retryCnt; i++) {
var success = await _this.hs1SendFWPktAndWait(i + 1, pktUpdateFW);
if(success === 0)
break;
}
if(fwLen === 0) {
_this.firmware.updating = false;
}
callback(null, ((id + 1) / totalPktNum).toFixed(3));
id++;
}
}
catch(err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Updat firmware failed ' + err.message);
_this.firmware.updating = false;
callback(err.message);
}
}
async rebootDev() {
Debug.IbctLogErr(COMP, this.devPath, 'reboot');
var _this = this;
var pktReboot = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeReboot]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
await _this.stop(true, true)
_this.hs1SendPkt(pktReboot);
}
setLed(Enable) {
Debug.IbctLogErr(COMP, 'Set', this.devPath, 'Led to', Enable ? 'ON' : 'OFF');
var _this = this;
var ledFlag = Enable === true ? 1 : 0;
var pktSetLED = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetLED]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0xb, 0x0 ,0x0 ,0x0]),
Flag:Buffer.from([ledFlag]),
led:Buffer.from([0xE8, 0x3, 0xC8, 0x0]), //ON 1s OFF:200ms
ender:Buffer.from(pktEnder)
};
_this.hs1SendPkt(pktSetLED);
}
async burnSNInfo(ptinfo) {
var _this = this
Debug.IbctLogErr(COMP, _this.devPath, 'Burn Sn Num..', ptinfo)
var pktPTInfo = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeProductTest]),
version: Buffer.from([PV]),
pktlen: Buffer.from([70, 0x0, 0x0, 0x0]),
SNInfo: Buffer.alloc(32),
HashInfo: Buffer.alloc(32),
ender: Buffer.from(pktEnder)
}
if (ptinfo.sn) {
var sn = Buffer.from(ptinfo.sn)
pktPTInfo.SNInfo[0] = sn.length
sn.copy(pktPTInfo.SNInfo, 1)
} else {
pktPTInfo.SNInfo[0] = 0
}
_this.recvPTInfoPkt = false;
_this.hs1SendPkt(pktPTInfo)
return new Promise(function (resolve, reject) {
waitUntil()
.interval(50)
.times(10)
.condition(function () {
return _this.recvPTInfoPkt
})
.done(function (result) {
if (result === false) {
Debug.IbctLogErr(COMP, _this.devPath, 'Burn SN num failed..')
resolve(__('写入SN序列号失败'))
} else {
resolve(null)
}
})
})
}
}
module.exports = function Geths1(options = {}) {
return new hs1(options);
}

909
src/miner/hs1plus.js Normal file
View file

@ -0,0 +1,909 @@
const Delimiter = require('@serialport/parser-delimiter');
const Debug = require('../log')();
var SerialPort = require('serialport');
const EventEmitter = require('events');
// var waitUntil = require('wait-until');
var waitUntil = require('../waitUntil');
var crc32 = require('crc32');
const COMP = '[HS1PLUS]:';
const hwTarget = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
const PV = 0x10;
const TypeSetBootMode = 0xA3;
const TypeUpdateFW = 0xAA;
const TypeQueryInfo = 0xA4;
const TypeProductTest = 0xAB;
const TypePlt = 0xAD
const TypeSetLED = 0xA6;
const TypeSendWork = 0xA1;
const TypeSetHWParams = 0xA2;
const TypeReboot = 0xAC;
const TypeRecvNonce = 0x51;
const TypeRecvJob = 0x55;
const TypeRecvState = 0X52;
const TypeRecvBootMode = 0x53;
const TypeRecvInfo = 0x54;
const TypeRecvFWState = 0x5A;
const TypeRecvTestResult = 0x5B;
const TypeRecvPltResult = 0x5D
const pktHeader = Buffer.from([0xA5, 0x3C, 0x96]);
const pktEnder = Buffer.from([0x69, 0xC3, 0x5A]);
const typeOffset = 0;
const hs1pcfg = {
model: "Goldshell-HS1-Plus",
algo: "blake2bsha3",
varity: 0x4,
targetFreq: 700, //MHz
targetVoltage: 420, //mv
targetTemp: 65,
warnTemp: 115,
offTemp: 125
};
class hs1p extends EventEmitter {
constructor({devPath, algo, varity, crypto}) {
super();
var _this = this;
_this.devPath = devPath;
_this.algo = algo;
_this.crypto = crypto;
_this.MinerShouldStop = false;
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.work = [{ jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }];
_this.Job = [];
_this.fakeJob = { overScan:false };
_this.job_id = null;
_this.info = {
firmwareVer: 'V0.0.1',
modelName: 'Goldshell-HS1-Plus',
sn:'unknown',
snAbbr: 'H11',
workDepth:8
};
_this.firmware = {
retryCnt : 2,
FWPageSize : 256,
curState : 0,
curID : 0
}
_this.submitNonce = null;
_this.curJobid = 1;
_this.jobsDone = 0;
_this.status = {
chips:0,
temp:0,
voltage:0,
freq:0,
varity:0x4,
cores:0,
goodcores:0,
scanbits:0,
scantime:0,
tempwarn:0,
fanwarn:0,
powerwarn:0,
rpm:0
};
_this.plt = {
chipid: 0,
reason: ''
}
_this.port = new SerialPort(_this.devPath, {
baudRate: 115200,
dataBits:8,
stopBits:1,
parity:'none',
rtscts:false
}, function (err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, '打开串口失败: ', err.message);
return;
} else
Debug.IbctLogErr(COMP, _this.devPath, '打开串口成功');
});
const parser = _this.port.pipe(new Delimiter({ delimiter: pktEnder}));
parser.on('data', function(data) {
_this.hs1pParseNotify(_this, data);
});
_this.on("error", function(err) {
Debug.IbctLogDbg(COMP, err);
})
_this.on("warning", function(err) {
Debug.IbctLogDbg(COMP, err);
})
}
hs1pParseNotify(_this, data) {
//Debug.IbctLogInfo(COMP, _this.devPath, 'Recv Pkt', data.toString('hex'));
var location = data.indexOf(pktHeader);
if(location === -1) {
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Invalid PKT', data);
return;
}
var typeLocation = location + pktHeader.length + typeOffset;
switch(data[typeLocation]) {
case TypeRecvNonce:
var jobID = data[9];
//var chipID = data[10];
//var coreID = data[11];
var nonce_l = data.readUInt32LE(12);
var nonce_h = data.readUInt32LE(16);
var nonce = Buffer.alloc(8);
nonce.writeUInt32LE(nonce_l, 0);
nonce.writeUInt32LE(nonce_h, 4);
if(jobID !== _this.work[0].jobID &&
jobID !== _this.work[1].jobID &&
jobID !== _this.work[2].jobID &&
jobID !== _this.work[3].jobID &&
jobID !== _this.work[4].jobID &&
jobID !== _this.work[5].jobID &&
jobID !== _this.work[6].jobID &&
jobID !== _this.work[7].jobID) {
Debug.IbctLogDbg(COMP, _this.devPath, 'Find Stale ', jobID, _this.work[0].jobID, _this.work[1].jobID, nonce.toString('hex'));
} else {
//Debug.IbctLogDbg(COMP, _this.devPath, 'Find Nonce ', nonce.toString('hex'));
let i;
if (jobID === _this.work[0].jobID) i = 0;
else if (jobID === _this.work[1].jobID) i = 1;
else if (jobID === _this.work[2].jobID) i = 2;
else if (jobID === _this.work[3].jobID) i = 3;
else if (jobID === _this.work[4].jobID) i = 4;
else if (jobID === _this.work[5].jobID) i = 5;
else if (jobID === _this.work[6].jobID) i = 6;
else i = 7;
if(_this.submitNonce)
_this.submitNonce(null, nonce, _this.Job[i]);
}
break;
case TypeRecvInfo:
_this.recvInfoPkt = true;
//Debug.IbctLogDbg(COMP, _this.devPath, 'RecvInfoPkt:', data.toString('hex'));
_this.info.modelName = data.toString('utf8', 10, 10 + data[9]);
_this.info.firmwareVer = data.toString('utf8', 31, 31 + data[30]);
_this.info.sn = data.toString('utf8', 40, 40 + data[39]);
_this.info.snAbbr = 'H11';
//_this.info.workDepth = data[57];
Debug.IbctLogDbg(_this.info);
break;
case TypeRecvFWState:
_this.recvFWStatePkt = true;
_this.firmware.curID = data.readUInt32LE(9);
_this.firmware.curState = data[13];
break;
case TypeRecvJob:
_this.recvJobPkt = true;
break;
case TypeRecvBootMode:
_this.recvBootModePkt = true;
break;
case TypeRecvPltResult:
_this.recvPltPkt = true;
_this.plt.chipid = data[9];
_this.plt.reason = data.toString('utf8', 10, 11);
break;
case TypeRecvState:
_this.recvStatePkt = true;
_this.recvQueryStatePkt = true;
//Debug.IbctLogInfo(COMP, _this.devPath, 'RecvState pkt:', data.toString('hex'));
_this.status.chips = data[9];
_this.status.cores = data[10];
_this.status.goodcores = data[11];
_this.status.scanbits = data[12];
_this.status.scantime = data.readUInt16LE(13) * 100; //ms
_this.status.voltage = data.readUInt16LE(15); //mV
_this.status.freq = data.readUInt16LE(17); //MHz
_this.status.varity = data.readUInt32LE(19);
_this.status.temp = data[23];
_this.status.hwreboot = data[24];
_this.status.tempwarn = data[25];
_this.status.fanwarn = data[26];
_this.status.powerwarn = data[27];
_this.status.rpm = data.readUInt16LE(28);
break;
case TypeRecvTestResult:
_this.recvPTInfoPkt = true;
break;
default:
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Unsupported PKT Type', data[location + typeOffset])
break;
}
return;
}
hs1pSendPkt (pkt) {
var _this = this;
var offset = 0;
var length = 0;
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
length += pkt[key].length;
}
});
var msg = Buffer.alloc(length);
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
pkt[key].copy(msg, offset);
offset += pkt[key].length;
}
});
//Debug.IbctLogDbg(COMP, _this.devPath, 'Send pkt', msg.toString('hex'));
_this.port.write(msg, function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on write: ', err.message)
}
_this.port.drain(function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on Drain: ', _this.devPath, err.message)
}
})
})
}
hs1pGetState(_this) {
var pktQueryStatus = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x7, 0x0 ,0x0 ,0x0]),
flag:Buffer.from([0x52]),
ender:Buffer.from(pktEnder)
};
_this.recvQueryStatePkt = false;
_this.hs1pSendPkt(pktQueryStatus);
/*TODO Wait response here*/
return true;
}
async hs1pGetStaticInfo(modelName) {
var _this = this;
var pktQueryInfo = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeQueryInfo]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvInfoPkt = false;
_this.hs1pSendPkt(pktQueryInfo);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(50)
.times(10)
.condition(function() {
return _this.recvInfoPkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '获取矿机信息出错');
resolve(2);
} else {
if(modelName === _this.info.modelName) {
resolve(0);
} else {
resolve(1);
}
}
})
});
}
hs1pSetBootMode() {
var _this = this;
var pktSetBootMode = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetBootMode]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x7, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvBootModePkt = false;
_this.hs1pSendPkt(pktSetBootMode);
}
hs1pBurnFWInit() {
var _this = this;
var pktSetBootMode = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetBootMode]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvBootModePkt = false;
_this.hs1pSendPkt(pktSetBootMode);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvBootModePkt
})
.done(function(result) {
if(result === false) {
resolve(1);
} else {
resolve(0);
}
})
})
}
hs1pSetHWParams(varity, freq, voltage) {
var _this = this;
var pktSetParam = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x10, 0x00, 0x00, 0x00]),
flag:Buffer.from([0xA2]),
voltage: Buffer.alloc(2),
freq:Buffer.alloc(2),
varity:Buffer.alloc(4),
targettemp:Buffer.from([80]),
ender:Buffer.from(pktEnder)
};
pktSetParam.varity.writeUInt32LE(varity, 0);
pktSetParam.freq.writeUInt16LE(freq, 0);
pktSetParam.voltage.writeUInt16LE(voltage, 0);
_this.recvStatePkt = false;
_this.hs1pSendPkt(pktSetParam);
/*TODO Wait response here*/
return true;
}
hs1pSetHWParamsAndWait(varity, freq, voltage) {
var _this = this;
var pktSetParam = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x10, 0x00, 0x00, 0x00]),
flag:Buffer.from([0xA2]),
voltage: Buffer.alloc(2),
freq:Buffer.alloc(2),
varity:Buffer.alloc(4),
targettemp:Buffer.from([80]),
ender:Buffer.from(pktEnder)
};
pktSetParam.varity.writeUInt32LE(varity, 0);
pktSetParam.freq.writeUInt16LE(freq, 0);
pktSetParam.voltage.writeUInt16LE(voltage, 0);
_this.recvStatePkt = false;
_this.hs1pSendPkt(pktSetParam);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvStatePkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '设置矿机参数出错');
resolve(1);
} else {
resolve(0);
}
})
})
}
async hs1pWriteJob(jobID, snonce, enonce, target, data) {
var _this = this;
_this.recvJobPkt = false;
var pktSendJob = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSendWork]),
version: Buffer.from([PV]),
pktlen: Buffer.alloc(4),
target:Buffer.alloc(8),
startNonce: Buffer.alloc(8),
endNonce:Buffer.alloc(8),
jobNum: Buffer.from([1]),
jobID: Buffer.alloc(1),
jobData:Buffer.alloc(128),
ender:Buffer.from(pktEnder)
};
pktSendJob.pktlen.writeUInt32LE(160, 0); //pktlen
target.copy(pktSendJob.target, 0);
snonce.copy(pktSendJob.startNonce, 0);
enonce.copy(pktSendJob.endNonce, 0);
pktSendJob.jobID[0] = jobID;
data.copy(pktSendJob.jobData, 0);
_this.hs1pSendPkt(pktSendJob);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(40)
.times(50)
.condition(function() {
return _this.recvJobPkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '发送Work失败');
reject(new Error(__('发送Work失败')).message);
} else {
resolve(0);
}
})
})
}
hs1pSendFWPktAndWait(cnt, FWPKT) {
var _this = this;
_this.recvFWStatePkt = false;
_this.firmware.curState = 0;
_this.firmware.curID = 0;
_this.hs1pSendPkt(FWPKT);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvFWStatePkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '烧录连接超时');
reject(new Error(__('烧录连接超时')).message);
} else if(_this.firmware.curState === 0x02) {
if(cnt < _this.firmware.retryCnt){
resolve(1);
} else {
Debug.IbctLogErr(COMP, _this.devPath, '串口出错');
reject(new Error(__('串口出错')).message);
}
} else {
resolve(0);
}
})
})
}
async stopScanWork() {
var _this = this;
Debug.IbctLogDbg(COMP, _this.devPath, 'stopScanWork ...');
//_this.curJobid = 0;
_this.MinerShouldStop = true;
}
async scanWork(workQueue, callback) {
var _this = this;
Debug.IbctLogInfo(COMP, _this.devPath, 'scanWork Begin ...');
if (!_this.inited) {
callback(null, null, null);
return;
}
if(_this.status.scantime === 0) {
_this.status.scantime = 7000; //10s default
}
if (_this.MinerShouldStop) {
_this.MinerShouldStop = false;
if (_this.job_id === workQueue[0].job_id) {
// new job arrived
_this.fakeJob.overScan = false;
// clean all workQueue
workQueue.splice(0, workQueue.length);
callback(null, null, _this.fakeJob);
Debug.IbctLogDbg("new job arrived, scanWork again");
return;
}
}
_this.job_id = workQueue[0].job_id;
for(let i = 0; i < _this.info.workDepth; i++) {
_this.Job[i] = workQueue.pop();
_this.work[i].target = Buffer.alloc(8);
var target = _this.Job[i].target;
if(Buffer.compare(hwTarget, target) > 0) {
hwTarget.copy(_this.work[i].target, 0);
_this.Job[i].hwTarget = hwTarget;
} else {
target.copy(_this.work[i].target, 0);
_this.Job[i].hwTarget = target;
}
_this.work[i].target = _this.work[i].target.swap64();
_this.work[i].highNonce = parseInt((_this.Job[i].snonce) / 0x100000000) >>> 0;
_this.work[i].data = Buffer.from(_this.Job[i].data);
_this.work[i].snonce = Buffer.alloc(8);
_this.work[i].snonce.writeUInt32LE(0, 0);
_this.work[i].snonce.writeUInt32LE(_this.work[i].highNonce, 4);
//Job.data.copy(_this.work[i].payload, 0);
//Debug.IbctLogDbg(COMP, _this.work[i].payload.toString('hex'));
_this.work[i].enonce = Buffer.alloc(8);
_this.work[i].enonce.writeUInt32LE(0xffffffff, 0);
_this.work[i].enonce.writeUInt32LE(_this.work[i].highNonce + 32, 4);
_this.submitNonce = callback;
// _this.MinerShouldStop = false;
_this.jobsDone++;
_this.work[i].jobID = _this.curJobid;
_this.curJobid = (_this.curJobid + 1) & 0xff;
if (!_this.curJobid) _this.curJobid = 1;
/*
Debug.IbctLogInfo(COMP, _this.devPath, 'Work Target:', _this.work[i].target.toString('hex'),
'Work highNonce:', _this.work[i].highNonce.toString(16),
'Work jobID:', _this.work[i].jobID);
*/
await _this.hs1pWriteJob(_this.work[i].jobID, _this.work[i].snonce, _this.work[i].enonce, _this.work[i].target, _this.work[i].data);
}
var interval = 50; //ms
var times = 7000 / interval;
waitUntil()
.interval(interval)
.times(times)
.condition(function() {
return _this.MinerShouldStop
})
.done(function(result) {
Debug.IbctLogDbg(COMP, _this.devPath, 'ScanWork Exit', result ? "(NewJob)...": "(ScanTime Out)...");
if (result === false)
_this.fakeJob.overScan = true;
else
_this.fakeJob.overScan = false;
callback(null, null, _this.fakeJob);
});
}
async setDevice(varity, freq, voltage) {
var _this = this;
_this.hs1pSetHWParams(varity, freq, voltage);
}
getInfo() {
var _this = this;
return _this.info;
}
getPlt() {
var _this = this
return _this.plt
}
getState() {
var _this = this;
return _this.status;
}
async detect(modelName) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1p detect...');
return await _this.hs1pGetStaticInfo(modelName);
}
async init(params) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1p init...');
if(_this.firmware.updating === true){
Debug.IbctLogErr(COMP, _this.devPath, 'Still Updating');
return 1;
}
if( _this.inited === true){
Debug.IbctLogErr(COMP, _this.devPath, 'Already Inited. return now');
return 0;
}
//_this.hs1pSetHWParams(hs1pcfg.varity, hs1pcfg.targetFreq, hs1pcfg.targetVoltage);
var ret = await _this.hs1pSetHWParamsAndWait(_this.varity, hs1pcfg.targetFreq, hs1pcfg.targetVoltage);
if (ret)
return ret;
if (_this.intervalObj) {
clearInterval(_this.intervalObj);
_this.intervalObj = null;
}
_this.intervalObj = setInterval(function() {
Debug.IbctLogErr(COMP, _this.devPath, 'Temp', _this.status.temp, 'RPM', _this.status.rpm, 'Tempwarn', _this.status.tempwarn, 'Fanwarn', _this.status.fanwarn, 'Powerwarn', _this.status.powerwarn,'Freq', _this.status.freq, 'Jobs', _this.jobsDone);
_this.hs1pGetState(_this);
waitUntil()
.interval(50)
.times(10)
.condition(function() {
return _this.recvQueryStatePkt
})
.done(function(result) {
if(result === false) {
_this.txTimeoutCnt++;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1pGetState Timeout ', _this.txTimeoutCnt);
if(_this.txTimeoutCnt > 10) {
_this.emit("error", __('获得矿机状态超时'));
_this.txTimeoutCnt = 0;
}
} else {
_this.txTimeoutCnt = 0;
if(_this.status.fanwarn)
_this.emit("error", __('矿机风扇异常'));
if(_this.status.powerwarn)
_this.emit("error", __('矿机电源异常'));
if((_this.status.temp > hs1pcfg.offTemp) || (_this.status.tempwarn)) {
_this.emit("error", __('矿机高温关机'));
} else if(_this.status.temp > hs1pcfg.warnTemp && _this.status.temp < hs1pcfg.offTemp) {
_this.emit("warning", __('矿机高温警报'));
}
}
});
}, 5000);
_this.inited = true;
return 0;
}
async stop(enable, wait) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'hs1p', enable ? 'remove...' : 'stop...');
if (!enable) {
await _this.hs1pSetHWParamsAndWait(0, 0, 0);
}
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.freq = 0;
_this.voltage = 0;
_this.work.jobID = 0;
_this.jobsDone = 0;
_this.MinerShouldStop = true;
if (_this.intervalObj) {
clearInterval(_this.intervalObj);
_this.intervalObj = null;
}
if (enable) {
if (wait === true) {
await _this.port.flush(function (err) {
_this.port.close(function(err) {
if(err)
Debug.IbctLogDbg(COMP, err.message);
});
});
} else {
await this.port.flush()
await this.port.close(function(err) {
if(err)
Debug.IbctLogDbg(COMP, err.message);
});
return new Promise(function (resolve) {
waitUntil()
.interval(10)
.times(50)
.condition(function() {
return !_this.port.isOpen;
})
.done(function() {
resolve(0);
})
})
}
}
}
async burnFirmware(firmware, callback) {
var _this = this;
if(_this.firmware.updating === true) {
callback(__('升级中,请等待'));
return;
}
if(_this.inited === true) {
callback(__('挖矿中,请先暂停挖矿'));
return;
}
var ret = await _this.hs1pGetStaticInfo(_this.info.modelName);
if (ret === 1) {
callback(__('无法获取当前版本号'));
return;
}
var init = await _this.hs1pBurnFWInit();
if(init === 0) {
_this.firmware.updating = true;
} else {
_this.firmware.updating = false;
callback(__('烧入固件初始化失败'));
return;
}
var fwLen = firmware.length;
var totalPktNum = Math.ceil(firmware.length / _this.firmware.FWPageSize)
var id = 0;
Debug.IbctLogErr(COMP, _this.devPath, 'BurnFW, CurVersion:', _this.info.firmwareVer, 'FW lenth' , fwLen , "PKTnum", totalPktNum);
try {
while(fwLen > 0) {
var currentLen = (fwLen > _this.firmware.FWPageSize) ? _this.firmware.FWPageSize : fwLen;
var curStart = firmware.length - fwLen;
var curEnd = curStart + currentLen;
var pktUpdateFW = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeUpdateFW]),
version: Buffer.from([PV]),
pktLen: Buffer.alloc(4),
pktID:Buffer.alloc(4),
pageSize:Buffer.alloc(4),
curLen:Buffer.alloc(4),
flag:Buffer.alloc(1),
crc32: Buffer.alloc(4),
fwData:Buffer.alloc(currentLen),
ender:Buffer.from(pktEnder)
};
pktUpdateFW.curLen.writeUInt32LE(currentLen, 0);
pktUpdateFW.pktID.writeUInt32LE(id, 0);
pktUpdateFW.pageSize.writeUInt32LE(_this.firmware.FWPageSize, 0);
pktUpdateFW.pktLen.writeUInt32LE(23 + currentLen);
firmware.copy(pktUpdateFW.fwData, 0, curStart, curEnd);
fwLen -= currentLen;
if(fwLen) {
pktUpdateFW.flag[0] = 0x00;
} else {
pktUpdateFW.flag[0] = 0x01;//last
}
var msg = Buffer.alloc(23 + currentLen);
//console.log('3====>', curStart, curEnd, currentLen, fwLen);
pktUpdateFW.type.copy(msg, 0);
pktUpdateFW.version.copy(msg, 1);
pktUpdateFW.pktLen.copy(msg, 2);
pktUpdateFW.pktID.copy(msg, 6);
pktUpdateFW.pageSize.copy(msg, 10);
pktUpdateFW.curLen.copy(msg, 14);
pktUpdateFW.flag.copy(msg, 18);
pktUpdateFW.crc32.copy(msg, 19);
pktUpdateFW.fwData.copy(msg, 23);
var crcValue = parseInt(crc32(msg), 16);
pktUpdateFW.crc32.writeUInt32LE(crcValue, 0);
for(var i = 0; i < _this.firmware.retryCnt; i++) {
var success = await _this.hs1pSendFWPktAndWait(i + 1, pktUpdateFW);
if(success === 0)
break;
}
if(fwLen === 0) {
_this.firmware.updating = false;
}
callback(null, ((id + 1) / totalPktNum).toFixed(3));
id++;
}
}
catch(err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Updat firmware failed ' + err.message);
_this.firmware.updating = false;
callback(err.message);
}
}
async rebootDev() {
Debug.IbctLogErr(COMP, this.devPath, 'reboot');
var _this = this;
var pktReboot = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeReboot]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
await _this.stop(true, true)
_this.hs1pSendPkt(pktReboot);
}
setPlt() {
var _this = this
console.log('function test')
_this.recvPltPkt = false
var pktPLT = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypePlt]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0, 0x0, 0x0]),
ender: Buffer.from(pktEnder)
}
_this.hs1pSendPkt(pktPLT)
}
setLed(Enable) {
Debug.IbctLogErr(COMP, 'Set', this.devPath, 'Led to', Enable ? 'ON' : 'OFF');
var _this = this;
var ledFlag = Enable === true ? 1 : 0;
var pktSetLED = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetLED]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0xb, 0x0 ,0x0 ,0x0]),
Flag:Buffer.from([ledFlag]),
led:Buffer.from([0xE8, 0x3, 0xC8, 0x0]), //ON 1s OFF:200ms
ender:Buffer.from(pktEnder)
};
_this.hs1pSendPkt(pktSetLED);
}
async burnSNInfo(ptinfo) {
var _this = this
Debug.IbctLogErr(COMP, _this.devPath, 'Burn Sn Num..', ptinfo)
var pktPTInfo = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeProductTest]),
version: Buffer.from([PV]),
pktlen: Buffer.from([70, 0x0, 0x0, 0x0]),
SNInfo: Buffer.alloc(32),
HashInfo: Buffer.alloc(32),
ender: Buffer.from(pktEnder)
}
if (ptinfo.sn) {
var sn = Buffer.from(ptinfo.sn)
pktPTInfo.SNInfo[0] = sn.length
sn.copy(pktPTInfo.SNInfo, 1)
} else {
pktPTInfo.SNInfo[0] = 0
}
_this.recvPTInfoPkt = false;
_this.hs1pSendPkt(pktPTInfo)
return new Promise(function (resolve, reject) {
waitUntil()
.interval(50)
.times(10)
.condition(function () {
return _this.recvPTInfoPkt
})
.done(function (result) {
if (result === false) {
Debug.IbctLogErr(COMP, _this.devPath, 'Burn SN num failed..')
resolve(__('写入SN序列号失败'))
} else {
resolve(null)
}
})
})
}
}
module.exports = function Geths1p(options = {}) {
return new hs1p(options);
}

922
src/miner/lb1.js Normal file
View file

@ -0,0 +1,922 @@
const Delimiter = require('@serialport/parser-delimiter');
const Debug = require('../log')();
var SerialPort = require('serialport');
const EventEmitter = require('events');
var waitUntil = require('../waitUntil');
var crc32 = require('crc32');
const COMP = '[LB1]:';
const hwTarget = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
const PV = 0x10;
const TypeSetBootMode = 0xA3;
const TypeUpdateFW = 0xAA;
const TypeQueryInfo = 0xA4;
const TypeProductTest = 0xAB;
const TypePlt = 0xAD
const TypeSetLED = 0xA6;
const TypeSendWork = 0xA1;
const TypeSetHWParams = 0xA2;
const TypeReboot = 0xAC;
const TypeRecvNonce = 0x51;
const TypeRecvJob = 0x55;
const TypeRecvState = 0X52;
const TypeRecvBootMode = 0x53;
const TypeRecvInfo = 0x54;
const TypeRecvFWState = 0x5A;
const TypeRecvTestResult = 0x5B;
const TypeRecvPltResult = 0x5D
const pktHeader = Buffer.from([0xA5, 0x3C, 0x96]);
const pktEnder = Buffer.from([0x69, 0xC3, 0x5A]);
const typeOffset = 0;
const lb1cfg = {
model: "Goldshell-LB1",
algo: "lbry",
varity: 0x11,
targetFreq: 750, //MHz
targetVoltage: 430, //mv
targetTemp: 65,
warnTemp: 115,
offTemp: 125
};
class lb1 extends EventEmitter {
constructor({devPath, algo, varity, crypto}) {
super();
var _this = this;
_this.devPath = devPath;
_this.algo = algo;
_this.crypto = crypto;
_this.MinerShouldStop = false;
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.work = [{ jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }];
_this.Job = [];
_this.fakeJob = { overScan:false };
_this.job_id = null;
_this.info = {
firmwareVer: 'V0.0.1',
modelName: 'Goldshell-LB1',
sn: 'unknown',
mc: 'unknown',
snAbbr: 'LB10',
workDepth: 8
};
_this.firmware = {
retryCnt : 2,
FWPageSize : 256,
curState : 0,
curID : 0
}
_this.submitNonce = null;
_this.curJobid = 1;
_this.jobsDone = 0;
_this.status = {
chips:0,
temp:0,
voltage:0,
freq:0,
varity:0x11,
cores:0,
goodcores:0,
scanbits:0,
scantime:0,
tempwarn:0,
fanwarn:0,
powerwarn:0,
rpm:0
};
_this.plt = {
chipid: 0,
reason: ''
}
_this.port = new SerialPort(_this.devPath, {
baudRate: 115200,
dataBits:8,
stopBits:1,
parity:'none',
rtscts:false
}, function (err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, '打开串口失败: ', err.message);
return;
} else
Debug.IbctLogErr(COMP, _this.devPath, '打开串口成功');
});
const parser = _this.port.pipe(new Delimiter({ delimiter: pktEnder}));
parser.on('data', function(data) {
_this.lb1ParseNotify(_this, data);
});
_this.on("error", function(err) {
Debug.IbctLogDbg(COMP, err);
})
_this.on("warning", function(err) {
Debug.IbctLogDbg(COMP, err);
})
}
lb1ParseNotify(_this, data) {
//Debug.IbctLogInfo(COMP, _this.devPath, 'Recv Pkt', data.toString('hex'));
var location = data.indexOf(pktHeader);
if(location === -1) {
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Invalid PKT', data);
return;
}
var typeLocation = location + pktHeader.length + typeOffset;
switch(data[typeLocation]) {
case TypeRecvNonce:
var jobID = data[9];
//var chipID = data[10];
//var coreID = data[11];
var nonce_l = data.readUInt32LE(12);
var tt = data.readUInt32LE(16);
var nonce = Buffer.alloc(8, 0);
nonce.writeUInt32LE(nonce_l, 0);
nonce.writeUInt32LE(tt, 4);
if(jobID !== _this.work[0].jobID &&
jobID !== _this.work[1].jobID &&
jobID !== _this.work[2].jobID &&
jobID !== _this.work[3].jobID &&
jobID !== _this.work[4].jobID &&
jobID !== _this.work[5].jobID &&
jobID !== _this.work[6].jobID &&
jobID !== _this.work[7].jobID) {
Debug.IbctLogDbg(COMP, _this.devPath, 'Find Stale ', jobID, _this.work[0].jobID, _this.work[1].jobID, nonce.toString('hex'));
} else {
//console.log("noncel", nonce_l.toString(16));
//console.log("ntime", ntime.toString(16));
//Debug.IbctLogDbg(COMP, _this.devPath, 'Find Nonce ', nonce.toString('hex'));
let i;
if (jobID === _this.work[0].jobID) i = 0;
else if (jobID === _this.work[1].jobID) i = 1;
else if (jobID === _this.work[2].jobID) i = 2;
else if (jobID === _this.work[3].jobID) i = 3;
else if (jobID === _this.work[4].jobID) i = 4;
else if (jobID === _this.work[5].jobID) i = 5;
else if (jobID === _this.work[6].jobID) i = 6;
else i = 7;
if(_this.submitNonce)
_this.submitNonce(null, nonce, _this.Job[i]);
}
break;
case TypeRecvInfo:
_this.recvInfoPkt = true;
//Debug.IbctLogDbg(COMP, _this.devPath, 'RecvInfoPkt:', data.toString('hex'));
_this.info.modelName = data.toString('utf8', 10, 10 + data[9]);
_this.info.firmwareVer = data.toString('utf8', 27, 27 + data[26]);
_this.info.sn = data.toString('utf8', 36, 36 + data[35]);
_this.info.mc = data.toString('utf8', 53, 55);
_this.info.snAbbr = 'LB10';
//console.log(_this.info);
break;
case TypeRecvFWState:
_this.recvFWStatePkt = true;
_this.firmware.curID = data.readUInt32LE(9);
_this.firmware.curState = data[13];
break;
case TypeRecvJob:
_this.recvJobPkt = true;
break;
case TypeRecvBootMode:
_this.recvBootModePkt = true;
break;
case TypeRecvPltResult:
_this.recvPltPkt = true;
_this.plt.chipid = data[9];
_this.plt.reason = data.toString('utf8', 10, 11);
break;
case TypeRecvState:
_this.recvStatePkt = true;
_this.recvQueryStatePkt = true;
//Debug.IbctLogInfo(COMP, _this.devPath, 'RecvState pkt:', data.toString('hex'));
_this.status.chips = data[9];
_this.status.cores = data[10];
_this.status.goodcores = data[11];
_this.status.scanbits = data[12];
_this.status.scantime = data.readUInt16LE(13) * 100; //ms
_this.status.voltage = data.readUInt16LE(15); //mV
_this.status.freq = data.readUInt16LE(17); //MHz
_this.status.varity = data.readUInt32LE(19);
_this.status.temp = data[23];
_this.status.hwreboot = data[24];
_this.status.tempwarn = data[25];
_this.status.fanwarn = data[26];
_this.status.powerwarn = data[27];
_this.status.rpm = data.readUInt16LE(28);
break;
case TypeRecvTestResult:
_this.recvPTInfoPkt = true;
break;
default:
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Unsupported PKT Type', data[location + typeOffset])
break;
}
return;
}
lb1SendPkt (pkt) {
var _this = this;
var offset = 0;
var length = 0;
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
length += pkt[key].length;
}
});
var msg = Buffer.alloc(length);
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
pkt[key].copy(msg, offset);
offset += pkt[key].length;
}
});
//Debug.IbctLogDbg(COMP, _this.devPath, 'Send pkt', msg.toString('hex'));
_this.port.write(msg, function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on write: ', err.message)
}
_this.port.drain(function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on Drain: ', _this.devPath, err.message)
}
})
})
}
lb1GetState(_this) {
var pktQueryStatus = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x7, 0x0 ,0x0 ,0x0]),
flag:Buffer.from([0x52]),
ender:Buffer.from(pktEnder)
};
_this.recvQueryStatePkt = false;
_this.lb1SendPkt(pktQueryStatus);
/*TODO Wait response here*/
return true;
}
async lb1GetStaticInfo(modelName) {
var _this = this;
var pktQueryInfo = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeQueryInfo]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvInfoPkt = false;
_this.lb1SendPkt(pktQueryInfo);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(50)
.times(10)
.condition(function() {
return _this.recvInfoPkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '获取矿机信息出错');
resolve(2);
} else {
if(modelName === _this.info.modelName) {
resolve(0);
} else {
resolve(1);
}
}
})
});
}
lb1SetBootMode() {
var _this = this;
var pktSetBootMode = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetBootMode]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x7, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvBootModePkt = false;
_this.lb1SendPkt(pktSetBootMode);
}
lb1BurnFWInit() {
var _this = this;
var pktSetBootMode = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetBootMode]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
_this.recvBootModePkt = false;
_this.lb1SendPkt(pktSetBootMode);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvBootModePkt
})
.done(function(result) {
if(result === false) {
resolve(1);
} else {
resolve(0);
}
})
})
}
lb1SetHWParams(varity, freq, voltage) {
var _this = this;
var pktSetParam = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x10, 0x00, 0x00, 0x00]),
flag:Buffer.from([0xA2]),
voltage: Buffer.alloc(2),
freq:Buffer.alloc(2),
varity:Buffer.alloc(4),
targettemp:Buffer.from([80]),
ender:Buffer.from(pktEnder)
};
pktSetParam.varity.writeUInt32LE(varity, 0);
pktSetParam.freq.writeUInt16LE(freq, 0);
pktSetParam.voltage.writeUInt16LE(voltage, 0);
_this.recvStatePkt = false;
_this.lb1SendPkt(pktSetParam);
return true;
}
lb1SetHWParamsAndWait(varity, freq, voltage) {
var _this = this;
var pktSetParam = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetHWParams]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x10, 0x00, 0x00, 0x00]),
flag:Buffer.from([0xA2]),
voltage: Buffer.alloc(2),
freq:Buffer.alloc(2),
varity:Buffer.alloc(4),
targettemp:Buffer.from([80]),
ender:Buffer.from(pktEnder)
};
pktSetParam.varity.writeUInt32LE(varity, 0);
pktSetParam.freq.writeUInt16LE(freq, 0);
pktSetParam.voltage.writeUInt16LE(voltage, 0);
_this.recvStatePkt = false;
_this.lb1SendPkt(pktSetParam);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvStatePkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '设置矿机参数出错');
resolve(1);
} else {
resolve(0);
}
})
})
}
async lb1WriteJob(jobID, snonce, enonce, target, data) {
var _this = this;
_this.recvJobPkt = false;
var pktSendJob = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSendWork]),
version: Buffer.from([PV]),
pktlen: Buffer.alloc(4),
target:Buffer.alloc(8),
startNonce: Buffer.alloc(8),
endNonce:Buffer.alloc(8),
jobNum: Buffer.from([1]),
jobID: Buffer.alloc(1),
jobData:Buffer.alloc(136),
ender:Buffer.from(pktEnder)
};
pktSendJob.pktlen.writeUInt32LE(168, 0); //pktlen
target.copy(pktSendJob.target, 0);
snonce.copy(pktSendJob.startNonce, 0);
enonce.copy(pktSendJob.endNonce, 0);
pktSendJob.jobID[0] = jobID;
data.copy(pktSendJob.jobData, 0);
_this.lb1SendPkt(pktSendJob);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(40)
.times(50)
.condition(function() {
return _this.recvJobPkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '发送Work失败');
reject(new Error(__('发送Work失败')).message);
} else {
resolve(0);
}
})
})
}
lb1SendFWPktAndWait(cnt, FWPKT) {
var _this = this;
_this.recvFWStatePkt = false;
_this.firmware.curState = 0;
_this.firmware.curID = 0;
_this.lb1SendPkt(FWPKT);
return new Promise(function (resolve, reject) {
waitUntil()
.interval(20)
.times(50)
.condition(function() {
return _this.recvFWStatePkt
})
.done(function(result) {
if(result === false) {
Debug.IbctLogErr(COMP, _this.devPath, '烧录连接超时');
reject(new Error(__('烧录连接超时')).message);
} else if(_this.firmware.curState === 0x02) {
if(cnt < _this.firmware.retryCnt){
resolve(1);
} else {
Debug.IbctLogErr(COMP, _this.devPath, '串口出错');
reject(new Error(__('串口出错')).message);
}
} else {
resolve(0);
}
})
})
}
async stopScanWork() {
var _this = this;
Debug.IbctLogDbg(COMP, _this.devPath, 'stopScanWork ...');
//_this.curJobid = 0;
_this.MinerShouldStop = true;
}
async scanWork(workQueue, callback) {
var _this = this;
Debug.IbctLogInfo(COMP, _this.devPath, 'scanWork Begin ...');
if (!_this.inited) {
callback(null, null, null);
return;
}
if(_this.status.scantime === 0) {
_this.status.scantime = 6000; //10s default
}
if (_this.MinerShouldStop) {
_this.MinerShouldStop = false;
if (_this.job_id === workQueue[0].job_id) {
// new job arrived
_this.fakeJob.overScan = false;
// clean all workQueue
workQueue.splice(0, workQueue.length);
callback(null, null, _this.fakeJob);
Debug.IbctLogDbg("new job arrived, scanWork again");
return;
}
}
_this.job_id = workQueue[0].job_id;
for(let i = 0; i < _this.info.workDepth; i++) {
_this.Job[i] = workQueue.pop();
_this.work[i].target = Buffer.alloc(8);
var target = _this.Job[i].target;
if(Buffer.compare(hwTarget, target) > 0) {
hwTarget.copy(_this.work[i].target, 0);
_this.Job[i].hwTarget = hwTarget;
} else {
target.copy(_this.work[i].target, 0);
_this.Job[i].hwTarget = target;
}
_this.work[i].target = _this.work[i].target.swap64();
_this.work[i].highNonce = parseInt((_this.Job[i].snonce) / 0x100000000) >>> 0;
_this.work[i].data = Buffer.from(_this.Job[i].data);
_this.work[i].snonce = Buffer.alloc(8);
_this.work[i].snonce.writeUInt32LE(0, 0);
_this.work[i].snonce.writeUInt32LE(_this.work[i].highNonce, 4);
//Job.data.copy(_this.work[i].payload, 0);
//Debug.IbctLogDbg(COMP, _this.work[i].payload.toString('hex'));
_this.work[i].enonce = Buffer.alloc(8);
_this.work[i].enonce.writeUInt32LE(0xffffffff, 0);
_this.work[i].enonce.writeUInt32LE(0x00000000, 4);
//_this.work[i].enonce.writeUInt32LE(_this.work[i].highNonce + 32, 4);
_this.submitNonce = callback;
// _this.MinerShouldStop = false;
_this.jobsDone++;
_this.work[i].jobID = _this.curJobid;
_this.curJobid = (_this.curJobid + 1) & 0xff;
if (!_this.curJobid) _this.curJobid = 1;
/*
Debug.IbctLogInfo(COMP, _this.devPath, 'Work Target:', _this.work[i].target.toString('hex'),
'Work highNonce:', _this.work[i].highNonce.toString(16),
'Work jobID:', _this.work[i].jobID);
*/
await _this.lb1WriteJob(_this.work[i].jobID, _this.work[i].snonce, _this.work[i].enonce, _this.work[i].target, _this.work[i].data);
}
var interval = 50; //ms
var times = 6000 / interval;
waitUntil()
.interval(interval)
.times(times)
.condition(function() {
return _this.MinerShouldStop
})
.done(function(result) {
Debug.IbctLogDbg(COMP, _this.devPath, 'ScanWork Exit', result ? "(NewJob)...": "(ScanTime Out)...");
if (result === false)
_this.fakeJob.overScan = true;
else
_this.fakeJob.overScan = false;
callback(null, null, _this.fakeJob);
});
}
async setDevice(varity, freq, voltage) {
var _this = this;
_this.lb1SetHWParams(varity, freq, voltage);
}
getInfo() {
var _this = this;
return _this.info;
}
getPlt() {
var _this = this
return _this.plt
}
getState() {
var _this = this;
return _this.status;
}
async detect(modelName) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'lb1 detect...');
return await _this.lb1GetStaticInfo(modelName);
}
async init(params) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'lb1 init...');
if(_this.firmware.updating === true){
Debug.IbctLogErr(COMP, _this.devPath, 'Still Updating');
return 1;
}
if( _this.inited === true){
Debug.IbctLogErr(COMP, _this.devPath, 'Already Inited. return now');
return 0;
}
//_this.lb1SetHWParams(lb1cfg.varity, lb1cfg.targetFreq, lb1cfg.targetVoltage);
var ret = await _this.lb1SetHWParamsAndWait(_this.varity, lb1cfg.targetFreq, lb1cfg.targetVoltage);
if (ret)
return ret;
if (_this.intervalObj) {
clearInterval(_this.intervalObj);
_this.intervalObj = null;
}
_this.intervalObj = setInterval(function() {
Debug.IbctLogErr(COMP, _this.devPath, 'Temp', _this.status.temp, 'RPM', _this.status.rpm, 'Tempwarn', _this.status.tempwarn, 'Fanwarn', _this.status.fanwarn, 'Powerwarn', _this.status.powerwarn,'Freq', _this.status.freq, 'Jobs', _this.jobsDone);
_this.lb1GetState(_this);
waitUntil()
.interval(50)
.times(10)
.condition(function() {
return _this.recvQueryStatePkt
})
.done(function(result) {
if(result === false) {
_this.txTimeoutCnt++;
Debug.IbctLogErr(COMP, _this.devPath, 'lb1GetState Timeout ', _this.txTimeoutCnt);
if(_this.txTimeoutCnt > 10) {
_this.emit("error", __('获得矿机状态超时'));
_this.txTimeoutCnt = 0;
}
} else {
_this.txTimeoutCnt = 0;
if(_this.status.fanwarn)
_this.emit("error", __('矿机风扇异常'));
if(_this.status.powerwarn)
_this.emit("error", __('矿机电源异常'));
if((_this.status.temp > lb1cfg.offTemp) || (_this.status.tempwarn)) {
_this.emit("error", __('矿机高温关机'));
} else if(_this.status.temp > lb1cfg.warnTemp && _this.status.temp < lb1cfg.offTemp) {
_this.emit("warning", __('矿机高温警报'));
}
}
});
}, 5000);
_this.inited = true;
return 0;
}
async stop(enable, wait) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'lb1', enable ? 'remove...' : 'stop...');
if (!enable) {
await _this.lb1SetHWParamsAndWait(0, 0, 0);
}
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.freq = 0;
_this.voltage = 0;
_this.work.jobID = 0;
_this.jobsDone = 0;
_this.MinerShouldStop = true;
if (_this.intervalObj) {
clearInterval(_this.intervalObj);
_this.intervalObj = null;
}
if (enable) {
if (wait) {
await _this.port.flush(function (err) {
_this.port.close(function(err) {
if(err)
Debug.IbctLogDbg(COMP, err.message);
});
});
} else {
await this.port.flush()
await this.port.close(function(err) {
if(err)
Debug.IbctLogDbg(COMP, err.message);
});
return new Promise(function (resolve) {
waitUntil()
.interval(10)
.times(50)
.condition(function() {
return !_this.port.isOpen;
})
.done(function() {
resolve(0);
})
})
}
}
}
async burnFirmware(firmWare, callback) {
var _this = this;
var firmware;
if(_this.firmware.updating === true) {
callback(__('升级中,请等待'));
return;
}
if(_this.inited === true) {
callback(__('挖矿中,请先暂停挖矿'));
return;
}
var ret = await _this.lb1GetStaticInfo(lb1cfg.model);
if (ret) {
callback(__('无法获取当前版本号'));
return;
} else {
var info = _this.getInfo();
if (info.mc === 'm1') {
firmware = firmWare.slice(0, 114689);
} else if (info.mc === 'm2') {
firmware = firmWare.slice(114689);
} else {
return;
}
}
var init = await _this.lb1BurnFWInit();
if(init === 0) {
_this.firmware.updating = true;
} else {
_this.firmware.updating = false;
callback(__('烧入固件初始化失败'));
return;
}
var fwLen = firmware.length;
var totalPktNum = Math.ceil(firmware.length / _this.firmware.FWPageSize)
var id = 0;
Debug.IbctLogErr(COMP, _this.devPath, 'BurnFW, CurVersion:', _this.info.firmwareVer, 'FW lenth' , fwLen , "PKTnum", totalPktNum);
try {
while(fwLen > 0) {
var currentLen = (fwLen > _this.firmware.FWPageSize) ? _this.firmware.FWPageSize : fwLen;
var curStart = firmware.length - fwLen;
var curEnd = curStart + currentLen;
var pktUpdateFW = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeUpdateFW]),
version: Buffer.from([PV]),
pktLen: Buffer.alloc(4),
pktID:Buffer.alloc(4),
pageSize:Buffer.alloc(4),
curLen:Buffer.alloc(4),
flag:Buffer.alloc(1),
crc32: Buffer.alloc(4),
fwData:Buffer.alloc(currentLen),
ender:Buffer.from(pktEnder)
};
pktUpdateFW.curLen.writeUInt32LE(currentLen, 0);
pktUpdateFW.pktID.writeUInt32LE(id, 0);
pktUpdateFW.pageSize.writeUInt32LE(_this.firmware.FWPageSize, 0);
pktUpdateFW.pktLen.writeUInt32LE(23 + currentLen);
firmware.copy(pktUpdateFW.fwData, 0, curStart, curEnd);
fwLen -= currentLen;
if(fwLen) {
pktUpdateFW.flag[0] = 0x00;
} else {
pktUpdateFW.flag[0] = 0x01;//last
}
var msg = Buffer.alloc(23 + currentLen);
//console.log('3====>', curStart, curEnd, currentLen, fwLen);
pktUpdateFW.type.copy(msg, 0);
pktUpdateFW.version.copy(msg, 1);
pktUpdateFW.pktLen.copy(msg, 2);
pktUpdateFW.pktID.copy(msg, 6);
pktUpdateFW.pageSize.copy(msg, 10);
pktUpdateFW.curLen.copy(msg, 14);
pktUpdateFW.flag.copy(msg, 18);
pktUpdateFW.crc32.copy(msg, 19);
pktUpdateFW.fwData.copy(msg, 23);
var crcValue = parseInt(crc32(msg), 16);
pktUpdateFW.crc32.writeUInt32LE(crcValue, 0);
for(var i = 0; i < _this.firmware.retryCnt; i++) {
var success = await _this.lb1SendFWPktAndWait(i + 1, pktUpdateFW);
if(success === 0)
break;
}
if(fwLen === 0) {
_this.firmware.updating = false;
}
callback(null, ((id + 1) / totalPktNum).toFixed(3));
id++;
}
}
catch(err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Updat firmware failed ' + err.message);
_this.firmware.updating = false;
callback(err.message);
}
}
async rebootDev() {
Debug.IbctLogErr(COMP, this.devPath, 'reboot');
var _this = this;
var pktReboot = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeReboot]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
await _this.stop(true, true)
_this.lb1SendPkt(pktReboot);
}
setPlt() {
var _this = this
console.log('function test')
_this.recvPltPkt = false
var pktPLT = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypePlt]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0, 0x0, 0x0]),
ender: Buffer.from(pktEnder)
}
_this.lb1SendPkt(pktPLT)
}
setLed(Enable) {
Debug.IbctLogErr(COMP, 'Set', this.devPath, 'Led to', Enable ? 'ON' : 'OFF');
var _this = this;
var ledFlag = Enable === true ? 1 : 0;
var pktSetLED = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetLED]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0xb, 0x0 ,0x0 ,0x0]),
Flag:Buffer.from([ledFlag]),
led:Buffer.from([0xE8, 0x3, 0xC8, 0x0]), //ON 1s OFF:200ms
ender:Buffer.from(pktEnder)
};
_this.lb1SendPkt(pktSetLED);
}
async burnSNInfo(ptinfo) {
var _this = this
Debug.IbctLogErr(COMP, _this.devPath, 'Burn Sn Num..', ptinfo)
var pktPTInfo = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeProductTest]),
version: Buffer.from([PV]),
pktlen: Buffer.from([70, 0x0, 0x0, 0x0]),
SNInfo: Buffer.alloc(32),
HashInfo: Buffer.alloc(32),
ender: Buffer.from(pktEnder)
}
if (ptinfo.sn) {
var sn = Buffer.from(ptinfo.sn)
pktPTInfo.SNInfo[0] = sn.length
sn.copy(pktPTInfo.SNInfo, 1)
} else {
pktPTInfo.SNInfo[0] = 0
}
_this.recvPTInfoPkt = false;
_this.lb1SendPkt(pktPTInfo)
return new Promise(function (resolve, reject) {
waitUntil()
.interval(50)
.times(10)
.condition(function () {
return _this.recvPTInfoPkt
})
.done(function (result) {
if (result === false) {
Debug.IbctLogErr(COMP, _this.devPath, 'Burn SN num failed..')
resolve(__('写入SN序列号失败'))
} else {
resolve(null)
}
})
})
}
}
module.exports = function Getlb1(options = {}) {
return new lb1(options);
}

139
src/miner/minerApi.js Normal file
View file

@ -0,0 +1,139 @@
const EventEmitter = require('events');
const cpu = require('./cpu');
const hs1 = require('./hs1');
const hs1plus = require('./hs1plus');
const lb1 = require('./lb1');
const unknow = require('./unknow');
const Debug = require('../log')();
const COMP = '[minerApi]';
var miners = [
{
name: 'cpu',
api: cpu
},
{
name: 'Goldshell-HS1',
api: hs1
},
{
name: 'Goldshell-HS1-Plus',
api: hs1plus
},
{
name: 'Goldshell-LB1',
api: lb1
},
{
name: 'unknow',
api: unknow
}
];
class miner extends EventEmitter {
constructor({
name,
devPath,
algo,
varity,
crypto
}) {
super();
var _this = this;
_this.name = name;
_this.devPath = devPath;
_this.algo = algo;
_this.varity = varity;
_this.crypto = crypto;
_this.minerApi = this.GetMinerApi(_this.name)({
devPath: _this.devPath,
algo: _this.algo,
varity: _this.varity,
crypto: _this.crypto
});
if (!_this.minerApi) {
_this.emit('error', __('不支持此矿机:'), _this.name);
}
_this.minerApi.on("error", function (data) {
return _this.emit("error", data);
})
_this.minerApi.on("warning", function (data) {
return _this.emit("warning", data);
})
return _this;
}
GetMinerApi(name) {
var dev = null;
miners.forEach(function (miner, index) {
if (miner.name === name) {
dev = miner;
}
})
return dev ? dev.api : null;
}
getInfo() {
/* 返回矿机信息,包括矿机名,固件版本。*/
return this.minerApi ? this.minerApi.getInfo() : null;
}
async detect(modelName) {
return this.minerApi ? await this.minerApi.detect(modelName) : null;
}
async init(params) {
/*
初始化硬件
*/
return this.minerApi ? await this.minerApi.init(params) : null;
}
async setDevice() {
/*
设置Miner参数电压, 频率目标温度报警温度
*/
return this.minerApi ? this.minerApi.setDevice() : null;
}
async scanWork(Job, Callback) {
return this.minerApi ? this.minerApi.scanWork(Job, Callback) : null;
}
async stop(enable) {
/*
停止硬件工作并关闭硬件
*/
return this.minerApi ? await this.minerApi.stop(enable) : null;
}
async release() {
this.minerApi.removeAllListeners("error");
this.minerApi.removeAllListeners("warning");
return await this.stop(true)
}
getState() {
/*
获取当前设备状态 温度电压频率功耗等
*/
return this.minerApi ? this.minerApi.getState() : null;
}
async stopScanWork() {
return this.minerApi ? this.minerApi.stopScanWork() : null;
}
setLed(Enable) {
return this.minerApi ? this.minerApi.setLed(Enable) : null;
}
reboot() {
return this.minerApi ? this.minerApi.rebootDev() : null;
}
updateImage(Image, Callback) {
return this.minerApi ? this.minerApi.burnFirmware(Image, Callback) : null;
}
async burnSNInfo(ptinfo) {
return this.minerApi ? await this.minerApi.burnSNInfo(ptinfo) : null;
}
}
module.exports = function Getminer(options = {}) {
return new miner(options);
};

318
src/miner/unknow.js Normal file
View file

@ -0,0 +1,318 @@
const Delimiter = require('@serialport/parser-delimiter');
const Debug = require('../log')();
var SerialPort = require('serialport');
const EventEmitter = require('events');
// var waitUntil = require('wait-until');
var waitUntil = require('../waitUntil');
var crc32 = require('crc32');
const COMP = '[UNKNOW]:';
const hwTarget = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
const PV = 0x10;
const TypeSetBootMode = 0xA3;
const TypeUpdateFW = 0xAA;
const TypeQueryInfo = 0xA4;
const TypeProductTest = 0xAB;
const TypeSetLED = 0xA6;
const TypeSendWork = 0xA1;
const TypeSetHWParams = 0xA2;
const TypeReboot = 0xAC;
const TypeRecvNonce = 0x51;
const TypeRecvJob = 0x55;
const TypeRecvState = 0X52;
const TypeRecvBootMode = 0x53;
const TypeRecvInfo = 0x54;
const TypeRecvFWState = 0x5A;
const TypeRecvTestResult = 0x5B;
const pktHeader = Buffer.from([0xA5, 0x3C, 0x96]);
const pktEnder = Buffer.from([0x69, 0xC3, 0x5A]);
const typeOffset = 0;
class unknow extends EventEmitter {
constructor({devPath, algo, varity, crypto}) {
super();
var _this = this;
_this.devPath = devPath;
_this.algo = algo;
_this.crypto = crypto;
_this.MinerShouldStop = false;
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.work = [{ jobID: 0 }, { jobID: 0 }, { jobID: 0 }, { jobID: 0 }];
_this.Job = [];
_this.fakeJob = { overScan:false };
_this.job_id = null;
_this.info = {
firmwareVer: 'V0.0.1',
modelName: 'unknow',
sn:'unknown',
hashRation:0,
workDepth:4,
};
_this.firmware = {
retryCnt : 2,
FWPageSize : 256,
curState : 0,
curID : 0
}
_this.submitNonce = null;
_this.curJobid = 4;
_this.jobsDone = 0;
_this.status = {
chips:0,
temp:0,
voltage:0,
freq:0,
varity:0x4,
cores:0,
goodcores:0,
scanbits:0,
scantime:0,
tempwarn:0,
fanwarn:0,
powerwarn:0,
rpm:0
};
_this.port = new SerialPort(_this.devPath, {
baudRate: 115200,
dataBits:8,
stopBits:1,
parity:'none',
rtscts:false
}, function (err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, '打开串口失败: ', err.message);
return;
} else
Debug.IbctLogErr(COMP, _this.devPath, '打开串口成功');
});
const parser = _this.port.pipe(new Delimiter({ delimiter: pktEnder}));
parser.on('data', function(data) {
_this.unknowParseNotify(_this, data);
});
_this.on("error", function(err) {
Debug.IbctLogDbg(COMP, err);
})
_this.on("warning", function(err) {
Debug.IbctLogDbg(COMP, err);
})
}
unknowParseNotify(_this, data) {
//Debug.IbctLogInfo(COMP, _this.devPath, 'Recv Pkt', data.toString('hex'));
var location = data.indexOf(pktHeader);
if(location === -1) {
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Invalid PKT', data);
return;
}
var typeLocation = location + pktHeader.length + typeOffset;
switch(data[typeLocation]) {
case TypeRecvNonce:
var jobID = data[9];
//var chipID = data[10];
//var coreID = data[11];
var nonce_l = data.readUInt32LE(12);
var nonce_h = data.readUInt32LE(16);
var nonce = Buffer.alloc(8);
nonce.writeUInt32LE(nonce_l, 0);
nonce.writeUInt32LE(nonce_h, 4);
if(jobID !== _this.work[0].jobID && jobID !== _this.work[1].jobID && jobID !== _this.work[2].jobID && jobID !== _this.work[3].jobID) {
Debug.IbctLogDbg(COMP, _this.devPath, 'Find Stale ', jobID, _this.work[0].jobID, _this.work[1].jobID, nonce.toString('hex'));
} else {
//Debug.IbctLogDbg(COMP, _this.devPath, 'Find Nonce ', nonce.toString('hex'));
let i;
if (jobID === _this.work[0].jobID) i = 0;
else if (jobID === _this.work[1].jobID) i = 1;
else if (jobID === _this.work[2].jobID) i = 2;
else i = 3;
if(_this.submitNonce)
_this.submitNonce(null, nonce, _this.Job[i]);
}
break;
case TypeRecvInfo:
_this.recvInfoPkt = true;
//Debug.IbctLogDbg(COMP, _this.devPath, 'RecvInfoPkt:', data.toString('hex'));
_this.info.modelName = data.toString('utf8', 10, 10 + data[9]);
_this.info.firmwareVer = data.toString('utf8', 43, 43 + data[42]);
_this.info.sn = data.toString('utf8', 52, 52 + data[51]);
_this.info.hashRation = data.readUInt16LE(85);
//_this.info.workDepth = data[117];
Debug.IbctLogDbg(_this.info);
break;
case TypeRecvFWState:
_this.recvFWStatePkt = true;
_this.firmware.curID = data.readUInt32LE(9);
_this.firmware.curState = data[13];
break;
case TypeRecvJob:
_this.recvJobPkt = true;
break;
case TypeRecvBootMode:
_this.recvBootModePkt = true;
break;
case TypeRecvState:
_this.recvStatePkt = true;
_this.recvQueryStatePkt = true;
//Debug.IbctLogInfo(COMP, _this.devPath, 'RecvState pkt:', data.toString('hex'));
_this.status.chips = data[9];
_this.status.cores = data[10];
_this.status.goodcores = data[11];
_this.status.scanbits = data[12];
_this.status.scantime = data.readUInt16LE(13) * 100; //ms
_this.status.voltage = data.readUInt16LE(15); //mV
_this.status.freq = data.readUInt16LE(17); //MHz
_this.status.varity = data.readUInt32LE(19);
_this.status.temp = data[23];
_this.status.hwreboot = data[24];
_this.status.tempwarn = data[25];
_this.status.fanwarn = data[26]
// _this.status.powerwarn = data[27]
_this.status.rpm = data.readUInt16LE(28)
break;
case TypeRecvTestResult:
_this.recvPTInfoPkt = true;
break;
default:
Debug.IbctLogErr(COMP, _this.devPath, 'Recv Unsupported PKT Type', data[location + typeOffset])
break;
}
return;
}
unknowSendPkt (pkt) {
var _this = this;
var offset = 0;
var length = 0;
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
length += pkt[key].length;
}
});
var msg = Buffer.alloc(length);
Object.keys(pkt).forEach(function(key) {
if(Buffer.isBuffer(pkt[key])) {
pkt[key].copy(msg, offset);
offset += pkt[key].length;
}
});
//Debug.IbctLogDbg(COMP, _this.devPath, 'Send pkt', msg.toString('hex'));
_this.port.write(msg, function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on write: ', err.message)
}
_this.port.drain(function(err) {
if (err) {
Debug.IbctLogErr(COMP, _this.devPath, 'Error on Drain: ', _this.devPath, err.message)
}
})
})
}
async stopScanWork() {
var _this = this;
Debug.IbctLogDbg(COMP, _this.devPath, 'stopScanWork ...');
//_this.curJobid = 0;
_this.MinerShouldStop = true;
}
async setDevice(varity, freq, voltage) {
Debug.IbctLogDbg(COMP, 'setDevice');
}
getInfo() {
return this.info;
}
getState() {
return this.status;
}
async detect(modelName) {
Debug.IbctLogErr(COMP, this.devPath, 'unknow detect...');
return true;
}
async init(params) {
Debug.IbctLogErr(COMP, this.devPath, 'unknow init...');
return 0;
}
async stop(enable) {
var _this = this;
Debug.IbctLogErr(COMP, _this.devPath, 'unknow', enable ? 'remove...' : 'stop...');
_this.inited = false;
_this.txTimeoutCnt = 0;
_this.freq = 0;
_this.voltage = 0;
_this.work.jobID = 0;
_this.jobsDone = 0;
_this.MinerShouldStop = true;
if (enable) {
await this.port.flush(function(err) {
_this.port.close(function(err) {
if(err)
Debug.IbctLogDbg(COMP, err.message);
});
})
}
}
async rebootDev() {
Debug.IbctLogErr(COMP, this.devPath, 'reboot');
var _this = this;
var pktReboot = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeReboot]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0x6, 0x0 ,0x0 ,0x0]),
ender:Buffer.from(pktEnder)
};
await _this.stop(true)
_this.unknowSendPkt(pktReboot);
}
setLed(Enable) {
Debug.IbctLogErr(COMP, 'Set', this.devPath, 'Led to', Enable ? 'ON' : 'OFF');
var _this = this;
var ledFlag = Enable === true ? 1 : 0;
var pktSetLED = {
header: Buffer.from(pktHeader),
type: Buffer.from([TypeSetLED]),
version: Buffer.from([PV]),
pktlen: Buffer.from([0xb, 0x0 ,0x0 ,0x0]),
Flag:Buffer.from([ledFlag]),
led:Buffer.from([0xE8, 0x3, 0xC8, 0x0]), //ON 1s OFF:200ms
ender:Buffer.from(pktEnder)
};
_this.unknowSendPkt(pktSetLED);
}
}
module.exports = function Getunknow(options = {}) {
return new unknow(options);
}

74
src/ping.js Normal file
View file

@ -0,0 +1,74 @@
var net = require('net');
var ping = function(o, callback, sorttag) {
var optionsArry = [];
if (Object.prototype.toString.call(o) === '[object Array]') {
optionsArry = o
} else if (Object.prototype.toString.call(o) === '[object Object]') {
optionsArry.push(o);
} else {
console.warn("parameter error");
}
var optionSize = optionsArry.length;
var outArry = [];
optionsArry.forEach(function(item) {
var options = {};
var i = 0;
var results = [];
options.address = item.address || 'localhost';
options.port = item.port || 80;
options.attempts = item.attempts || 10;
options.timeout = item.timeout || 5000;
connect(options);
function check(error, options) {
if (error) {
if (i < options.attempts) {
connect(options);
return;
}
}
callback(error, item.address);
};
function connect(options) {
var s = new net.Socket();
var start = process.hrtime();
s.connect(options.port, options.address, function() {
var time_arr = process.hrtime(start);
var time = (time_arr[0] * 1e9 + time_arr[1]) / 1e6;
results.push({
seq: i,
time: time
});
s.destroy();
i++;
check(null, options);
});
s.on('error', function(e) {
results.push({
seq: i,
time: undefined,
err: e
});
s.destroy();
i++;
check(e, options);
});
s.setTimeout(options.timeout, function() {
results.push({
seq: i,
time: undefined,
err: Error('Request timeout')
});
s.destroy();
i++;
check('Request timeout', options);
});
};
});
};
module.exports.ping = ping;

275
src/protocol/HnsStratum.js Normal file
View file

@ -0,0 +1,275 @@
const EventEmitter = require('events');
const stratum = require('./stratum/lib');
const Debug = require('../log')();
const uuid = require("uuid");
const COMP = '[HnsStratum]';
class intStratum extends EventEmitter {
constructor({}) {
super();
var _this = this;
_this.client = null;
_this.online = false;
_this.auth = false;
_this.subinfo = null;
_this.subinfoturn = false;
_this.diffinfo = null;
_this.socket = null;
_this.sendQueue = [];
_this.user = null;
_this.pass = null;
_this.lastRecvPacket = null;
_this.lastSendPacket = null;
_this.id = uuid.v4();
return _this;
}
stratumAuthorize(id, user, pass) {
return this.socket.stratumSend({
'method': 'mining.authorize',
'id': id,
'params': [user, pass]
}, true);
}
stratumSubmit(id, submitData) {
this.socket.setLastActivity();
return this.socket.stratumSend({
'method': 'mining.submit',
'id': id,
'params': submitData
});
}
setupLoginPacket(data, job, diff, info) {
// for setup for connect.js
var packet = {
id: data.id,
error: null,
result: {
id: this.id,
job: job,
diff: diff,
subscribe: info,
}
};
return packet;
}
getPacketByID(id) {
var packet = null;
this.sendQueue = this.sendQueue.filter(function (pkt) {
if (pkt.id === id && !packet) {
packet = pkt;
return false;
} else {
return true;
}
});
return packet;
}
getPacketByMethod(method) {
var packet = null;
this.sendQueue = this.sendQueue.filter(function (pkt) {
if (pkt.method === method && !packet) {
packet = pkt;
return false;
} else {
return true;
}
});
return packet;
}
init(ssl, host, port) {
var _this = this;
if (_this.online) {
_this.kill();
return;
} else if (!_this.client) {
_this.client = stratum.Client.$create();
if (!_this.client) {
_this.emit('error', __('创建Stratum客户端失败'));
}
}
_this.client.on('error', function (socket, e) {
_this.emit("error", __('网络连接中断'));
});
_this.client.on('timeout', function (socket, e) {
_this.emit("error", __('Socket请求超时'));
});
_this.client.on('mining.error', function (msg, socket) {
Debug.IbctLogErr(COMP, msg);
_this.emit("error", __('Stratum连接中断'));
});
_this.client.connect({
host: host,
port: port
}).then(function (socket) {
Debug.IbctLogDbg(COMP, 'Connected! lets ask for subscribe');
if (!_this.online) {
_this.online = true;
}
_this.socket = socket;
_this.emit('connect');
})
}
kill() {
if (this.client) {
this.client.removeAllListeners();
if (this.client.destroy) {
this.client.destroy();
}
this.client = null;
}
if (this.sendQueue.length)
this.sendQueue.splice(0, this.sendQueue.length);
this.lastRecvPacket = null;
this.lastSendPacket = null;
if (this.online) {
this.online = false;
this.auth = false;
this.removeAllListeners();
}
}
getPacket() {
var _this = this;
var packet = null;
// TODO: have done in init
// the client is a one-way communication, it receives data from the server after issuing commands
_this.client.on('mining', function (data, socket, type) {
// type will be either 'broadcast' or 'result'
Debug.IbctLogInfo(COMP, '<===' + type + ' = ', JSON.stringify(data));
// you can issue more commands to the socket, it's the exact same socket as "client" variable
// in this example
// the socket (client) got some fields like:
// client.name = name of the worker
// client.authorized = if the current connection is authorized or not
// client.id = an UUID ([U]niversal [U]nique [ID]entifier) that you can safely rely on it's uniqueness
// client.subscription = the subscription data from the server
switch (data.method) {
case 'set_difficulty':
case 'mining.set_difficulty':
// server sent the new difficulty
Debug.IbctLogInfo(COMP, 'mining.set_difficulty');
if (!socket.authorized) {
_this.diffinfo = data;
} else {
_this.emit('data', _this.setupLoginPacket(data, null,data.params,null));
}
break;
case 'notify':
case 'mining.notify':
// server sent a new block
// Debug.IbctLogInfo(COMP, 'mining.notify');
if (!_this.auth) {
_this.lastRecvPacket = data.params;
} else {
_this.emit('data', _this.setupLoginPacket(data, data.params,null,null));
}
break;
default:
if (!socket.authorized) {
_this.subinfo = data;
Debug.IbctLogDbg(COMP, 'Asking for authorization');
if (_this.lastSendPacket && _this.lastSendPacket.method === 'login' && !_this.auth) {
Debug.IbctLogErr(COMP, 'Login failed: ', _this.user);
if (data.error && data.error.message)
_this.emit('error', __('矿池登陆失败') + ': ' + data.error.message);
else if (data.error && (data.error instanceof Array) && data.error.length > 2) {
_this.emit('error', __('矿池登陆失败') + ': ' + data.error[1])
} else
_this.emit('error', __('矿池登陆失败'));
return
}
packet = _this.getPacketByMethod('login');
if (packet) {
_this.lastSendPacket = packet;
Debug.IbctLogInfo(COMP, 'Login User: ', _this.user);
_this.stratumAuthorize(packet.id, packet.params.login, packet.params.pass);
}
} else {
if (!_this.lastSendPacket) {
return
}
/*add this step for 6 block */
if(_this.subinfoturn === true) {
_this.subinfo = data;
_this.subinfoturn = false;
}
if (_this.lastSendPacket.method === 'login' && !_this.auth) {
Debug.IbctLogDbg(COMP, 'We are authorized', _this.lastRecvPacket);
_this.emit('data', _this.setupLoginPacket(data, _this.lastRecvPacket, null, null));
_this.auth = true;
if(_this.subinfo.result === null){
_this.subinfoturn = true;
_this.socket.stratumSubscribe('icbtminer');
}
}
if(typeof _this.subinfo.result === 'object' && _this.subinfo.result !== null) {
_this.emit('data', _this.setupLoginPacket(_this.subinfo, null, null, _this.subinfo.result));
_this.subinfo.result = null;
}
if( _this.diffinfo !== null) {
_this.emit('data', _this.setupLoginPacket(_this.diffinfo, null, _this.diffinfo.params, null));
_this.diffinfo = null;
}
if (_this.lastSendPacket.method === 'submit') {
_this.emit('data', data);
}
}
}
});
}
sendPacket(message) {
var _this = this;
Debug.IbctLogInfo(COMP, 'sendPacket: ', JSON.stringify(message));
this.sendQueue.push(message);
if (message.method === 'login') {
this.user = message.params.login;
this.pass = message.params.pass;
// After the first stratumSubscribe, the data will be handled internally
// and returned deferreds to be resolved / rejected through the event 'mining'
// above
this.socket.stratumSubscribe('icbtminer').then(
// This isn't needed, it's handled automatically inside the Client class
// but if you want to deal with anything special after subscribing and such.
function (socket) {
Debug.IbctLogDbg(COMP, 'Sent!');
},
function (error) {
Debug.IbctLogErr(COMP, 'Error');
}
);
}
// have login
if (message.method === 'submit') {
_this.lastSendPacket = message;
return this.stratumSubmit(message.id, message.params.params.submit);
}
}
isWritable() {
return true;
}
};
module.exports = function GetIntStratum(options = {}) {
return new intStratum(options);
};

283
src/protocol/LbcStratum.js Normal file
View file

@ -0,0 +1,283 @@
const EventEmitter = require('events');
const stratum = require('./stratum/lib');
const Debug = require('../log')();
const uuid = require("uuid");
const COMP = '[LbcStratum]';
class intStratum extends EventEmitter {
constructor({}) {
super();
var _this = this;
_this.client = null;
_this.online = false;
_this.auth = false;
_this.subinfo = null;
_this.subinfoturn = false;
_this.diffinfo = null;
_this.socket = null;
_this.sendQueue = [];
_this.user = null;
_this.pass = null;
_this.lastRecvPacket = null;
_this.lastSendPacket = null;
_this.id = uuid.v4();
return _this;
}
stratumAuthorize(id, user, pass) {
return this.socket.stratumSend({
'method': 'mining.authorize',
'id': id,
'params': [user, pass]
}, true);
}
stratumSubmit(id, submitData) {
this.socket.setLastActivity();
return this.socket.stratumSend({
'method': 'mining.submit',
'id': id,
'params': submitData
});
}
setupLoginPacket(data, job, diff, info) {
// for setup for connect.js
var packet = {
id: data.id,
error: null,
result: {
id: this.id,
job: job,
diff: diff,
subscribe: info,
}
};
return packet;
}
getPacketByID(id) {
var packet = null;
this.sendQueue = this.sendQueue.filter(function (pkt) {
if (pkt.id === id && !packet) {
packet = pkt;
return false;
} else {
return true;
}
});
return packet;
}
getPacketByMethod(method) {
var packet = null;
this.sendQueue = this.sendQueue.filter(function (pkt) {
if (pkt.method === method && !packet) {
packet = pkt;
return false;
} else {
return true;
}
});
return packet;
}
init(ssl, host, port) {
var _this = this;
if (_this.online) {
_this.kill();
return;
} else if (!_this.client) {
_this.client = stratum.Client.$create();
if (!_this.client) {
_this.emit('error', __('创建Stratum客户端失败'));
}
}
_this.client.on('error', function (socket, e) {
// xmr.viabtc.com donation pool
if (e.host !== 'xmr.viabtc.com') {
_this.emit("error", __('网络连接中断'));
}
});
_this.client.on('timeout', function (socket, e) {
// xmr.viabtc.com donation pool
if (socket.socket._host !== 'xmr.viabtc.com') {
_this.emit("error", __('Socket请求超时'));
}
});
_this.client.on('mining.error', function (msg, socket) {
Debug.IbctLogErr(COMP, msg);
_this.emit("error", __('Stratum连接中断'));
});
_this.client.connect({
host: host,
port: port
}).then(function (socket) {
Debug.IbctLogDbg(COMP, 'Connected! lets ask for subscribe');
if (!_this.online) {
_this.online = true;
}
_this.socket = socket;
_this.emit('connect');
})
}
kill() {
if (this.client) {
this.client.removeAllListeners();
if (this.client.destroy) {
this.client.destroy();
}
this.client = null;
}
if (this.sendQueue.length)
this.sendQueue.splice(0, this.sendQueue.length);
this.lastRecvPacket = null;
this.lastSendPacket = null;
if (this.online) {
this.online = false;
this.auth = false;
this.removeAllListeners();
}
}
getPacket() {
var _this = this;
var packet = null;
// TODO: have done in init
// the client is a one-way communication, it receives data from the server after issuing commands
_this.client.on('mining', function (data, socket, type) {
// type will be either 'broadcast' or 'result'
Debug.IbctLogInfo(COMP, '<===' + type + ' = ', JSON.stringify(data));
// you can issue more commands to the socket, it's the exact same socket as "client" variable
// in this example
// the socket (client) got some fields like:
// client.name = name of the worker
// client.authorized = if the current connection is authorized or not
// client.id = an UUID ([U]niversal [U]nique [ID]entifier) that you can safely rely on it's uniqueness
// client.subscription = the subscription data from the server
switch (data.method) {
case 'set_difficulty':
case 'mining.set_difficulty':
// server sent the new difficulty
Debug.IbctLogInfo(COMP, 'mining.set_difficulty');
if (!socket.authorized) {
_this.diffinfo = data;
} else {
_this.emit('data', _this.setupLoginPacket(data, null,data.params,null));
}
break;
case 'notify':
case 'mining.notify':
// server sent a new block
// Debug.IbctLogInfo(COMP, 'mining.notify');
if (!_this.auth) {
_this.lastRecvPacket = data.params;
} else {
_this.emit('data', _this.setupLoginPacket(data, data.params,null,null));
}
break;
default:
if (!socket.authorized) {
_this.subinfo = data;
Debug.IbctLogDbg(COMP, 'Asking for authorization');
if (_this.lastSendPacket && _this.lastSendPacket.method === 'login' && !_this.auth) {
Debug.IbctLogErr(COMP, 'Login failed: ', _this.user);
if (data.error && data.error.message)
_this.emit('error', __('矿池登陆失败') + ': ' + data.error.message);
else if (data.error && (data.error instanceof Array) && data.error.length > 2) {
_this.emit('error', __('矿池登陆失败') + ': ' + data.error[1])
} else
_this.emit('error', __('矿池登陆失败'));
return
}
packet = _this.getPacketByMethod('login');
if (packet) {
_this.lastSendPacket = packet;
Debug.IbctLogInfo(COMP, 'Login User: ', _this.user);
_this.stratumAuthorize(packet.id, packet.params.login, packet.params.pass);
}
} else {
if (!_this.lastSendPacket) {
return
}
/*add this step for 6 block */
if(_this.subinfoturn === true) {
_this.subinfo = data;
_this.subinfoturn = false;
}
if (_this.lastSendPacket.method === 'login' && !_this.auth) {
Debug.IbctLogDbg(COMP, 'We are authorized', _this.lastRecvPacket);
_this.emit('data', _this.setupLoginPacket(data, _this.lastRecvPacket, null, null));
_this.auth = true;
if(_this.subinfo.result === null){
_this.subinfoturn = true;
_this.socket.stratumSubscribe('icbtminer');
}
}
if(typeof _this.subinfo.result === 'object' && _this.subinfo.result !== null) {
_this.emit('data', _this.setupLoginPacket(_this.subinfo, null, null, _this.subinfo.result));
_this.subinfo.result = null;
}
if( _this.diffinfo !== null) {
_this.emit('data', _this.setupLoginPacket(_this.diffinfo, null, _this.diffinfo.params, null));
_this.diffinfo = null;
}
if (_this.lastSendPacket.method === 'submit') {
_this.emit('data', data);
}
}
}
});
}
sendPacket(message) {
var _this = this;
//Debug.IbctLogInfo(COMP, 'sendPacket: ', JSON.stringify(message));
this.sendQueue.push(message);
if (message.method === 'login') {
this.user = message.params.login;
this.pass = message.params.pass;
// After the first stratumSubscribe, the data will be handled internally
// and returned deferreds to be resolved / rejected through the event 'mining'
// above
this.socket.stratumSubscribe('icbtminer').then(
// This isn't needed, it's handled automatically inside the Client class
// but if you want to deal with anything special after subscribing and such.
function (socket) {
Debug.IbctLogDbg(COMP, 'Sent!');
},
function (error) {
Debug.IbctLogErr(COMP, 'Error');
}
);
}
// have login
if (message.method === 'submit') {
_this.lastSendPacket = message;
//console.log(COMP, 'sendPacket------------->: ', JSON.stringify(message));
//console.log(COMP, 'sendPacket------------->: ', message);
return this.stratumSubmit(message.id, message.params);
}
}
isWritable() {
return true;
}
};
module.exports = function GetIntStratum(options = {}) {
return new intStratum(options);
};

72
src/protocol/jsonrpc.js Normal file
View file

@ -0,0 +1,72 @@
const EventEmitter = require('events');
const Debug = require('../log')();
const net = require("net");
const tls = require("tls");
const COMP = '[jsonrpc]';
class jsonrpc extends EventEmitter {
constructor({ }) {
super();
var _this = this;
_this.socket = null;
_this.buffer = "";
return _this;
}
init(ssl, host, port) {
var _this = this;
if (ssl) {
this.socket = tls.connect(+port, host, { rejectUnauthorized: false });
} else {
this.socket = net.connect(+port, host);
}
this.socket.on("connect", function () {
_this.emit("connect");
});
this.socket.on("error", function (error) {
_this.emit("error", error);
});
this.socket.on("close", function () {
_this.emit("close");
});
this.socket.setKeepAlive(true);
this.socket.setEncoding("utf8");
}
kill() {
if (this.socket != null) {
try {
this.socket.end();
this.socket.destroy();
}
catch (e) {
Debug.IbctLogErr(COMP, "something went wrong while destroying socket (" + this.host + ":" + this.port + "):", e.message);
}
}
}
getPacket() {
var _this = this;
this.socket.on("data", function (chunk) {
_this.buffer += chunk;
while (_this.buffer.includes("\n")) {
var newLineIndex = _this.buffer.indexOf("\n");
var stratumMessage = _this.buffer.slice(0, newLineIndex);
_this.buffer = _this.buffer.slice(newLineIndex + 1);
_this.emit("data", stratumMessage);
}
});
}
sendPacket(message) {
Debug.IbctLogInfo(COMP, JSON.stringify(message));
return this.socket.write(JSON.stringify(message) + "\n");
}
isWritable() {
return this.socket.writable;
}
}
module.exports = function Getjsonrpc(options = {}) {
return new jsonrpc(options);
};

83
src/protocol/protocol.js Normal file
View file

@ -0,0 +1,83 @@
const EventEmitter = require('events');
const Debug = require('../log')();
const jsonrpc = require('./jsonrpc');
const HnsStratum = require('./HnsStratum');
const LbcStratum = require('./LbcStratum');
const COMP = '[protocol]';
var protocols = [
{
name: 'stratum',
attr: 'hns',
api: HnsStratum
},
{
name: 'stratum',
attr: 'lbc',
api: LbcStratum
}
];
class protocol extends EventEmitter {
constructor({
name,
cryptoname
}) {
super();
var _this = this;
Debug.IbctLogDbg(COMP, name);
_this.name = name;
_this.cryptoname = cryptoname;
_this.protocolApi = _this.GetProtocolApi(_this.name, _this.cryptoname);
if (!_this.protocolApi) {
_this.emit('error', __('不支持此种连接模式:'), _this.name);
}
_this.protocolApi.on('error', function (error) {
_this.emit('error', error);
});
_this.protocolApi.on("connect", function () {
_this.emit("connect");
});
_this.protocolApi.on("close", function () {
_this.emit("close");
});
this.protocolApi.on("data", function (message) {
_this.emit("data", message);
});
return _this;
}
GetProtocolApi(name, cryptoname) {
var dev = null;
protocols.forEach(function (protocol, index) {
if (protocol.name === name && protocol.attr === cryptoname) {
dev = protocol;
}
})
return dev ? dev.api() : null;
}
init(ssl, host, port) {
return this.protocolApi ? this.protocolApi.init(ssl, host, port) : null;
}
kill() {
return this.protocolApi ? this.protocolApi.kill() : null;
}
getPacket() {
return this.protocolApi ? this.protocolApi.getPacket() : null;
}
sendPacket(message) {
return this.protocolApi ? this.protocolApi.sendPacket(message) : null;
}
isWritable() {
return this.protocolApi ? this.protocolApi.isWritable() : null;
}
}
module.exports = function GetProtocol(options = {}) {
return new protocol(options);
};

View file

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Stratum protocol server using Express
Copyright (C) 2013 Paulo Cesar
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View file

@ -0,0 +1,82 @@
var stratum = require('../lib');
var client = stratum.Client.$create();
//must be specified per EventEmitter requirements
client.on('error', function(socket){
socket.destroy();
console.log('Connection closed');
process.exit(1);
});
// this usually happens when we are not authorized to send commands (the server didn't allow us)
// or share was rejected
// Stratum errors are usually an array with 3 items [int, string, null]
client.on('mining.error', function(msg, socket){
console.log(msg);
});
var submitted = false;
// the client is a one-way communication, it receives data from the server after issuing commands
client.on('mining', function(data, socket, type){
// type will be either 'broadcast' or 'result'
console.log('Mining data: ' + type + ' = ', data);
// you can issue more commands to the socket, it's the exact same socket as "client" variable
// in this example
// the socket (client) got some fields like:
// client.name = name of the worker
// client.authorized = if the current connection is authorized or not
// client.id = an UUID ([U]niversal [U]nique [ID]entifier) that you can safely rely on it's uniqueness
// client.subscription = the subscription data from the server
switch (data.method) {
case 'set_difficulty':
// server sent the new difficulty
break;
case 'notify':
// server sent a new block
break;
default:
if (!socket.authorized){
console.log('Asking for authorization');
socket.stratumAuthorize('user','pass');
} else {
console.log('We are authorized');
if (!submitted) {
console.log('Lets submit fake data once for test purposes, then close');
socket.stratumSubmit('', '' ,'' ,' ', '').done(function(){
client.destroy(); // after call to destroy, "client" doesnt exist anymore
});
submitted = true;
}
}
}
});
client.connect({
host: 'localhost',
port: 3333
}).then(function (socket){
// defered, this can be chained if needed, no callback hell
// "socket" refers to the current client, in this case, the 'client'
// variable
console.log('Connected! lets ask for subscribe');
// After the first stratumSubscribe, the data will be handled internally
// and returned deferreds to be resolved / rejected through the event 'mining'
// above
socket.stratumSubscribe('Node.js Stratum').then(
// This isn't needed, it's handled automatically inside the Client class
// but if you want to deal with anything special after subscribing and such.
function(socket){
console.log('Sent!');
},
function(error){
console.log('Error');
}
);
})
// this can be chained and is the last callback to execute
//.done(function(){ });
;

View file

@ -0,0 +1,11 @@
'use strict';
var
debug = require('debug')('stratum');
module.exports = require('es5class').$define('Base', {}, {
debug: function(msg){
debug(this.$className + ': ', msg);
return msg;
}
}).$implement(require('eventemitter3'), true);

View file

@ -0,0 +1,335 @@
module.exports = function (classes) {
'use strict';
var
q = classes.q,
_ = classes.lodash,
uuid = classes.uuid;
var Debug = classes.debug;
/**
* @param {Socket} socket
* @param {Boolean} isServer
* @constructor
*/
var Client = classes.Base.$define('Client', {
construct: function (socket, isServer) {
var self = this;
self.$super();
// although BFGMiner starts at id 0, we start at 1, because it makes sense
self.currentId = 0;
// our TCP_NODELAY and KeepAlive'd socket
self.socket = self.$class.createSocket(socket);
self.authorized = false;
self.byServer = isServer === undefined ? false : isServer;
self.subscription = '';
self.name = '';
self.pending = {};
// Uniquely identify this socket, regardless of clusters or inter-processes,
// it's unique.
self.id = uuid.v4();
// Last activity is now!
self.setLastActivity();
self.socket.on('end', function clientSocketEnd() {
if (self.emit) {
self.emit('end', self);
}
});
self.socket.on('error', function clientSocketError(e) {
if (self.emit) {
self.emit('error', self, e);
}
});
// set 60s timeout
self.socket.setTimeout(120000, function() {
if (self.emit) {
self.emit('timeout', self, 'Socket请求超时');
}
});
self.socket.on('drain', function clientSocketDrain() {
if (self.emit) {
self.emit('drain', self);
}
});
if (isServer !== true) {
self.socket.on('data', function (data) {
self.handleData(self.socket, data);
// classes.curry.predefine(self.handleData, [self], self)
});
}
},
/**
* Keep track of idle sockets, update the last activity
*
* @param {Number} [time] Unix Timestamp
*
* @return {this}
*/
setLastActivity: function (time) {
this.lastActivity = _.isNumber(time) ? time : Date.now();
return this;
},
/**
* Either emit an event, or fulfill a pending request by id
*/
fullfill: function (command) {
var self = this, method;
//Debug.IbctLogDbg('command', JSON.stringify(command));
//Debug.IbctLogDbg(JSON.stringify(self.pending));
if (_.has(command, 'method') && command.method === 'mining.notify') {
// set command id = 0
command['id'] = 0;
self.emit('mining', command, self, 'notify');
} else if (_.has(command, 'id') && (_.isNull(command['id']))) {
// null id, it's a broadcast most likely, we need to check the last command
if (_.has(command, 'method')) {
method = command.method.split('mining.');
if (classes.Server.commands[method[1]].broadcast === true || method[1] === 'error') {
command['method'] = method[1];
self.emit('mining', command, self, 'broadcast');
} else {
self.emit('mining', command, self, 'broadcast');
//throw new Error('Server sent unknown command: ' + JSON.stringify(command));
}
} else {
throw new Error('Broadcast without a method: ' + JSON.stringify(command));
}
} else if (_.has(command, 'id') && _.has(self.pending, command['id'])) {
// need to resolve pending requests by id
self.$class.debug('Received pending request response: ' + command + ' ' + self.pending);
var type = self.pending[command['id']];
switch (type) {
case 'mining.subscribe':
self.subscription = command['result'];
break;
case 'mining.authorize':
self.authorized = !!command['result'] || (command['error'] === null || command['error'] === undefined);
break;
}
self.emit('mining', command, self, 'result', self.pending[command['id']]);
if (type === self.pending[command['id']]) {
delete self.pending[command['id']];
}
} else if (_.has(command, 'id') && _.has(command, 'result')) {
// regular result that wasnt issued by this socket
self.emit('mining', command, self, 'result');
} else if (_.has(command, 'error') && (!_.isNull(command['error']) && !_.isEmpty(command['error']))) {
// we have an error, we need to act on that, regardless of other members in the command received
throw new Error(command);
} else if (_.has(command, 'method') && command.method === 'mining.set_difficulty') {
self.emit('mining', command, self, 'broadcast');
} else {
throw new Error('No suitable command was issued from the server');
}
},
/**
* Get the current socket IP address
*
* @returns {{port: Number, address: String, family: String}}
*/
address: function () {
return this.socket.address();
},
/**
* This method is exposed just for testing purposes
*
* @param {Socket} socket
* @param {Buffer} buffer
* @private
*/
handleData: function (socket, buffer) {
var
c = classes.Server.getStratumCommands(buffer),
cmds = c.cmds;
classes.Server.processCommands.call(this, this, cmds);
},
/**
* Destroy the socket and unattach any listeners
*/
destroy: function () {
this.removeAllListeners();
this.socket.destroy();
this.$destroy();
},
/**
* Connect to somewhere
*
* @param {Object} opts Where to connect
* @returns {Q.promise}
*/
connect: function (opts) {
var d = q.defer(), self = this;
this.socket.connect(opts, function clientSocketConnect() {
d.resolve(self);
});
return d.promise;
},
/**
* Don't use this functions directly, they are called from the server side,
* it's not a client side command, but an answer
*
* @return {Q.promise}
* @private
*/
set_difficulty: function (args) {
return classes.Server.commands.set_difficulty.apply(this, [null].concat(args));
},
/**
* Don't use this functions directly, they are called from the server side
* it's not a client side command, but an answer
*
* @return {Q.promise}
* @private
*/
notify: function (args) {
return classes.Server.commands.notify.apply(this, [null].concat(args));
},
/**
* Send HTTP header
*
* @param {String} hostname
* @param {Number} port
*
* @return {Q.promise}
*/
stratumHttpHeader: function (hostname, port) {
var
result = '{"error": null, "result": false, "id": 0}',
d = q.defer(),
header = [
'HTTP/1.1 200 OK',
'X-Stratum: stratum+tcp://' + hostname + ':' + port,
'Connection: Close',
'Content-Length: ' + (result.length + 1),
'',
'',
result
];
this.$class.debug('Sending Stratum HTTP header');
this.socket.write(header.join('\n'), classes.curry.wrap(d.resolve, d));
return d.promise;
},
/**
* Subscribe to the pool
*
* @param {String} [UA] Send the User-Agent
* @returns {Q.promise}
*/
stratumSubscribe: function (UA) {
this.name = UA;
return this.stratumSend({
'method': 'mining.subscribe',
'id': this.currentId++,
'params': typeof UA !== 'undefined' ? [UA] : []
}, true);
},
/**
* Asks for authorization
*
* @param {String} user
* @param {String} pass
* @returns {Q.promise}
*/
stratumAuthorize: function (user, pass) {
return this.stratumSend({
'method': 'mining.authorize',
'id': this.currentId++,
'params': [user, pass]
}, true);
},
/**
* Sends a share
*
* @param {String} worker
* @param {String} job_id
* @param {String} extranonce2
* @param {String} ntime
* @param {String} nonce
* @returns {Q.promise}
*/
stratumSubmit: function (worker, job_id, extranonce2, ntime, nonce) {
this.setLastActivity();
return this.stratumSend({
'method': 'mining.submit',
'id': this.currentId++,
'params': [worker, job_id, extranonce2, ntime, nonce]
});
},
/**
* Send Stratum command
*
* @param {Object} data
* @param {Boolean} bypass Bypass unauthorized
* @param {String} name Call from the server
*
* @returns {Q.promise}
*/
stratumSend: function (data, bypass, name) {
if (this.authorized === true || bypass === true) {
this.pending[data.id || this.currentId++] = name || data.method;
return this.send(JSON.stringify(data) + '\n');
} else {
var error = this.$class.debug(classes.Server.errors.UNAUTHORIZED_WORKER);
this.emit('mining.error', error);
return classes.Server.rejected(error);
}
},
/**
* Send raw data to the server
*
* @param {*} data
* @returns {Q.promise}
*/
send: function (data) {
this.$class.debug('(' + this.id + ') Sent command ' + data);
//Debug.IbctLogDbg('(' + this.id + ') Sent command ' + data);
var d = q.defer(), self = this;
try {
self.socket.write(data, function clientSocketWrite(err) {
if (err) {
d.reject(err);
} else {
d.resolve(self);
}
});
} catch (err) {
d.reject(err);
}
return d.promise;
}
}, {
createSocket: function (socket) {
if (!socket) {
socket = new classes.net.Socket();
}
socket.setNoDelay(true);
socket.setKeepAlive(true, 120);
return socket;
}
});
return Client;
};

View file

@ -0,0 +1,24 @@
'use strict';
var
classes = {
path : require('path'),
dir : function (path){
return this.path.join(__dirname, path);
},
fs : require('fs'),
lodash : require('lodash'),
net : require('net'),
uuid : require('uuid'),
q : require('bluebird'),
rpc : require('json-rpc2'),
curry : require('better-curry'),
debug : require('../../../log')()
};
classes.Base = require('./base');
classes.RPCServer = require('./rpc')(classes);
classes.Client = require('./client')(classes);
classes.Server = require('./server')(classes);
module.exports = classes;

View file

@ -0,0 +1,175 @@
module.exports = function(classes){
'use strict';
var
_ = classes.lodash,
rpc = require('json-rpc2'),
crypto = require('crypto');
var RPC = classes.Base.$define('RPC', {
construct: function(opts){
this.$super();
opts = opts || {};
opts = _.defaults(opts, {
'mode': 'tcp',
'port': false,
'host': 'localhost',
'password': false
});
if (!opts.port) {
throw new Error('Port must be set');
}
if (!opts.password) {
throw new Error('Password must be set');
}
this.opts = opts;
this.server = new rpc.Server({type: this.opts.mode});
this._server = null;
},
/**
* Generate a SHA 256 hex from a base64 string
*
* @param {String} base64 The base64 string of the password
* @returns {Boolean|String} False if invalid Base64, the SHA256 hex otherwise
* @private
*/
_password: function(base64){
var
ascii = new Buffer(base64, 'base64').toString('ascii'),
check = new Buffer(ascii).toString('base64');
if (base64 === check) {
return crypto
.createHash('sha256')
.update(
ascii,
'ascii'
).digest('hex');
}
return false;
},
/**
* Returns a bound function to the current exposed name, functions and context
*
* @param {String} name
* @param {Function} func
* @param {Object} [context]
*
* @returns {Function}
* @private
*/
_authenticate: function(name, func, context){
var self = this;
return function rpcAuthenticate(args, connection, callback){
var password = false;
self.$class.debug('Received request for "' + name + '" with args "' + args + '"');
if (typeof args[0] === 'string') {
password = self._password(args[0]);
} else {
callback(self.$class.debug('No password provided'));
}
if (password && password === self.opts.password) {
func.apply(context || self, [args.slice(1), connection, callback]);
} else {
callback(self.$class.debug('Unauthorized access'));
}
};
},
/**
* Expose a function, but first check if the password
* is valid (as the first parameter from the RPC call).
*
* This isn't standard, but forces each call to be signed,
* so there's no need to keep track of sessions, connections,
* etc.
*
* So make sure that each request it's made, the first param
* is the base64 password
*
* {
* "method":"any_method",
* "params":["base64ed_password", "param1", "param2"],
* "id": 1
* }
*
* @param {String} method Name of the RPC method
* @param {Function} func Function that will be called with the args, connection and the callback.
* The callback must be called, so that the remote RPC endpoint receives an answer
* <code>
* RPC.expose('name', function(args, connection, callback){
* if (args.length === 0){
* callback(error);
* } else {
* callback(null, result);
* }
* });
* </code>
* @param {Object} [context] The "this" of the previous parameter, func
* @returns {RPC}
*/
expose: function(method, func, context){
var self = this;
self.$class.debug('Exposing "' + method + '"');
self.server.expose(method, self._authenticate(method, func, context));
return self;
},
/**
* Listen the RPC on the defined port
*
* @returns {RPC}
*/
listen: function(){
if (_.isNull(this._server)) {
this.$class.debug('Listening on port ' + this.opts.port);
var func;
switch (this.opts.mode) {
case 'both':
func = this.server.listenHybrid;
break;
case 'http':
func = this.server.listen;
break;
case 'tcp':
func = this.server.listenRaw;
break;
}
this._server = func.call(this.server, this.opts.port, this.opts.host);
} else {
throw new Error(this.$class.debug('Server already listening on port ' + this.opts.port));
}
return this;
},
/**
* Close the RPC server
*
* @returns {RPC}
*/
close: function(){
if (!_.isNull(this._server) && this._server.close) {
this._server.close();
this._server = null;
}
return this;
}
});
return RPC;
};

View file

@ -0,0 +1,630 @@
module.exports = function (classes){
'use strict';
var
_ = classes.lodash,
q = classes.q;
var Server = classes.Base.$define('Server', {
construct : function (opts){
var self = this;
this.$super();
self.clients = {};
opts = opts || {};
self.opts = _.defaults(_.clone(opts), Server.defaults);
if (opts.rpc){
self.rpc = new classes.RPCServer(self.opts.rpc);
self.expose('mining.connections');
self.expose('mining.block');
self.expose('mining.wallet');
self.expose('mining.alert');
}
// classes.toobusy.maxLag(self.opts.settings.toobusy);
self.server = classes.net.createServer();
Server.debug('Created server');
self.server.on('connection', function serverConnection(socket){
self.newConnection(socket);
});
},
expose : function (name){
if (this.rpc) {
this.rpc.expose(name, Server.expose(this, name), this);
} else {
throw new Error('RPC is not enabled in the server');
}
},
/**
* Emits 'close' event when a connection was closed
*
* @param {Client} socket
*/
closeConnection: function (socket){
var id = socket.id;
socket.destroy();
if (typeof this.clients[id] !== 'undefined') {
delete this.clients[id];
}
this.emit('close', id);
Server.debug('(' + id + ') Closed connection ' + _.size(this.clients) + ' connections');
},
/**
* Emits 'busy' event when the server is on high load
* Emits 'connection' event when there's a new connection, passes the newly created socket as the first argument
*
* @param {Socket} socket
*/
newConnection : function (socket){
var
self = this,
closeSocket,
handleData;
// if (classes.toobusy()) {
if (false) {
socket.destroy();
Server.debug('Server is busy, ' + _.size(this.clients) + ' connections');
self.emit('busy');
} else {
socket = new classes.Client(socket, true);
closeSocket = (function (socket){
return function closeSocket(){
self.closeConnection(socket);
};
})(socket);
handleData = (function (socket){
return function handleData(buffer){
self.handleData(socket, buffer);
};
})(socket);
classes.Server.debug('(' + socket.id + ') New connection');
this.clients[socket.id] = socket;
socket.on('end', closeSocket);
socket.on('error', closeSocket);
socket.socket.on('data', handleData);
this.emit('connection', socket);
}
},
/**
*
* @param {Client} socket
* @param {Buffer} buffer
*/
handleData : function (socket, buffer){
var
self = this,
c = Server.getStratumCommands(buffer),
string = c.string,
cmds = c.cmds;
if (/ HTTP\/1\.1\n/i.test(string)) {
// getwork trying to access the stratum server, send HTTP header
socket.stratumHttpHeader(
this.opts.settings.hostname,
this.opts.settings.port
).done(function handleDataHttp(){
self.closeConnection(socket);
});
} else if (cmds.length) {
// We got data
self.$class.processCommands.call(self, socket, cmds);
}
},
/**
* Start the Stratum server, the RPC and any coind that are enabled
*
* @return {Q.promise}
*/
listen : function (){
var self = this, d = q.defer();
this.server.listen(this.opts.settings.port, this.opts.settings.host, function serverListen(){
d.resolve(Server.debug('Listening on port ' + self.opts.settings.port));
});
/*istanbul ignore else */
if (this.rpc) {
this.rpc.listen();
}
return d.promise;
},
close : function (){
Server.debug('Shutting down servers...');
this.server.close();
/*istanbul ignore else */
if (this.rpc) {
this.rpc.close();
}
},
/**
* Sends a Stratum result command directly to one socket
*
* @param {String} id UUID of the socket
* @param {String} type The type of command, as defined in server.commands
* @param {Array} array Parameters to send
*
* @return {Q.promise}
*/
sendToId : function (id, type, array){
var d = q.defer();
if (type && _.isFunction(this.$class.commands[type])) {
if (id && _.has(this.clients, id)) {
this.$class.commands[type].apply(this.clients[id], [id].concat(array)).done(
classes.curry.wrap(d.resolve, d),
classes.curry.wrap(d.reject, d)
);
} else {
d.reject(this.$class.debug('sendToId socket id not found "' + id + '"'));
}
} else {
d.reject(this.$class.debug('sendToId command doesnt exist "' + type + '"'));
}
return d.promise;
},
/**
* Send a mining method or result to all connected
* sockets
*
* Returns a promise, so when it's done sending, you can
* do:
*
* server.broadcast('notify', [...params]).then(function(){
* console.log('done');
* });
*
* @param {String} type
* @param {Array} data
* @returns {Q.promise}
*/
broadcast : function (type, data){
var self = this, d = q.defer(), total = 0;
if (typeof type === 'string' && _.isArray(data)) {
if (typeof self.$class.commands[type] === 'function' && self.$class.commands[type].broadcast === true) {
if (_.size(self.clients)) {
self.$class.debug('Brodcasting ' + type + ' with ', data);
q.try(function serverBroadcast(){
_.forEach(self.clients, function serverBroadcastEach(socket){
self.$class.commands[type].apply(socket, [null].concat(data));
total++;
});
return total;
}).done(
classes.curry.wrap(d.resolve, d),
classes.curry.wrap(d.reject, d)
);
} else {
d.reject(self.$class.debug('No clients connected'));
}
} else {
d.reject(self.$class.debug('Invalid broadcast type "' + type + '"'));
}
} else {
d.reject(self.$class.debug('Missing type and data array parameters'));
}
return d.promise;
}
}, {
/**
* Parse the incoming data for commands
*
* @param {Buffer} buffer
* @returns {{string: string, cmds: Array}}
*/
getStratumCommands: function (buffer){
var
string,
cmds = [];
if (Buffer.isBuffer(buffer) && buffer.length) {
string = buffer.toString().replace(/[\r\x00]/g, '');
cmds = _.filter(string.split('\n'), function serverCommandsFilter(item){ return !_.isEmpty(item) && !_.isNull(item); });
}
// Separate cleaned up raw string and commands array
return {string: string, cmds: cmds};
},
/**
* Process the Stratum commands and act on them
* Emits 'mining' event
*
* @param {Client} socket
* @param {Array} cmds
*/
processCommands : function (socket, cmds){
var
command,
method,
self = this,
onClient = self.$instanceOf(classes.Client),
onServer = !onClient && self.$instanceOf(classes.Server);
self.$class.debug('(' + socket.id + ') Received command ' + cmds);
_.forEach(cmds, function serverForEachCommand(cmd){
try {
command = JSON.parse(cmd);
// Is it a method Stratum call?
if (
// Deal with method calls only when on Server
onServer &&
typeof command['method'] !== 'undefined' &&
command.method.indexOf('mining.') !== -1
) {
method = command.method.split('mining.');
command['method'] = method[1];
if (method.length === 2 && typeof self.$class.commands[method[1]] === 'function') {
// We don't want client sockets messing around with broadcast functions!
if (self.$class.commands[method[1]].broadcast !== true && method[1] !== 'error') {
// only set lastActivity for real mining activity
socket.setLastActivity();
var
d = q.defer(),
accept, reject;
// Resolved, call the method and send data to socket
accept = self.$class.bindCommand(socket, method[1], command.id);
// Rejected, send error to socket
reject = self.$class.bindCommand(socket, 'error', command.id);
d.promise.spread(accept, reject);
self.emit('mining', command, d, socket);
} else {
throw new Error('(' + socket.id + ') Client trying to reach a broadcast function "' + method[1] + '"');
}
} else {
self.$class.commands.error.call(socket, command.id, self.$class.errors.METHOD_NOT_FOUND);
throw new Error('Method not found "' + command.method + '"');
}
} else if ((onClient || socket.byServer === true) && (_.has(command, 'result') || _.has(command, 'method'))) {
// Result commands ONLY when 'self' is an instance of Client
// Since (unfortunately) stratum expects every command to be given in order
// we need to keep track on what we asked to the server, so we can
// act accordingly. This call is either a result from a previous command or a method call (broadcast)
socket.fullfill(command);
} else {
throw new Error('Stratum request without method or result field');
}
} catch (e) {
if (self.emit)
self.emit('mining.error', self.$class.debug(e), socket);
}
});
},
/**
* Wraps the callback and predefine the ID of the current stratum call
*
* @param {Client} socket
* @param {String} type
* @param {String} id
*
* @returns {Function} curryed function
*/
bindCommand : function (socket, type, id){
return classes.curry.predefine(this.commands[type], [id], socket);
},
rejected : function (msg){
return q.reject(this.$class.debug(msg));
},
expose : function (base, name){
return function serverExposedFunction(args, connection, callback){
var d = q.defer();
classes.RPCServer.debug('Method "' + name + '": ' + args);
d.promise.then(function serverExposedResolve(res){
res = [].concat(res);
classes.RPCServer.debug('Resolve "' + name + '": ' + res);
callback.call(base, null, [res[0]]);
}, function serverExposedReject(err){
classes.RPCServer.debug('Reject "' + name + '": ' + err);
callback.call(base, ([].concat(err))[0]);
});
base.emit('rpc', name, args, connection, d);
};
},
invalidArgs: function(id, name, expected, args) {
var count = _.filter(args, function(i){ return typeof i !== 'undefined'; }).length - 1;
if ((id === null || id === undefined) || count !== expected) {
return classes.Server.rejected(
(id === null || id === undefined) ?
'No ID provided' :
'Wrong number of arguments in "' + name + '", expected ' + expected + ' but got ' + count
);
}
return true;
},
commands : {
/**
* Return subscription parameters to the new client
*
* @param id
* @param {String} difficulty
* @param {String} subscription
* @param {String} extranonce1
* @param {Number} extranonce2_size
*
* @returns {Q.promise}
*/
subscribe : function (id, difficulty, subscription, extranonce1, extranonce2_size){
var ret;
if ((ret = classes.Server.invalidArgs(id, 'subscribe', 4, arguments)) !== true){
return ret;
}
this.subscription = subscription;
return this.stratumSend({
id : id,
result: [
[
['mining.set_difficulty', difficulty],
['mining.notify', subscription]
],
extranonce1,
extranonce2_size
],
error : null
}, true, 'subscribe');
},
/**
* Send if submitted share is valid
*
* @param {Number} id ID of the call
* @param {Boolean} accepted
* @returns {Q.promise}
*/
submit : function (id, accepted){
var ret;
if ((ret = classes.Server.invalidArgs(id, 'submit', 1, arguments)) !== true){
return ret;
}
return this.stratumSend({
id : id,
result: !!accepted,
error : null
}, false, 'submit');
},
/**
* Send an error
*
* @param {Number} id
* @param {Array|String} error
* @returns {Q.promise}
*/
error : function (id, error){
var ret;
if ((ret = classes.Server.invalidArgs(id, 'error', 1, arguments)) !== true){
return ret;
}
this.$class.debug('Stratum error: ' + error);
return this.stratumSend({
id : id,
error : error,
result: null
}, true, 'error');
},
/**
* Authorize the client (or not). Must be subscribed
*
* @param {Number} id
* @param {Boolean} authorized
*
* @returns {Q.promise}
*/
authorize : function (id, authorized){
var ret;
if ((ret = classes.Server.invalidArgs(id, 'authorize', 1, arguments)) !== true){
return ret;
}
if (!this.subscription) {
return Server.commands.error.call(this, id, Server.errors.NOT_SUBSCRIBED);
}
this.authorized = !!authorized;
return this.stratumSend({
id : id,
result: this.authorized,
error : null
}, true, 'authorize');
},
/**
* Miner is asking for pool transparency
*
* @param {String} id txlist_jobid
* @param {*} merkles
*/
get_transactions: function (id, merkles){
var ret;
if ((ret = classes.Server.invalidArgs(id, 'get_transactions', 1, arguments)) !== true){
return ret;
}
return this.stratumSend({
id : id,
result: [].concat(merkles),
error : null
}, false, 'get_transactions');
},
/**
* Notify of a new job
*
* @param {Number} id
* @param {*} job_id
* @param {String} previous_hash
* @param {String} coinbase1
* @param {String} coinbase2
* @param {Array} branches
* @param {String} block_version
* @param {String} nbit
* @param {String} ntime
* @param {Boolean} clean
*
* @returns {Q.promise}
*/
notify : function (id, job_id, previous_hash, coinbase1, coinbase2, branches, block_version, nbit, ntime, clean){
var ret;
if ((ret = classes.Server.invalidArgs(false, 'notify', 9, arguments)) !== true){
return ret;
}
return this.stratumSend({
id : null,
method: 'mining.notify',
params: [job_id, previous_hash, coinbase1, coinbase2, branches, block_version, nbit, ntime, clean]
}, true, 'notify');
},
/**
* Set the difficulty
*
* @param {Number} id
* @param {Number} value
* @returns {Q.promise}
*/
set_difficulty : function (id, value){
var ret;
if ((ret = classes.Server.invalidArgs(false, 'set_difficulty', 1, arguments)) !== true){
return ret;
}
return this.stratumSend({
id : null,
method: 'mining.set_difficulty',
params: [value]
}, true, 'set_difficulty');
}
},
errors : {
'FEE_REQUIRED' : [-10, 'Fee required', null],
'SERVICE_NOT_FOUND' : [-2, 'Service not found', null],
'METHOD_NOT_FOUND' : [-3, 'Method not found', null],
'UNKNOWN' : [-20, 'Unknown error', null],
'STALE_WORK' : [-21, 'Stale work', null],
'DUPLICATE_SHARE' : [-22, 'Duplicate share', null],
'HIGH_HASH' : [-23, 'Low difficulty share', null],
'UNAUTHORIZED_WORKER': [-24, 'Unauthorized worker', null],
'NOT_SUBSCRIBED' : [-25, 'Not subscribed', null]
},
defaults : {
/**
* RPC to listen interface for this server
*/
rpc : {
/**
* Bind to address
*
* @type {String}
*/
host: 'localhost',
/**
* RPC port
*
* @type {Number}
*/
port: 1337,
/**
* RPC password, this needs to be a SHA256 hash, defaults to 'password'
* To create a hash out of your password, launch node.js and write
*
* require('crypto').createHash('sha256').update('password').digest('hex');
*
* @type {String}
*/
password: '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8',
/**
* Mode to listen. By default listen only on TCP, but you may use 'http' or 'both' (deal
* with HTTP and TCP at same time)
*/
mode: 'tcp'
},
/**
* The server settings itself
*/
settings: {
/**
* Address to set the X-Stratum header if someone connects using HTTP
* @type {String}
*/
hostname: 'localhost',
/**
* Max server lag before considering the server "too busy" and drop new connections
* @type {Number}
*/
toobusy : 70,
/**
* Bind to address, use 0.0.0.0 for external access
* @type {string}
*/
host : 'localhost',
/**
* Port for the stratum TCP server to listen on
* @type {Number}
*/
port : 3333
}
}
});
Server.commands.notify.broadcast = true;
Server.commands.set_difficulty.broadcast = true;
Server.commands.error.serverOnly = true;
return Server;
};

View file

@ -0,0 +1,61 @@
{
"bin": {
"stratum-notify": "./bin/stratum-notify"
},
"dependencies": {
"better-curry": "*",
"bluebird": "2.x",
"chalk": "*",
"commander": "*",
"debug": "*",
"es5class": "1.x.x",
"eventemitter3": "1.x",
"json-rpc2": "1.x",
"lodash": "3.x",
"toobusy-js": "*",
"uuid": "*"
},
"deprecated": false,
"description": "Stratum protocol server and client for node.js",
"devDependencies": {
"coveralls": "*",
"expect.js": "*",
"istanbul": "*",
"jshint": "*",
"mocha": "*",
"mocha-lcov-reporter": "*",
"sinon": "*"
},
"directories": {
"lib": "./lib",
"bin": "./bin"
},
"engines": {
"node": "0.10.x"
},
"homepage": "https://github.com/pocesar/node-stratum#readme",
"license": "GPLv2",
"main": "./lib",
"name": "stratum",
"repository": {
"type": "git",
"url": "git://github.com/pocesar/node-stratum.git"
},
"version": "0.2.4",
"warnings": [
{
"code": "ENOTSUP",
"required": {
"node": "0.10.x"
},
"pkgid": "stratum@0.2.4"
},
{
"code": "ENOTSUP",
"required": {
"node": "0.10.x"
},
"pkgid": "stratum@0.2.4"
}
]
}

4
src/proxy/app.json Normal file
View file

@ -0,0 +1,4 @@
{
"name": "CHS",
"repository": "https://github.com/cazala/coin-hive-stratum"
}

View file

@ -0,0 +1,62 @@
#!/usr/bin/env node
"use strict";
const Proxy = require("../build");
const fs = require("fs");
const argv = require("minimist")(process.argv.slice(2));
const defaults = require("../config/defaults");
function help() {
const text = fs.createReadStream(`${__dirname}/help`);
text.pipe(process.stderr);
text.on("close", () => process.exit(1));
}
if (argv.help || argv.h) {
help();
return;
}
const port = argv._[0];
const key = argv.key;
const cert = argv.cert;
const isHTTPS = !!(key && cert);
const options = {
host: argv.host || defaults.host,
pass: argv.pass || defaults.pass,
port: argv.port || defaults.port,
ssl: argv.hasOwnProperty("ssl") || defaults.ssl,
address: argv.address || defaults.address,
user: argv.user || defaults.user,
diff: argv.diff || defaults.diff,
dynamicPool: argv.hasOwnProperty("dynamic-pool") || defaults.dynamicPool,
maxMinersPerConnection: argv["max-miners-per-connection"] || defaults.maxMinersPerConnection,
path: argv.path || defaults.path
};
if (argv["credentials"]) {
try {
const split = argv["credentials"].split(":");
options.credentials = {
user: split[0],
pass: split[1]
};
} catch (e) {
console.warn(`invalid credentials: "${argv["credentials"]}", the should be like "user:pass"`);
}
}
if (isHTTPS) {
options.key = fs.readFileSync(key);
options.cert = fs.readFileSync(cert);
}
// proxy
const proxy = new Proxy(options);
proxy.listen(port);
// handle errors
process.on("unhandledRejection", function(e) {
console.error("An error occured", e.message);
process.exit(1);
});

19
src/proxy/bin/help Normal file
View file

@ -0,0 +1,19 @@
Usage: 'coin-hive-stratum <port>'
<port>: The port where the server will listen to
Options:
--host The pool's host.
--port The pool's port.
--pass The pool's password, by default it's "x".
--ssl Use SSL/TLS to connect to the pool.
--address A fixed wallet address for all the miners.
--user A fixed user for all the miners.
--diff A fixed difficulty for all the miner. This is not supported by all the pools.
--dynamic-pool If true, the pool can be set dynamically by sending a ?pool=host:port:pass query param to the websocket endpoint.
--max-miners-per-connection Set the max amount of miners per TCP connection. When this number is exceded, a new socket is created. By default it's 100.
--path Accept connections on a specific path.
--key Path to private key file. Used for HTTPS/WSS.
--cert Path to certificate file. Used for HTTPS/WSS.
--credentials Credentials to access the /stats, /miners and /connections endponts. (usage: --credentials=username:password)

View file

@ -0,0 +1,388 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter = require("events");
var Protocol = require('../../protocol/protocol');
var uuid = require("uuid");
var Queue_1 = require("./Queue");
// var Metrics_1 = require("./Metrics");
const COMP = '[Connect]';
const Debug = require('../../log')();
var Connection = /** @class */ (function (_super) {
__extends(Connection, _super);
function Connection(options) {
var _this = _super.call(this) || this;
_this.id = uuid.v4();
_this.host = null;
_this.port = null;
_this.ssl = null;
_this.online = null;
_this.queue = null;
_this.protocolname = null;
_this.user = null;
_this.pass = null;
_this.Reconnect = true;
_this.buffer = "";
_this.rpcId = 1;
_this.rpc = {};
_this.auth = {};
_this.minerId = {};
_this.miners = [];
_this.host = options.host;
_this.port = options.port;
_this.ssl = options.ssl;
_this.user = options.user;
_this.pass = options.pass;
_this.cryptoname = options.cryptoname;
_this.protocolname = options.protocolname ? options.protocolname : 'jsonrpc';
_this.Protocol = Protocol({ name: _this.protocolname, cryptoname: _this.cryptoname });
return _this;
}
Connection.prototype.connect = function () {
var _this = this;
if (this.online) {
this.kill();
}
this.queue = new Queue_1.default();
this.Protocol.init(_this.ssl, _this.host, _this.port);
this.Protocol.on("connect", function () {
_this.ready();
});
this.Protocol.on("error", function (error) {
if (_this.online) {
Debug.IbctLogErr(COMP, "Protocol error (" + _this.host + ":" + _this.port + ")", error.message);
_this.emit("error", error);
// exit protocol
_this.Protocol.kill();
_this.Protocol.removeAllListeners("connect");
_this.Protocol.removeAllListeners("error");
_this.Protocol.removeAllListeners("close");
_this.Protocol.removeAllListeners("data");
_this.Protocol.removeAllListeners();
}
});
this.Protocol.on("close", function () {
if (_this.online) {
Debug.IbctLogDbg(COMP, "Protocol closed (" + _this.host + ":" + _this.port + ")");
_this.emit("close");
}
});
this.online = true;
};
Connection.prototype.setReconnect = function (flags) {
this.Reconnect = flags;
}
Connection.prototype.kill = function () {
this.Protocol.kill();
this.Protocol.removeAllListeners();
if (this.queue != null) {
this.queue.stop();
this.queue.removeAllListeners();
}
if (this.online) {
this.online = false;
this.removeAllListeners()
}
};
Connection.prototype.ready = function () {
var _this = this;
// message from pool
this.Protocol.getPacket();
this.Protocol.on("data", function (message) {
if (_this.protocolname === 'jsonrpc') {
_this.receive(message);
} else {
_this.stratum_receive(message);
}
});
// message from miner
this.queue.on("message", function (message) {
if (!_this.online) {
return false;
}
if (!_this.Protocol.isWritable()) {
if (message.method === "keepalived") {
return false;
}
var retry = message.retry ? message.retry * 2 : 1;
var ms = retry * 100;
message.retry = retry;
setTimeout(function () {
_this.queue.push({
type: "message",
payload: message
});
}, ms);
return false;
}
try {
if (message.retry) {
delete message.retry;
}
_this.Protocol.sendPacket(message);
}
catch (e) {
Debug.IbctLogErr(COMP, "failed to send message to pool (" + _this.host + ":" + _this.port + "): " + JSON.stringify(message));
}
});
// kick it
this.queue.start();
this.emit("ready");
};
Connection.prototype.stratum_receive = function (message) {
// it's a response
Debug.IbctLogInfo(COMP, 'stratum_receive: ', JSON.stringify(message));
if (message.id) {
var response = message;
if (!this.rpc[response.id]) {
if(response.result && response.result.subscribe){
var subinfo = response.result;
var minerId = this.minerId[subinfo.id];
if (!minerId) {
// miner is not online anymore
return;
}
this.emit(minerId + ":subscribe", subinfo.subscribe);
}
// miner is not online anymore
return;
}
var minerId = this.rpc[response.id].minerId;
var method = this.rpc[response.id].message.method;
switch (method) {
case "login": {
if (response.error && response.error.code === -1) {
this.emit(minerId + ":error", {
error: "invalid_site_key"
});
return;
} else if (response.error) {
this.emit(minerId + ":error", {
error: response.error[1]
});
return;
}
var result = response.result;
var auth = result.id;
this.auth[minerId] = auth;
this.minerId[auth] = minerId;
this.emit(minerId + ":authed", auth);
if (result.job) {
this.emit(minerId + ":job", result.job);
}
break;
}
case "submit": {
var job = this.rpc[response.id].message.params;
if (response.result === true || response.result === 'true') {
this.emit(minerId + ":accepted", job);
} else if (response.error) {
this.emit(minerId + ":rejected", job, response.error);
} else {
this.emit(minerId + ":rejected", job, 'Submit Error');
}
break;
}
default: {
if (response.error && response.error.code === -1) {
this.emit(minerId + ":error", response.error);
}
}
}
delete this.rpc[response.id];
}
else {
// it's a request
var request = message;
if (request.error) {
this.emit(minerId + ":error", response.error);
return;
}
if (request.result && request.result.job) {
var jobParams = request.result;
var minerId = this.minerId[jobParams.id];
if (!minerId) {
// miner is not online anymore
return;
}
this.emit(minerId + ":job", jobParams.job);
}
if(request.result && request.result.subscribe){
var subinfo = request.result;
var minerId = this.minerId[subinfo.id];
if (!minerId) {
// miner is not online anymore
return;
}
this.emit(minerId + ":subscribe", subinfo.subscribe);
}
if(request.result && request.result.diff){
var diffinfo = request.result;
var minerId = this.minerId[diffinfo.id];
if (!minerId) {
// miner is not online anymore
Debug.IbctLogDbg(COMP, 'diff but no minerID.....');
return;
}
this.emit(minerId + ":diff", diffinfo.diff);
}
}
};
Connection.prototype.receive = function (message) {
var data = null;
try {
data = JSON.parse(message);
}
catch (e) {
return Debug.IbctLogErr(COMP, "invalid stratum message:", message);
}
// it's a response
Debug.IbctLogInfo(COMP, 'receive: ', JSON.stringify(data));
if (data.id) {
var response = data;
if (!this.rpc[response.id]) {
// miner is not online anymore
return;
}
var minerId = this.rpc[response.id].minerId;
var method = this.rpc[response.id].message.method;
switch (method) {
case "login": {
if (response.error && response.error.code === -1) {
this.emit(minerId + ":error", {
error: "invalid_site_key"
});
return;
} else if (response.error) {
this.emit(minerId + ":error", {
error: response.error.message
});
return;
}
var result = response.result;
var auth = result.id;
this.auth[minerId] = auth;
this.minerId[auth] = minerId;
this.emit(minerId + ":authed", auth);
if (result.job) {
this.emit(minerId + ":job", result.job);
}
break;
}
case "submit": {
var job = this.rpc[response.id].message.params;
if (response.result && response.result.status === "OK") {
this.emit(minerId + ":accepted", job);
}
else if (response.error) {
this.emit(minerId + ":rejected", job, response.error);
} else {
this.emit(minerId + ":rejected", job, 'Submit Error');
}
break;
}
default: {
if (response.error && response.error.code === -1) {
this.emit(minerId + ":error", response.error);
}
}
}
delete this.rpc[response.id];
}
else {
// it's a request
var request = data;
switch (request.method) {
case "job": {
var jobParams = request.params;
var minerId = this.minerId[jobParams.id];
if (!minerId) {
// miner is not online anymore
return;
}
this.emit(minerId + ":job", request.params);
break;
}
}
}
};
Connection.prototype.send = function (id, method, params) {
if (params === void 0) { params = {}; }
var message = {
id: this.rpcId++,
method: method,
params: params
};
switch (method) {
case "login": {
// ..
break;
}
case "keepalived": {
if (this.auth[id]) {
var keepAliveParams = message.params;
keepAliveParams.id = this.auth[id];
}
else {
return false;
}
}
case "submit": {
if (this.auth[id]) {
var submitParams = message.params;
submitParams.id = this.auth[id];
}
else {
Debug.IbctLogErr('Submit Err: Maybe have killed');
return false;
}
}
}
this.rpc[message.id] = {
minerId: id,
message: message
};
this.queue.push({
type: "message",
payload: message
});
};
Connection.prototype.addMiner = function (miner) {
if (this.miners.indexOf(miner) === -1) {
this.miners.push(miner);
}
};
Connection.prototype.removeMiner = function (minerId) {
var miner = this.miners.find(function (x) { return x.id === minerId; });
if (miner) {
this.miners = this.miners.filter(function (x) { return x.id !== minerId; });
this.clear(miner.id);
}
};
Connection.prototype.clear = function (id) {
var _this = this;
var auth = this.auth[id];
delete this.auth[id];
delete this.minerId[auth];
Object.keys(this.rpc).forEach(function (key) {
if (_this.rpc[key].minerId === id) {
delete _this.rpc[key];
}
});
};
return Connection;
}(EventEmitter));
exports.default = Connection;

View file

@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var pmx = require("pmx");
var probe = pmx.probe();
const COMP = '[Metrics]';
exports.minersCounter = probe.counter({
name: "Miners"
});
exports.connectionsCounter = probe.counter({
name: "Connections"
});
exports.sharesCounter = probe.counter({
name: "Shares"
});
exports.sharesMeter = probe.meter({
name: "Shares per minute",
samples: 60
});

303
src/proxy/build/Miner.js Normal file
View file

@ -0,0 +1,303 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function () { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter = require("events");
var uuid = require("uuid");
var Queue_1 = require("./Queue");
// var Metrics_1 = require("./Metrics");
const Debug = require('../../log')();
const COMP = '[Proxy Miner]';
var Miner = /** @class */ (function (_super) {
__extends(Miner, _super);
function Miner(options) {
var _this = _super.call(this) || this;
_this.id = uuid.v4();
_this.login = null;
_this.address = null;
_this.user = null;
_this.diff = null;
_this.pass = null;
_this.heartbeat = null;
_this.connection = null;
_this.minerQueue = null;
_this.protocolname = null;
_this.queue = new Queue_1.default();
_this.online = false;
_this.jobs = [];
_this.hashes = 0;
_this.rejected = 0;
_this.connection = options.connection;
_this.address = options.address;
_this.user = options.user;
_this.diff = options.diff;
_this.pass = options.pass;
_this.minerQueue = options.minerQueue;
_this.queueId = options.queueId
_this.protocolname = options.protocolname;
return _this;
}
Miner.prototype.connect = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
Debug.IbctLogDbg(COMP, "miner connected (" + this.id + ")");
// Metrics_1.minersCounter.inc();
this.connection.addMiner(this);
this.connection.on(this.id + ":authed", this.handleAuthed.bind(this));
this.connection.on(this.id + ":job", this.handleJob.bind(this));
this.connection.on(this.id + ":subscribe", this.handleSubscribe.bind(this));
this.connection.on(this.id + ":diff", this.handleSetdiff.bind(this));
this.connection.on(this.id + ":accepted", this.handleAccepted.bind(this));
this.connection.on(this.id + ":error", this.handleError.bind(this));
this.connection.on(this.id + ":rejected", this.handleRejected.bind(this));
this.queue.on("message", function (message) {
return _this.connection.send(_this.id, message.method, message.params);
});
if (this.protocolname === 'jsonrpc') {
this.heartbeat = setInterval(function () { return _this.connection.send(_this.id, "keepalived"); }, 30000);
}
this.online = true;
return [4 /*yield*/];
case 1:
_a.sent();
if (this.online) {
this.queue.start();
this.emit("open", {
id: this.id
});
}
return [2 /*return*/];
}
});
});
};
Miner.prototype.kill = function () {
if (this.queue) {
this.queue.stop();
this.queue.removeAllListeners();
}
this.connection.removeMiner(this.id);
this.connection.removeAllListeners(this.id + ":authed");
this.connection.removeAllListeners(this.id + ":job");
this.connection.removeAllListeners(this.id + ":accepted");
this.connection.removeAllListeners(this.id + ":rejected");
this.connection.removeAllListeners(this.id + ":error");
this.jobs = [];
this.hashes = 0;
this.rejected = 0;
if (this.heartbeat) {
clearInterval(this.heartbeat);
this.heartbeat = null;
}
if (this.online) {
this.online = false;
// Metrics_1.minersCounter.dec();
Debug.IbctLogDbg(COMP, "miner disconnected (" + this.id + ")");
this.emit("close", {
id: this.id,
login: this.login
});
}
this.removeAllListeners();
};
Miner.prototype.sendToMiner = function (payload) {
if (this.online && this.minerQueue) {
payload.type += ':' + this.queueId;
this.minerQueue.push(payload);
}
};
Miner.prototype.sendToPool = function (method, params) {
this.queue.push({
type: "message",
payload: {
method: method,
params: params
}
});
};
Miner.prototype.handleAuthed = function (auth) {
Debug.IbctLogDbg(COMP, "miner authenticated (" + this.id + "):", auth);
this.sendToMiner({
type: "authed",
payload: {
token: "",
hashes: 0,
auth: auth
}
});
this.emit("authed", {
id: this.id,
login: this.login,
auth: auth
});
};
Miner.prototype.handleSetdiff = function (diff) {
Debug.IbctLogDbg(COMP, "miner setdiff (" + this.id + "):", diff);
this.sendToMiner({
type: "diff",
payload:diff
});
this.emit("diff", {
id: this.id,
login: this.login,
diff: diff
});
};
Miner.prototype.handleSubscribe = function (subscribe) {
Debug.IbctLogDbg(COMP, "miner subscribed (" + this.id + "):", subscribe);
this.sendToMiner({
type: "subscribe",
payload: subscribe
});
this.emit("subscribe", {
id: this.id,
login: this.login,
subscribe: subscribe
});
};
Miner.prototype.handleJob = function (job) {
Debug.IbctLogDbg(COMP, "job arrived (" + this.id + "):", job[0]);
this.jobs.push(job);
this.sendToMiner({
type: "job",
payload: this.jobs.pop()
});
this.emit("job", {
id: this.id,
login: this.login,
job: job
});
};
Miner.prototype.handleAccepted = function (job) {
this.hashes++;
Debug.IbctLogDbg(COMP, "shares accepted (" + this.id + "):", this.hashes);
// Metrics_1.sharesCounter.inc();
// Metrics_1.sharesMeter.mark();
this.sendToMiner({
type: "accepted",
payload: {
hashes: this.hashes,
nonce: job
}
});
this.emit("accepted", {
id: this.id,
login: this.login,
hashes: this.hashes
});
};
Miner.prototype.handleError = function (error) {
Debug.IbctLogErr(COMP, "pool connection error (" + this.id + "):", error.error || (error && JSON.stringify(error)) || "unknown error");
if (this.online) {
if (error.error === "invalid_site_key") {
this.sendToMiner({
type: "error",
payload: error
});
}
this.emit("error", {
id: this.id,
login: this.login,
error: error
});
}
this.kill();
};
Miner.prototype.handleRejected = function (job, error) {
Debug.IbctLogDbg(COMP, "shares rejected(" + this.id + "):", this.hashes);
this.rejected++;
this.sendToMiner({
type: "rejected",
payload: {
rejected: this.rejected,
nonce: job,
err: error
}
});
this.emit("rejected", {
id: this.id,
login: this.login,
rejected: this.rejected
});
};
Miner.prototype.handleMessage = function (message) {
switch (message.type) {
case "auth": {
var params = message.params;
this.login = this.user || params.user;
if (this.diff) {
this.login += "+" + this.diff;
}
this.sendToPool("login", {
login: this.login,
pass: this.pass,
agent: "Ibctminer/1.0.0"
});
break;
}
case "submit": {
var job = message.params;
Debug.IbctLogDbg(COMP, "job submitted (" + this.id + "):", job.job_id);
this.sendToPool("submit", job);
this.emit("found", {
id: this.id,
login: this.login,
job: job
});
break;
}
}
};
return Miner;
}(EventEmitter));
exports.default = Miner;

141
src/proxy/build/Proxy.js Normal file
View file

@ -0,0 +1,141 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter = require("events");
var defaults = require("../config/defaults");
var Connection_1 = require("./Connection");
var Miner_1 = require("./Miner");
const Debug = require('../../log')();
const COMP = '[Proxy]';
var Proxy = /** @class */ (function (_super) {
__extends(Proxy, _super);
function Proxy(constructorOptions) {
if (constructorOptions === void 0) { constructorOptions = defaults; }
var _this = _super.call(this) || this;
_this.host = null;
_this.port = null;
_this.pass = null;
_this.ssl = null;
_this.address = null;
_this.user = null;
_this.maxMinersPerConnection = 100;
_this.connections = {};
_this.online = false;
var options = Object.assign({}, defaults, constructorOptions);
_this.host = options.host;
_this.port = options.port;
_this.pass = options.pass;
_this.ssl = options.ssl;
_this.protocolname = options.protocolname;
_this.cryptoname = options.cryptoname;
_this.address = options.address;
_this.user = options.user;
_this.maxMinersPerConnection = options.maxMinersPerConnection;
_this.on("error", function (error) {
/* prevent unhandled proxy errors from stopping the proxy */
Debug.IbctLogErr(COMP, "proxy error:", error);
});
}
Proxy.prototype.createProxy = function (id, queue) {
var _this = this;
if (this.online) {
this.kill();
}
var connection = _this.getConnection(_this.host, _this.port);
var miner = new Miner_1.default({
protocolname: _this.protocolname,
connection: connection,
address: _this.address,
user: _this.user,
pass: _this.pass,
minerQueue: queue,
queueId: id
});
miner.on("open", function (data) { return _this.emit("open", data); });
miner.on("authed", function (data) { return _this.emit("authed", data); });
miner.on("job", function (data) {return _this.emit("job", data); });
miner.on("diff", function (data) { return _this.emit("diff", data); });
miner.on("subscribe", function (data) {return _this.emit("subscribe", data); });
miner.on("found", function (data) { return _this.emit("found", data); });
miner.on("accepted", function (data) { return _this.emit("accepted", data); });
miner.on("rejected", function (data) { return _this.emit("rejected", data); });
miner.on("close", function (data) { return _this.emit("close", data); });
miner.on("error", function (data) { return _this.emit("error", data); });
miner.connect();
return miner;
};
Proxy.prototype.getConnection = function (host, port) {
var _this = this;
var connectionId = host + ":" + port;
if (!this.connections[connectionId]) {
this.connections[connectionId] = [];
}
var connections = this.connections[connectionId];
var availableConnections = connections.filter(function (connection) { return _this.isAvailable(connection); });
if (availableConnections.length === 0) {
var connection = new Connection_1.default({ host: host, port: port, user: this.user, pass: this.pass, ssl: this.ssl, protocolname: this.protocolname, cryptoname: this.cryptoname });
connection.connect();
connection.on("close", function () {
Debug.IbctLogDbg(COMP, "connection closed (" + connectionId + ")");
});
connection.on("error", function (error) {
Debug.IbctLogErr(COMP, "connection error (" + connectionId + "):", error.message);
_this.emit("error", error);
});
connections.push(connection);
return connection;
}
return availableConnections.pop();
};
Proxy.prototype.isAvailable = function (connection) {
return (connection.miners.length < this.maxMinersPerConnection);
};
Proxy.prototype.isEmpty = function (connection) {
return connection.miners.length === 0;
};
Proxy.prototype.getStats = function () {
var _this = this;
return Object.keys(this.connections).reduce(function (stats, key) { return ({
miners: stats.miners.concat(_this.connections[key].reduce(function (miners, connection) { return miners.concat(connection.miners.map(function (miner) { return ({
id: miner.id,
login: miner.login,
hashes: miner.hashes
}); })); }, [])),
connections: stats.connections.concat(_this.connections[key].map(function (connection) { return ({
id: connection.id,
host: connection.host,
port: connection.port,
miners: connection.miners.length
}); }))
}); }, {
miners: [],
connections: []
});
};
Proxy.prototype.kill = function () {
var _this = this;
Object.keys(this.connections).forEach(function (connectionId) {
var connections = _this.connections[connectionId];
connections.forEach(function (connection) {
connection.setReconnect(false);
connection.kill();
connection.miners.forEach(function (miner) { return miner.kill(); });
});
});
this.online = false;
Debug.IbctLogDbg(COMP, "\uD83D\uDC80");
};
return Proxy;
}(EventEmitter));
exports.default = Proxy;

63
src/proxy/build/Queue.js Normal file
View file

@ -0,0 +1,63 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter = require("events");
const Debug = require('../../log')();
const COMP = '[Queue]';
var Queue = /** @class */ (function (_super) {
__extends(Queue, _super);
function Queue(ms) {
if (ms === void 0) { ms = 100; }
var _this = _super.call(this) || this;
_this.events = [];
_this.interval = null;
_this.bypassed = false;
_this.ms = 100;
_this.ms = ms;
return _this;
}
Queue.prototype.start = function () {
var _this = this;
if (this.interval == null) {
var that_1 = this;
this.interval = setInterval(function () {
var event = that_1.events.pop();
if (event) {
that_1.emit(event.type, event.payload);
}
else {
_this.bypass();
}
}, this.ms);
}
};
Queue.prototype.stop = function () {
if (this.interval != null) {
clearInterval(this.interval);
this.interval = null;
}
};
Queue.prototype.bypass = function () {
this.bypassed = true;
this.stop();
};
Queue.prototype.push = function (event) {
if (this.bypassed) {
this.emit(event.type, event.payload);
}
else {
this.events.push(event);
}
};
return Queue;
}(EventEmitter));
exports.default = Queue;

9
src/proxy/build/index.js Normal file
View file

@ -0,0 +1,9 @@
"use strict";
var Proxy = require("./Proxy");
const Debug = require('../../log')();
const COMP = '[Proxy Index]';
process.on("uncaughtException", function (error) {
/* prevent unhandled process errors from stopping the proxy */
Debug.IbctLogErr(COMP, "process error:", error);
});
module.exports = Proxy.default;

3
src/proxy/build/types.js Normal file
View file

@ -0,0 +1,3 @@
"use strict";
// Misc
Object.defineProperty(exports, "__esModule", { value: true });

View file

@ -0,0 +1,11 @@
module.exports = {
host: null,
port: 3333,
pass: "x",
ssl: false,
address: null,
user: null,
diff: null,
dynamicPool: false,
maxMinersPerConnection: 100,
};

218
src/proxy/package.json Normal file
View file

@ -0,0 +1,218 @@
{
"name": "coin-hive-stratum",
"version": "2.6.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "8.0.53",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.53.tgz",
"integrity": "sha512-54Dm6NwYeiSQmRB1BLXKr5GELi0wFapR1npi8bnZhEcu84d/yQKqnwwXQ56hZ0RUbTG6L5nqDZaN3dgByQXQRQ=="
},
"@types/ws": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-3.2.0.tgz",
"integrity": "sha512-XehU2SdII5wu7EUV1bAwCoTDZYZCCU7Es7gbHtJjGXq6Bs2AI4HuJ//wvPrVuuYwkkZseQzDUxsZF8Urnb3I1A==",
"requires": {
"@types/node": "8.0.53"
}
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"async-listener": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.8.tgz",
"integrity": "sha512-1Sy1jDhjlgxcSd9/ICHqiAHT8VSJ9R1lzEyWwP/4Hm9p8nVTNtU0SxG/Z15XHD/aZvQraSw9BpDU3EBcFnOVrw==",
"requires": {
"semver": "5.4.1",
"shimmer": "1.2.0"
}
},
"basic-auth": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz",
"integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=",
"requires": {
"safe-buffer": "5.1.1"
}
},
"continuation-local-storage": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz",
"integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==",
"requires": {
"async-listener": "0.6.8",
"emitter-listener": "1.1.1"
}
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"emitter-listener": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.1.tgz",
"integrity": "sha1-6Lu+gkS8jg0LTvcc0UKUx/JBx+w=",
"requires": {
"shimmer": "1.2.0"
}
},
"exec-sh": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.1.tgz",
"integrity": "sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg==",
"requires": {
"merge": "1.2.0"
}
},
"extend": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
},
"is": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/is/-/is-3.2.1.tgz",
"integrity": "sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU="
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"lodash.findindex": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz",
"integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"lodash.merge": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz",
"integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU="
},
"merge": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz",
"integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"moment": {
"version": "2.19.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.19.1.tgz",
"integrity": "sha1-VtoaLRy/AdOLfhr8McELz6GSkWc="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"pmx": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/pmx/-/pmx-1.5.5.tgz",
"integrity": "sha1-tuC4V27c9Y1/QGlntE2z2nfjV/A=",
"requires": {
"debug": "3.1.0",
"json-stringify-safe": "5.0.1",
"vxx": "1.2.2"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
},
"shimmer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz",
"integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag=="
},
"typescript": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.1.tgz",
"integrity": "sha1-7znN6ierrAtQAkLWcmq5DgyEZjE="
},
"ultron": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz",
"integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ="
},
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
},
"vxx": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/vxx/-/vxx-1.2.2.tgz",
"integrity": "sha1-dB+1HG8R0zg9pvm5IBil17qAdhE=",
"requires": {
"continuation-local-storage": "3.2.1",
"debug": "2.6.9",
"extend": "3.0.1",
"is": "3.2.1",
"lodash.findindex": "4.6.0",
"lodash.isequal": "4.5.0",
"lodash.merge": "4.6.0",
"methods": "1.1.2",
"semver": "5.4.1",
"shimmer": "1.2.0",
"uuid": "3.1.0"
}
},
"watch": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz",
"integrity": "sha1-NApxe952Vyb6CqB9ch4BR6VR3ww=",
"requires": {
"exec-sh": "0.2.1",
"minimist": "1.2.0"
}
},
"ws": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-3.2.0.tgz",
"integrity": "sha512-hTS3mkXm/j85jTQOIcwVz3yK3up9xHgPtgEhDBOH3G18LDOZmSAG1omJeXejLKJakx+okv8vS1sopgs7rw0kVw==",
"requires": {
"async-limiter": "1.0.0",
"safe-buffer": "5.1.1",
"ultron": "1.1.0"
}
}
}
}

10
src/proxy/server.js Normal file
View file

@ -0,0 +1,10 @@
const Proxy = require("./build");
const proxy = new Proxy({
});
proxy.listen(process.env.PORT || 8892);
setInterval(function() {
console.log(`Going to reload proxy`);
process.exit(0);
}, 14400 * 1000);

275
src/proxy/src/Connection.ts Normal file
View file

@ -0,0 +1,275 @@
import * as EventEmitter from "events";
import * as net from "net";
import * as tls from "tls";
import * as uuid from "uuid";
import Miner from "./Miner";
import Queue from "./Queue";
import { connectionsCounter } from "./Metrics";
import {
Dictionary,
Socket,
StratumRequestParams,
StratumResponse,
StratumRequest,
StratumJob,
StratumLoginResult,
RPCMessage,
StratumKeepAlive,
Job
} from "./types";
export type Options = {
host: string;
port: number;
ssl: boolean;
};
class Connection extends EventEmitter {
id: string = uuid.v4();
host: string = null;
port: number = null;
ssl: boolean = null;
online: boolean = null;
socket: Socket = null;
queue: Queue = null;
buffer: string = "";
rpcId: number = 1;
rpc: Dictionary<RPCMessage> = {};
auth: Dictionary<string> = {};
minerId: Dictionary<string> = {};
miners: Miner[] = [];
constructor(options: Options) {
super();
this.host = options.host;
this.port = options.port;
this.ssl = options.ssl;
}
connect() {
if (this.online) {
this.kill();
}
this.queue = new Queue();
if (this.ssl) {
this.socket = tls.connect(+this.port, this.host, { rejectUnauthorized: false });
} else {
this.socket = net.connect(+this.port, this.host);
}
this.socket.on("connect", this.ready.bind(this));
this.socket.on("error", error => {
if (this.online) {
console.warn(`socket error (${this.host}:${this.port})`, error.message);
this.emit("error", error);
this.connect();
}
});
this.socket.on("close", () => {
if (this.online) {
console.log(`socket closed (${this.host}:${this.port})`);
this.emit("close");
}
});
this.socket.setKeepAlive(true);
this.socket.setEncoding("utf8");
this.online = true;
}
kill() {
if (this.socket != null) {
try {
this.socket.end();
this.socket.destroy();
} catch (e) {
console.warn(`something went wrong while destroying socket (${this.host}:${this.port}):`, e.message);
}
}
if (this.queue != null) {
this.queue.stop();
}
if (this.online) {
this.online = false;
}
}
ready() {
// message from pool
this.socket.on("data", chunk => {
this.buffer += chunk;
while (this.buffer.includes("\n")) {
const newLineIndex = this.buffer.indexOf("\n");
const stratumMessage = this.buffer.slice(0, newLineIndex);
this.buffer = this.buffer.slice(newLineIndex + 1);
this.receive(stratumMessage);
}
});
// message from miner
this.queue.on("message", (message: StratumRequest) => {
if (!this.online) {
return false;
}
if (!this.socket.writable) {
if (message.method === "keepalived") {
return false;
}
const retry = message.retry ? message.retry * 2 : 1;
const ms = retry * 100;
message.retry = retry;
setTimeout(() => {
this.queue.push({
type: "message",
payload: message
});
}, ms);
return false;
}
try {
if (message.retry) {
delete message.retry;
}
this.socket.write(JSON.stringify(message) + "\n");
} catch (e) {
console.warn(`failed to send message to pool (${this.host}:${this.port}): ${JSON.stringify(message)}`);
}
});
// kick it
this.queue.start();
this.emit("ready");
}
receive(message: string) {
let data = null;
try {
data = JSON.parse(message);
} catch (e) {
return console.warn(`invalid stratum message:`, message);
}
// it's a response
if (data.id) {
const response = data as StratumResponse;
if (!this.rpc[response.id]) {
// miner is not online anymore
return;
}
const minerId = this.rpc[response.id].minerId;
const method = this.rpc[response.id].message.method;
switch (method) {
case "login": {
if (response.error && response.error.code === -1) {
this.emit(minerId + ":error", {
error: "invalid_site_key"
});
return;
}
const result = response.result as StratumLoginResult;
const auth = result.id;
this.auth[minerId] = auth;
this.minerId[auth] = minerId;
this.emit(minerId + ":authed", auth);
if (result.job) {
this.emit(minerId + ":job", result.job);
}
break;
}
case "submit": {
const job = this.rpc[response.id].message.params as StratumJob;
if (response.result && response.result.status === "OK") {
this.emit(minerId + ":accepted", job);
} else if (response.error) {
this.emit(minerId + ":error", response.error);
}
break;
}
default: {
if (response.error && response.error.code === -1) {
this.emit(minerId + ":error", response.error);
}
}
}
delete this.rpc[response.id];
} else {
// it's a request
const request = data as StratumRequest;
switch (request.method) {
case "job": {
const jobParams = request.params as StratumJob;
const minerId = this.minerId[jobParams.id];
if (!minerId) {
// miner is not online anymore
return;
}
this.emit(minerId + ":job", request.params);
break;
}
}
}
}
send(id: string, method: string, params: StratumRequestParams = {}) {
let message: StratumRequest = {
id: this.rpcId++,
method,
params
};
switch (method) {
case "login": {
// ..
break;
}
case "keepalived": {
if (this.auth[id]) {
const keepAliveParams = message.params as StratumKeepAlive;
keepAliveParams.id = this.auth[id];
} else {
return false;
}
}
case "submit": {
if (this.auth[id]) {
const submitParams = message.params as StratumJob;
submitParams.id = this.auth[id];
} else {
return false;
}
}
}
this.rpc[message.id] = {
minerId: id,
message
};
this.queue.push({
type: "message",
payload: message
});
}
addMiner(miner: Miner): void {
if (this.miners.indexOf(miner) === -1) {
this.miners.push(miner);
}
}
removeMiner(minerId: string): void {
const miner = this.miners.find(x => x.id === minerId);
if (miner) {
this.miners = this.miners.filter(x => x.id !== minerId);
this.clear(miner.id);
}
}
clear(id: string): void {
const auth = this.auth[id];
delete this.auth[id];
delete this.minerId[auth];
Object.keys(this.rpc).forEach(key => {
if (this.rpc[key].minerId === id) {
delete this.rpc[key];
}
});
}
}
export default Connection;

19
src/proxy/src/Metrics.ts Normal file
View file

@ -0,0 +1,19 @@
import * as pmx from "pmx";
const probe = pmx.probe();
export const minersCounter = probe.counter({
name: "Miners"
});
export const connectionsCounter = probe.counter({
name: "Connections"
});
export const sharesCounter = probe.counter({
name: "Shares"
});
export const sharesMeter = probe.meter({
name: "Shares per minute",
samples: 60
});

248
src/proxy/src/Miner.ts Normal file
View file

@ -0,0 +1,248 @@
import * as EventEmitter from "events";
import * as WebSocket from "ws";
import * as uuid from "uuid";
import Connection from "./Connection";
import Queue from "./Queue";
import { minersCounter, sharesCounter, sharesMeter } from "./Metrics";
import {
Job,
CoinHiveError,
CoinHiveResponse,
CoinHiveLoginParams,
CoinHiveRequest,
StratumRequest,
StratumRequestParams,
StratumError,
StratumJob
} from "./types";
export type Options = {
connection: Connection | null;
ws: WebSocket | null;
address: string | null;
user: string | null;
diff: number | null;
pass: string | null;
};
class Miner extends EventEmitter {
id: string = uuid.v4();
login: string = null;
address: string = null;
user: string = null;
diff: number = null;
pass: string = null;
heartbeat: NodeJS.Timer = null;
connection: Connection = null;
queue: Queue = new Queue();
ws: WebSocket = null;
online: boolean = false;
jobs: Job[] = [];
hashes: number = 0;
constructor(options: Options) {
super();
this.connection = options.connection;
this.ws = options.ws;
this.address = options.address;
this.user = options.user;
this.diff = options.diff;
this.pass = options.pass;
}
async connect() {
console.log(`miner connected (${this.id})`);
minersCounter.inc();
this.ws.on("message", this.handleMessage.bind(this));
this.ws.on("close", () => {
if (this.online) {
console.log(`miner connection closed (${this.id})`);
this.kill();
}
});
this.ws.on("error", error => {
if (this.online) {
console.log(`miner connection error (${this.id}):`, error.message);
this.kill();
}
});
this.connection.addMiner(this);
this.connection.on(this.id + ":authed", this.handleAuthed.bind(this));
this.connection.on(this.id + ":job", this.handleJob.bind(this));
this.connection.on(this.id + ":accepted", this.handleAccepted.bind(this));
this.connection.on(this.id + ":error", this.handleError.bind(this));
this.queue.on("message", (message: StratumRequest) =>
this.connection.send(this.id, message.method, message.params)
);
this.heartbeat = setInterval(() => this.connection.send(this.id, "keepalived"), 30000);
this.online = true;
if (this.online) {
this.queue.start();
console.log(`miner started (${this.id})`);
this.emit("open", {
id: this.id
});
}
}
kill() {
this.queue.stop();
this.connection.removeMiner(this.id);
this.connection.removeAllListeners(this.id + ":authed");
this.connection.removeAllListeners(this.id + ":job");
this.connection.removeAllListeners(this.id + ":accepted");
this.connection.removeAllListeners(this.id + ":error");
this.jobs = [];
this.hashes = 0;
this.ws.close();
if (this.heartbeat) {
clearInterval(this.heartbeat);
this.heartbeat = null;
}
if (this.online) {
this.online = false;
minersCounter.dec();
console.log(`miner disconnected (${this.id})`);
this.emit("close", {
id: this.id,
login: this.login
});
}
this.removeAllListeners();
}
sendToMiner(payload: CoinHiveResponse) {
const coinhiveMessage = JSON.stringify(payload);
if (this.online && this.ws.readyState === WebSocket.OPEN) {
try {
this.ws.send(coinhiveMessage);
} catch (e) {
this.kill();
}
}
}
sendToPool(method: string, params: StratumRequestParams) {
this.queue.push({
type: "message",
payload: {
method,
params
}
});
}
handleAuthed(auth: string): void {
console.log(`miner authenticated (${this.id}):`, auth);
this.sendToMiner({
type: "authed",
params: {
token: "",
hashes: 0
}
});
this.emit("authed", {
id: this.id,
login: this.login,
auth
});
}
handleJob(job: Job): void {
console.log(`job arrived (${this.id}):`, job.job_id);
this.jobs.push(job);
this.sendToMiner({
type: "job",
params: this.jobs.pop()
});
this.emit("job", {
id: this.id,
login: this.login,
job
});
}
handleAccepted(job: StratumJob): void {
this.hashes++;
console.log(`shares accepted (${this.id}):`, this.hashes);
sharesCounter.inc();
sharesMeter.mark();
this.sendToMiner({
type: "hash_accepted",
params: {
hashes: this.hashes
}
});
this.emit("accepted", {
id: this.id,
login: this.login,
hashes: this.hashes
});
}
handleError(error: StratumError): void {
console.warn(
`pool connection error (${this.id}):`,
error.error || (error && JSON.stringify(error)) || "unknown error"
);
if (this.online) {
if (error.error === "invalid_site_key") {
this.sendToMiner({
type: "error",
params: error
});
}
this.emit("error", {
id: this.id,
login: this.login,
error
});
}
this.kill();
}
handleMessage(message: string) {
let data: CoinHiveRequest;
try {
data = JSON.parse(message);
} catch (e) {
console.warn(`can't parse message as JSON from miner:`, message, e.message);
return;
}
switch (data.type) {
case "auth": {
const params = data.params as CoinHiveLoginParams;
this.login = this.address || params.site_key;
const user = this.user || params.user;
if (user) {
this.login += "." + user;
}
if (this.diff) {
this.login += "+" + this.diff;
}
this.sendToPool("login", {
login: this.login,
pass: this.pass
});
break;
}
case "submit": {
const job = data.params as Job;
console.log(`job submitted (${this.id}):`, job.job_id);
this.sendToPool("submit", job);
this.emit("found", {
id: this.id,
login: this.login,
job
});
break;
}
}
}
}
export default Miner;

310
src/proxy/src/Proxy.ts Normal file
View file

@ -0,0 +1,310 @@
import * as EventEmitter from "events";
import * as WebSocket from "ws";
import * as url from "url";
import * as http from "http";
import * as https from "https";
import * as defaults from "../config/defaults";
import Connection from "./Connection";
import Miner from "./Miner";
import {
Dictionary,
Stats,
WebSocketQuery,
ErrorEvent,
CloseEvent,
AcceptedEvent,
FoundEvent,
JobEvent,
AuthedEvent,
OpenEvent,
Credentials
} from "./types";
import { ServerRequest } from "http";
export type Options = {
host: string;
port: number;
pass: string;
ssl: false;
address: string | null;
user: string | null;
diff: number | null;
dynamicPool: boolean;
maxMinersPerConnection: number;
key: Buffer;
cert: Buffer;
path: string;
server: http.Server | https.Server;
credentials: Credentials;
};
class Proxy extends EventEmitter {
host: string = null;
port: number = null;
pass: string = null;
ssl: boolean = null;
address: string = null;
user: string = null;
diff: number = null;
dynamicPool: boolean = false;
maxMinersPerConnection: number = 100;
connections: Dictionary<Connection[]> = {};
wss: WebSocket.Server = null;
key: Buffer = null;
cert: Buffer = null;
path: string = null;
server: http.Server | https.Server = null;
credentials: Credentials = null;
online: boolean = false;
constructor(constructorOptions: Partial<Options> = defaults) {
super();
let options = Object.assign({}, defaults, constructorOptions) as Options;
this.host = options.host;
this.port = options.port;
this.pass = options.pass;
this.ssl = options.ssl;
this.address = options.address;
this.user = options.user;
this.diff = options.diff;
this.dynamicPool = options.dynamicPool;
this.maxMinersPerConnection = options.maxMinersPerConnection;
this.key = options.key;
this.cert = options.cert;
this.path = options.path;
this.server = options.server;
this.credentials = options.credentials;
this.on("error", error => {
/* prevent unhandled proxy errors from stopping the proxy */
console.error("proxy error:", error.message);
});
}
listen(port: number, host?: string, callback?: () => void): void {
const version = require("../package").version;
console.log(`coin-hive-stratum v${version}`);
if (this.online) {
this.kill();
}
// create server
const isHTTPS = !!(this.key && this.cert);
if (!this.server) {
const stats = (req: http.ServerRequest, res: http.ServerResponse) => {
if (this.credentials) {
const auth = require("basic-auth")(req);
if (!auth || auth.name !== this.credentials.user || auth.pass !== this.credentials.pass) {
res.statusCode = 401;
res.setHeader("WWW-Authenticate", 'Basic realm="Access to stats"');
res.end("Access denied");
return;
}
}
const url = require("url").parse(req.url);
if (url.pathname === "/ping") {
res.statusCode = 200;
res.end();
return;
}
if (url.pathname === "/ready") {
res.statusCode = this.online ? 200 : 503;
res.end();
return;
}
if (url.pathname === "/version") {
const body = JSON.stringify({ version });
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Content-Length": Buffer.byteLength(body),
"Content-Type": "application/json",
});
res.end(body);
return;
}
const proxyStats = this.getStats();
let body = JSON.stringify({
code: 404,
error: "Not Found"
});
if (url.pathname === "/stats") {
body = JSON.stringify(
{
miners: proxyStats.miners.length,
connections: proxyStats.connections.length
},
null,
2
);
}
if (url.pathname === "/miners") {
body = JSON.stringify(proxyStats.miners, null, 2);
}
if (url.pathname === "/connections") {
body = JSON.stringify(proxyStats.connections, null, 2);
}
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Content-Length": Buffer.byteLength(body),
"Content-Type": "application/json"
});
res.end(body);
};
if (isHTTPS) {
const certificates = {
key: this.key,
cert: this.cert
};
this.server = https.createServer(certificates, stats);
} else {
this.server = http.createServer(stats);
}
}
const wssOptions: WebSocket.ServerOptions = {
server: this.server
};
if (this.path) {
wssOptions.path = this.path;
}
this.wss = new WebSocket.Server(wssOptions);
this.wss.on("connection", (ws: WebSocket, req: ServerRequest) => {
const params = url.parse(req.url, true).query as WebSocketQuery;
params.pool = params.id;
let host = this.host;
let port = this.port;
let pass = this.pass;
if (params.pool && this.dynamicPool) {
const split = params.pool.split(":");
host = split[0] || this.host;
port = Number(split[1]) || this.port;
pass = split[2] || this.pass;
console.log(`Miner connected to pool`, host);
}
const connection = this.getConnection(host, port);
const miner = new Miner({
connection,
ws,
address: this.address,
user: this.user,
diff: this.diff,
pass
});
miner.on("open", (data: OpenEvent) => this.emit("open", data));
miner.on("authed", (data: AuthedEvent) => this.emit("authed", data));
miner.on("job", (data: JobEvent) => this.emit("job", data));
miner.on("found", (data: FoundEvent) => this.emit("found", data));
miner.on("accepted", (data: AcceptedEvent) => this.emit("accepted", data));
miner.on("close", (data: CloseEvent) => this.emit("close", data));
miner.on("error", (data: ErrorEvent) => this.emit("error", data));
miner.connect();
});
if (!host && !callback) {
this.server.listen(port);
} else if (!host && callback) {
this.server.listen(port, callback);
} else if (host && !callback) {
this.server.listen(port, host);
} else {
this.server.listen(port, host, callback);
}
this.wss.on("listening", () => {
this.online = true;
console.log(`listening on port ${port}` + (isHTTPS ? ", using a secure connection" : ""));
console.log(`miners per connection:`, this.maxMinersPerConnection);
if (wssOptions.path) {
console.log(`path: ${wssOptions.path}`);
}
if (!this.dynamicPool) {
console.log(`host: ${this.host}`);
console.log(`port: ${this.port}`);
console.log(`pass: ${this.pass}`);
}
});
}
getConnection(host: string, port: number): Connection {
const connectionId = `${host}:${port}`;
if (!this.connections[connectionId]) {
this.connections[connectionId] = [];
}
const connections = this.connections[connectionId];
const availableConnections = connections.filter(connection => this.isAvailable(connection));
if (availableConnections.length === 0) {
const connection = new Connection({ host, port, ssl: this.ssl });
connection.connect();
connection.on("close", () => {
console.log(`connection closed (${connectionId})`);
});
connection.on("error", error => {
console.log(`connection error (${connectionId}):`, error.message);
});
connections.push(connection);
return connection;
}
return availableConnections.pop();
}
isAvailable(connection: Connection): boolean {
return (
connection.miners.length < this.maxMinersPerConnection);
}
isEmpty(connection: Connection): boolean {
return connection.miners.length === 0;
}
getStats(): Stats {
return Object.keys(this.connections).reduce(
(stats, key) => ({
miners: [
...stats.miners,
...this.connections[key].reduce(
(miners, connection) => [
...miners,
...connection.miners.map(miner => ({
id: miner.id,
login: miner.login,
hashes: miner.hashes
}))
],
[]
)
],
connections: [
...stats.connections,
...this.connections[key].map(connection => ({
id: connection.id,
host: connection.host,
port: connection.port,
miners: connection.miners.length
}))
]
}),
{
miners: [],
connections: []
}
);
}
kill() {
Object.keys(this.connections).forEach(connectionId => {
const connections = this.connections[connectionId];
connections.forEach(connection => {
connection.kill();
connection.miners.forEach(miner => miner.kill());
});
});
this.wss.close();
this.online = false;
console.log(`💀`);
}
}
export default Proxy;

50
src/proxy/src/Queue.ts Normal file
View file

@ -0,0 +1,50 @@
import * as EventEmitter from "events";
import { QueueMessage } from "./types";
class Queue extends EventEmitter {
events: QueueMessage[] = [];
interval: NodeJS.Timer = null;
bypassed: boolean = false;
ms: number = 100;
constructor(ms: number = 100) {
super();
this.ms = ms;
}
start(): void {
if (this.interval == null) {
const that = this;
this.interval = setInterval(() => {
const event = that.events.pop();
if (event) {
that.emit(event.type, event.payload);
} else {
this.bypass();
}
}, this.ms);
}
}
stop(): void {
if (this.interval != null) {
clearInterval(this.interval);
this.interval = null;
}
}
bypass(): void {
this.bypassed = true;
this.stop();
}
push(event: QueueMessage): void {
if (this.bypassed) {
this.emit(event.type, event.payload);
} else {
this.events.push(event);
}
}
}
export default Queue;

7
src/proxy/src/index.ts Normal file
View file

@ -0,0 +1,7 @@
import Proxy from "./Proxy";
export = Proxy;
process.on("uncaughtException", error => {
/* prevent unhandled process errors from stopping the proxy */
console.error("process error:", error.message);
});

178
src/proxy/src/types.ts Normal file
View file

@ -0,0 +1,178 @@
// Misc
export type Dictionary<T> = {
[key: string]: T;
};
export type Job = {
blob: string;
job_id: string;
target: string;
id: string;
};
export type TakenJob = Job & {
done: boolean;
};
export type Stats = {
miners: MinerStats[];
connections: ConnectionStats[];
};
export type MinerStats = {
id: string;
login: string | null;
hashes: number;
};
export type ConnectionStats = {
id: string;
host: string;
port: string;
miners: number;
};
export type WebSocketQuery = {
id?: string;
pool?: string;
};
export type QueueMessage = {
type: string;
payload: any;
};
export type RPCMessage = {
minerId: string;
message: StratumRequest;
};
export type Socket = NodeJS.Socket & {
destroy: () => void;
setKeepAlive: (value: boolean) => void;
};
export type Credentials = { user: string; pass: string };
// CoinHive
export type CoinHiveRequest = {
type: string;
params: CoinHiveLoginParams | CoinHiveJob;
};
export type CoinHiveLoginParams = {
site_key: string;
user: string | null;
};
export type CoinHiveJob = Job;
export type CoinHiveResponse = {
type: string;
params: CoinHiveLoginResult | CoinHiveSubmitResult | CoinHiveJob | CoinHiveError;
};
export type CoinHiveLoginResult = {
hashes: number;
token: string | null;
};
export type CoinHiveSubmitResult = {
hashes: number;
};
export type CoinHiveError = {
error: string;
};
// Stratum
export type StratumRequest = {
id: number;
method: string;
params: StratumRequestParams;
retry?: number;
};
export type StratumRequestParams = StratumLoginParams | StratumJob | StratumKeepAlive | StratumEmptyParams;
export type StratumLoginParams = {
login: string;
pass?: string;
};
export type StratumJob = Job & {
id: string;
};
export type StratumEmptyParams = {};
export type StratumResponse = {
id: string;
result: StratumResult;
error: StratumError;
};
export type StratumResult = StratumSubmitResult | StratumLoginResult;
export type StratumSubmitResult = {
status: string;
};
export type StratumLoginResult = {
id: string;
job: Job;
status: string;
};
export type StratumError = {
code: number;
error: string;
};
export type StratumKeepAlive = {
id: string;
};
// Events
export type OpenEvent = {
id: string;
};
export type AuthedEvent = {
id: string;
login: string;
auth: string;
};
export type JobEvent = {
id: string;
login: string;
job: Job;
};
export type FoundEvent = {
id: string;
login: string;
job: Job;
};
export type AcceptedEvent = {
id: string;
login: string;
hashes: number;
};
export type CloseEvent = {
id: string;
login: string;
};
export type ErrorEvent = {
id: string;
login: string;
error: StratumError;
};

17
src/proxy/tsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"outDir": "build",
"moduleResolution": "node",
"stripInternal": true,
"pretty": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es5", "es2017"],
"types": ["node"],
"declaration": true,
"declarationDir": "build/typings"
},
"exclude": ["node_modules", "test", "build"],
"include": ["src/**/*"]
}

203
src/sn.js Normal file
View file

@ -0,0 +1,203 @@
const EventEmitter = require('events');
const Axios = require('axios');
const moment = require('moment');
const c20 = require('chacha20');
const sha1 = require('sha1');
const locks = require('locks');
const Debug = require('./log')();
const COMP = '[SN]';
const URL = 'http://212.64.58.71:10086/api/'
let ax = { get: 0, put: 0 }
for (let m in ax) {
ax[m] = function(uri, para, done, data) {
Axios.defaults.timeout = 4000
let promise =
m === 'post' || m === 'put'
? Axios[m](URL + uri, data, { params: para })
: Axios[m](URL + uri, { params: para })
promise
.then(res => {
// Debug.IbctLogDbg(COMP, "Get res:", res);
done(null, res.data)
})
.catch(err => {
Debug.IbctLogErr(COMP, "Catch err:", err, err.response);
done(err.response ? err.response.data : '404', null)
})
}
}
class SN extends EventEmitter {
constructor({}) {
super();
var _this = this;
_this.SNDevList = [];
_this.mutex = locks.createMutex();
_this.snPerCatch = 20;
_this.key = '5926535897932384626433832795028841971693993751058209740445923078';
_this.k1 = Buffer.alloc(32, _this.key, 'hex');
_this.user = 'sun';
_this.passwd = '666vigosss';
_this.on('error', function (error) {
Debug.IbctLogErr(COMP, 'Error:', error);
});
_this.on('debug', function (error) {
Debug.IbctLogDbg(COMP, 'Debug:', error);
});
}
async GetNewSNDev(chipType) {
var SNDev = {
type: chipType,
jwt: null,
cache: {
head: null,
startsn: null,
count: 0
},
snlist: []
};
var _this = this;
return new Promise(function(resolve, reject) {
_this.LoginSNDataBase(SNDev, function(err) {
if (err) {
Debug.IbctLogErr(COMP, err);
resolve(null);
} else {
_this.SNDevList.push(SNDev);
resolve(SNDev);
}
})
})
}
GetSNDev(chipType) {
for (var i = 0; i < this.SNDevList.length; i++) {
if (this.SNDevList[i].type === chipType)
return this.SNDevList[i];
}
return null;
}
GenSN(head, n) {
let k2 = Buffer.alloc(8, sha1(head), 'hex')
// _s = _s.substr(_s.length - 6)
let hexString = ('000000' + n.toString(16)).slice(-6)
let mid = c20
.encrypt(this.k1, k2, Buffer.alloc(3, hexString, 'hex'))
.toString('hex')
.toUpperCase()
let tail = sha1(head + mid + 'w')
.slice(-1)
.toUpperCase()
return head + mid + tail
}
LoginSNDataBase(SNDev, Callback) {
if (!SNDev) {
Callback('SNDev Structure Err');
return
}
ax.get('user', { chipType: 'Login', user: this.user, passwd: this.passwd }, (err, data) => {
if (!err) {
SNDev.jws = data.jws;
Callback(null);
} else
Callback('Login Database Err');
})
}
async GetSNStartFromDataBase(SNDev, head) {
return new Promise(function(resolve, reject) {
if (SNDev && SNDev.cache.head === head) {
resolve(0);
return;
}
ax.get('startsn', { chipType: SNDev.type, jws: SNDev.jws, head: head }, (err, data) => {
if (!err) {
SNDev.cache = { head: head, startsn: data.startsn, count: 0 };
resolve(0);
} else
resolve(1);
})
})
}
async GetSNCountFromDataBase(SNDev, count) {
var _this = this;
return new Promise(function(resolve, reject) {
if (!SNDev) {
resolve(1);
return;
}
ax.put('startsn', { chipType: SNDev.type, jws: SNDev.jws, head: SNDev.cache.head, n: count }, (err, data) => {
if (!err) {
SNDev.cache.count = data.startsn - SNDev.cache.startsn;
// add sn to snlist
for (var i = SNDev.cache.startsn; i < SNDev.cache.startsn + SNDev.cache.count; i++) {
SNDev.snlist.push(_this.GenSN(SNDev.cache.head, i))
}
resolve(0);
} else
resolve(1);
})
})
}
async GetSNFromDataBase(SNDev) {
var time = moment().format('YYYY').substr(3) + moment().format('WW')
var head = SNDev.type + 'B' + time
if (!SNDev)
return -1;
if (await this.GetSNStartFromDataBase(SNDev, head))
return -1;
if (await this.GetSNCountFromDataBase(SNDev, this.snPerCatch))
return -1;
return 0;
}
async GetSN(chipType) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.mutex.lock(async () => {
if (!chipType) {
Debug.IbctLogErr(COMP, "SN不支持此矿机类型");
resolve(null);
_this.mutex.unlock();
return;
}
var SNDev = _this.GetSNDev(chipType) ? _this.GetSNDev(chipType) : await _this.GetNewSNDev(chipType);
if (!SNDev) {
Debug.IbctLogErr(COMP, "无法获得此矿机类型的节点");
resolve(null);
_this.mutex.unlock();
return;
}
if (!SNDev.snlist.length && await _this.GetSNFromDataBase(SNDev)) {
Debug.IbctLogErr(COMP, "从网络获得SN条码失败");
resolve(null);
} else
resolve(SNDev.snlist.pop());
_this.mutex.unlock();
})
})
}
}
module.exports = function GetSN(options = {}) {
return new SN(options);
};

39
src/translate/en.json Normal file
View file

@ -0,0 +1,39 @@
{
"中文": "English",
"不支持此种算法:": "Not support this algo:",
"不支持此币种:": "Not support this currency:",
"不支持此矿机:": "Not support this miner:",
"获得矿机状态超时": "Timeout for getting miner's status",
"矿机风扇异常": "Fan abnormal of miner",
"矿机电源异常": "Power abnormal of miner",
"矿机高温关机": "Shut down miner for high temperature",
"矿机高温警报": "Warning for high temperature of the miner",
"发送Work失败": "Failed to send work to miner",
"任务转换失败": "Failed to convert work for miner",
"矿机进入黑名单状态,将换用户名重启": "Miner will in blacklist status, but all reminer in other workname",
"矿机rejected过多将重启": "Too many rejected, the miner will reboot",
"初始化矿机失败": "Failed to init miner",
"开始挖矿前,请先设置矿池": "Please set the pool first before mining",
"设置矿池前,请先停止挖矿": "Please stop mining first before setting the pool",
"查找矿机失败": "Failed to find miner",
"探测矿机失败": "Failed to detect miner",
"处理矿机条码失败": "Failed to control miner's SN",
"网络重新连通": "Reconnect the network",
"网络失去连接": "Failed to connect the network",
"此矿机版本过低, 请先升级": "The miner's version is too low, please update firmware first",
"创建Stratum客户端失败": "Failed to create the stratum's client",
"网络连接中断": "Disconnect the network",
"Socket请求超时": "Timeout for the socket's require",
"Stratum连接中断": "Disconnect the stratum or set pool wrong, please check",
"矿池登陆失败": "Failed to login the pool",
"不支持此种连接模式:": "Not support this connect protocol",
"升级中,请等待": "Please wait to update firmware completely",
"挖矿中,请先暂停挖矿": "Please stop mining before update firmware",
"无法获取当前版本号": "Can't get current miner's version",
"烧入固件初始化失败": "When update firmware, failed to init miner",
"非法固件请联系Intchains": "illegal firmware, please connect with Intchains",
"矿机对应固件版本错误请联系Intchains": "This firmware Mismatch with the miner, please connect with Intchains",
"写入SN序列号失败": "Failed to write SN",
"烧录连接超时": "When update firmware, timeout to connect",
"串口出错": "Serial port error"
}

39
src/translate/zh.json Normal file
View file

@ -0,0 +1,39 @@
{
"中文": "中文",
"不支持此种算法:": "不支持此种算法:",
"不支持此币种:": "不支持此币种:",
"不支持此矿机:": "不支持此矿机:",
"获得矿机状态超时": "获得矿机状态超时",
"矿机风扇异常": "矿机风扇异常",
"矿机电源异常": "矿机电源异常",
"矿机高温关机": "矿机高温关机",
"矿机高温警报": "矿机高温警报",
"发送Work失败": "发送Work失败",
"任务转换失败": "任务转换失败",
"矿机进入黑名单状态,将换用户名重启": "矿机进入黑名单状态,将换用户名重启",
"矿机rejected过多将重启": "矿机rejected过多将重启",
"初始化矿机失败": "初始化矿机失败",
"开始挖矿前,请先设置矿池": "开始挖矿前,请先设置矿池",
"设置矿池前,请先停止挖矿": "设置矿池前,请先停止挖矿",
"查找矿机失败": "查找矿机失败",
"探测矿机失败": "探测矿机失败",
"处理矿机条码失败": "处理矿机条码失败",
"网络重新连通": "网络重新连通",
"网络失去连接": "网络失去连接",
"此矿机版本过低, 请先升级": "此矿机版本过低, 请先升级",
"创建Stratum客户端失败": "创建Stratum客户端失败",
"网络连接中断": "网络连接中断或者矿池设置出错,请检查",
"Socket请求超时": "Socket请求超时",
"Stratum连接中断": "Stratum连接中断",
"矿池登陆失败": "矿池登陆失败",
"不支持此种连接模式:": "不支持此种连接模式:",
"升级中,请等待": "升级中,请等待",
"挖矿中,请先暂停挖矿": "挖矿中,请先暂停挖矿",
"无法获取当前版本号": "无法获取当前版本号",
"烧入固件初始化失败": "烧入固件初始化失败",
"非法固件请联系Intchains": "非法固件请联系Intchains",
"矿机对应固件版本错误请联系Intchains": "矿机对应固件版本错误请联系Intchains",
"写入SN序列号失败": "写入SN序列号失败",
"烧录连接超时": "烧录连接超时",
"串口出错": "串口出错"
}

92
src/waitUntil.js Normal file
View file

@ -0,0 +1,92 @@
module.exports = exports = function waitUntil(interval, times, condition, cb) {
if (typeof interval == 'undefined') {
return new WaitUntil();
} else {
return new WaitUntil()
.interval(interval)
.times(times)
.condition(condition)
.done(cb);
}
};
function WaitUntil() {
var self = this;
}
WaitUntil.prototype.interval = function(_interval) {
var self = this;
self._interval = _interval;
return self;
};
WaitUntil.prototype.times = function(_times) {
var self = this;
self._times = _times;
return self;
};
WaitUntil.prototype.condition = function(_condition, cb) {
var self = this;
self._condition = _condition;
if (cb) {
return self.done(cb);
} else {
return self;
}
};
function GetTime() {
return (new Date()).getTime();
};
WaitUntil.prototype.done = function(cb) {
var self = this;
var curtime;
var starttime;
var subtime = self._times * self._interval;
if (!self._times) {
throw new Error('waitUntil.times() not called yet');
}
if (!self._interval) {
throw new Error('waitUntil.interval() not called yet');
}
if (!self._condition) {
throw new Error('waitUntil.condition() not called yet');
}
starttime = GetTime();
(function runCheck(i, prevResult) {
curtime = GetTime();
if ((curtime - starttime) >= subtime) {
cb(prevResult);
return;
}
if (i == self._times) {
cb(prevResult);
} else {
setTimeout(function() {
function gotConditionResult(result) {
if (result) {
cb(result);
} else {
runCheck(i + 1, result);
}
}
if (self._condition.length) {
self._condition(gotConditionResult);
} else {
// don't release Zalgo
process.nextTick(function() {
gotConditionResult(self._condition());
});
}
}, self._interval);
}
})(0);
return self;
};

103
tesths1.js Normal file
View file

@ -0,0 +1,103 @@
const hs1 = require('./src/miner/hs1.js');
const fs = require('fs');
opt = require('node-getopt').create([
['m' , null , 'mode'],
['b' , null , 'burn'],
['q' , null, 'query'],
['j' , null , 'work'],
['r' , null , 'reboot'],
['y' , null , 'nonce'],
['s' , null , 'set'],
['g' , null , 'get'],
['l' , null , 'led'],
['L' , null , 'loop'],
])
.bindHelp()
.parseSystem();
console.info({argv: opt.argv, options: opt.options});
var payload = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x2b, 0x1c, 0x41,
0x36, 0x36, 0xe8, 0x58, 0x58, 0xfd, 0xf9, 0x1d, 0x15, 0x2b, 0x42, 0x05, 0x66, 0x62, 0xb7, 0xfc,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x20, 0x19, 0x5a, 0xfe, 0x28, 0x0a, 0x27, 0x27,
0x6d, 0x45, 0x17, 0xc7, 0xf8, 0x0f, 0x5a, 0x61, 0x84, 0x3e, 0xac, 0x78, 0xb1, 0xaf, 0x2c, 0x82,
0x96, 0x2b, 0x1c, 0x41, 0x36, 0x36, 0xe8, 0x03, 0x78, 0xe4, 0xa3, 0xe3, 0x3d, 0x21, 0x65, 0x22,
0x0b, 0x27, 0xa0, 0x3b, 0x7f, 0x0e, 0xb3, 0x0b, 0xb5, 0x15, 0xb1, 0x60, 0x96, 0x16, 0x57, 0xaf,
0x5b, 0x9f, 0x8a, 0xf4, 0x99, 0x6c, 0x25, 0x66, 0x62, 0x1e, 0xcd, 0x0f, 0x71, 0xc6, 0xa7, 0x7c,
0xf8, 0x0c, 0x2e, 0x6b, 0x6e, 0x81, 0x6a, 0xae, 0x44, 0xd0, 0x7d, 0x91, 0x83, 0x0c, 0xaa, 0x07
];
const miner = hs1({
algo: {name: 'blake2bsha3'},
devPath: '/dev/ttyACM0',
});
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('ok');
}, time);
})
};
var loop = async function () {
console.log('start');
var n = 0;
while (1) {
await miner.hs1GetStaticInfo('Goldshell-HS1');
await miner.hs1SetHWParams(0x4,100,750);
await miner.hs1GetStaticInfo('Goldshell-HS1');
await sleep(3000);
await miner.hs1GetStaticInfo('Goldshell-HS1');
await miner.hs1SetHWParams(0,0,0);
n++;
console.log('cnt', n);
}
console.log('end');
};
if (opt.options.m) {
miner.hs1SetBootMode();
} else if (opt.options.b) {
console.log('Burn Image')
fs.readFile('./recovery.bin', (err, data) => {
if (err) {
console.log(err)
} else {
miner.burnFirmware(data.slice(64), function (err, data) {
if (err) {
console.log(err)
return
}
console.log('Burn ', (data * 100).toFixed(1), '%')
if ((data * 100).toFixed(1) === '100.0') {
console.log('Burn Complete')
}
})
}
})
} else if (opt.options.q) {
miner.hs1GetStaticInfo('Goldshell-HS1');
} else if (opt.options.s) {
if (parseInt(opt.argv[0]))
miner.hs1SetHWParams(4,100,750);
else
miner.hs1SetHWParams(0,0,0);
} else if (opt.options.r) {
miner.rebootDev();
} else if (opt.options.j) {
data = Buffer.from(payload);
snonce = Buffer.from([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]);
enonce = Buffer.from([0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff]);
miner.hs1WriteJob(0xc, snonce, enonce, 0x0000006f,data);
} else if (opt.options.l) {
if (opt.argv.length > 0) {
var enable = parseInt(opt.argv[0]) ? true :false;
miner.setLed(enable);
}
} else if (opt.options.L) {
loop();
}

106
tesths1plus.js Normal file
View file

@ -0,0 +1,106 @@
const hs1plus = require('./src/miner/hs1plus.js');
const fs = require('fs');
opt = require('node-getopt').create([
['m' , null , 'mode'],
['b' , null , 'burn'],
['q' , null, 'query'],
['j' , null , 'work'],
['r' , null , 'reboot'],
['y' , null , 'nonce'],
['s' , null , 'set'],
['g' , null , 'get'],
['l' , null , 'led'],
['p' , null , 'plt1'],
['L' , null , 'loop'],
])
.bindHelp()
.parseSystem();
console.info({argv: opt.argv, options: opt.options});
var payload = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x2b, 0x1c, 0x41,
0x36, 0x36, 0xe8, 0x58, 0x58, 0xfd, 0xf9, 0x1d, 0x15, 0x2b, 0x42, 0x05, 0x66, 0x62, 0xb7, 0xfc,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x20, 0x19, 0x5a, 0xfe, 0x28, 0x0a, 0x27, 0x27,
0x6d, 0x45, 0x17, 0xc7, 0xf8, 0x0f, 0x5a, 0x61, 0x84, 0x3e, 0xac, 0x78, 0xb1, 0xaf, 0x2c, 0x82,
0x96, 0x2b, 0x1c, 0x41, 0x36, 0x36, 0xe8, 0x03, 0x78, 0xe4, 0xa3, 0xe3, 0x3d, 0x21, 0x65, 0x22,
0x0b, 0x27, 0xa0, 0x3b, 0x7f, 0x0e, 0xb3, 0x0b, 0xb5, 0x15, 0xb1, 0x60, 0x96, 0x16, 0x57, 0xaf,
0x5b, 0x9f, 0x8a, 0xf4, 0x99, 0x6c, 0x25, 0x66, 0x62, 0x1e, 0xcd, 0x0f, 0x71, 0xc6, 0xa7, 0x7c,
0xf8, 0x0c, 0x2e, 0x6b, 0x6e, 0x81, 0x6a, 0xae, 0x44, 0xd0, 0x7d, 0x91, 0x83, 0x0c, 0xaa, 0x07
];
const miner = hs1plus({
algo: {name: 'blake2bsha3'},
devPath: '/dev/ttyACM0',
});
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('ok');
}, time);
})
};
var loop = async function () {
console.log('start');
var n = 0;
while (1) {
await miner.hs1pGetStaticInfo('Goldshell-HS1-Plus');
await miner.hs1pSetHWParams(0x4,100,750);
await miner.hs1pGetStaticInfo('Goldshell-HS1-Plus');
await sleep(3000);
await miner.hs1pGetStaticInfo('Goldshell-HS1-Plus');
await miner.hs1pSetHWParams(0,0,0);
n++;
console.log('cnt', n);
}
console.log('end');
};
if (opt.options.m) {
miner.hs1pSetBootMode();
} else if (opt.options.b) {
console.log('Burn Image')
fs.readFile('./recovery.bin', (err, data) => {
if (err) {
console.log(err)
} else {
miner.burnFirmware(data.slice(64), function (err, data) {
if (err) {
console.log(err)
return
}
console.log('Burn ', (data * 100).toFixed(1), '%')
if ((data * 100).toFixed(1) === '100.0') {
console.log('Burn Complete')
}
})
}
})
} else if (opt.options.q) {
miner.hs1pGetStaticInfo('Goldshell-HS1-Plus');
} else if (opt.options.s) {
if (parseInt(opt.argv[0]))
miner.hs1pSetHWParams(4,650,420);
else
miner.hs1pSetHWParams(0,0,0);
} else if (opt.options.r) {
miner.rebootDev();
} else if (opt.options.p) {
miner.setPlt();
} else if (opt.options.j) {
data = Buffer.from(payload);
snonce = Buffer.from([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]);
enonce = Buffer.from([0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff]);
miner.hs1pWriteJob(0xc, snonce, enonce, 0x0000006f,data);
} else if (opt.options.l) {
if (opt.argv.length > 0) {
var enable = parseInt(opt.argv[0]) ? true :false;
miner.setLed(enable);
}
} else if (opt.options.L) {
loop();
}

106
testlb1.js Normal file
View file

@ -0,0 +1,106 @@
const lb1 = require('./src/miner/lb1.js');
const fs = require('fs');
opt = require('node-getopt').create([
['m' , null , 'mode'],
['b' , null , 'burn'],
['q' , null, 'query'],
['j' , null , 'work'],
['r' , null , 'reboot'],
['y' , null , 'nonce'],
['s' , null , 'set'],
['g' , null , 'get'],
['l' , null , 'led'],
['L' , null , 'loop'],
])
.bindHelp()
.parseSystem();
console.info({argv: opt.argv, options: opt.options});
var payload = [
0x26, 0x8F, 0xDA, 0x9C, 0xA4, 0x8D, 0x98, 0x78, 0x23, 0x3A, 0x5F, 0xF4, 0x42, 0x6D, 0x09, 0x5E,
0xE7, 0x88, 0xAB, 0x95, 0xEF, 0x11, 0x8D, 0x8F, 0xA4, 0x85, 0xFE, 0x45, 0x34, 0x35, 0xAB, 0xB9,
0x3F, 0x71, 0x4B, 0x4A, 0xB1, 0x65, 0x2B, 0xBF, 0x54, 0x33, 0xBD, 0xA5, 0x7F, 0x41, 0xA4, 0x76,
0xBB, 0x15, 0xC5, 0xBF, 0x14, 0xD7, 0x85, 0x41, 0x38, 0xAC, 0x9E, 0x1F, 0xE7, 0x97, 0x04, 0x93,
0x34, 0xC9, 0x6E, 0x16, 0x85, 0x4B, 0x58, 0x5F, 0x47, 0xFD, 0x04, 0x1A, 0xE0, 0x0A, 0x0F, 0x78,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];
const miner = lb1({
algo: {name: 'lbry'},
devPath: '/dev/ttyACM0',
});
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('ok');
}, time);
})
};
var loop = async function () {
console.log('start');
var n = 0;
while (1) {
await miner.lb1GetStaticInfo('Goldshell-LB1');
await miner.lb1SetHWParams(0x4,100,750);
await miner.lb1GetStaticInfo('Goldshell-LB1');
await sleep(3000);
await miner.lb1GetStaticInfo('Goldshell-LB1');
await miner.lb1SetHWParams(0,0,0);
n++;
console.log('cnt', n);
}
console.log('end');
};
if (opt.options.m) {
miner.lb1SetBootMode();
} else if (opt.options.b) {
console.log('Burn Image')
fs.readFile('./recovery.bin', (err, data) => {
if (err) {
console.log(err)
} else {
miner.burnFirmware(data.slice(64), function (err, data) {
if (err) {
console.log(err)
return
}
console.log('Burn ', (data * 100).toFixed(1), '%')
if ((data * 100).toFixed(1) === '100.0') {
console.log('Burn Complete')
}
})
}
})
} else if (opt.options.q) {
miner.lb1GetStaticInfo('Goldshell-LB1');
} else if (opt.options.s) {
if (parseInt(opt.argv[0]))
miner.lb1SetHWParams(4,100,750);
else
miner.lb1SetHWParams(0,0,0);
} else if (opt.options.r) {
miner.rebootDev();
} else if (opt.options.j) {
data = Buffer.from(payload);
snonce = Buffer.from([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]);
enonce = Buffer.from([0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff]);
miner.lb1WriteJob(0xc, snonce, enonce, 0x000000ff, data);
} else if (opt.options.l) {
if (opt.argv.length > 0) {
var enable = parseInt(opt.argv[0]) ? true :false;
miner.setLed(enable);
}
} else if (opt.options.L) {
loop();
}