initial
This commit is contained in:
parent
8d38611b7c
commit
4a37c92f11
70 changed files with 12999 additions and 0 deletions
19
LICENSE
Normal file
19
LICENSE
Normal 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
25
config.json
Normal 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
84
dashboard.js
Normal 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
250
entrance.js
Normal 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
25
help
Normal 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
103
package.json
Normal 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
52
src/algo/algo.js
Normal 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
28
src/algo/base/assert.js
Normal 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
356
src/algo/base/blake2b.js
Normal 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
118
src/algo/base/hmac.js
Normal 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
427
src/algo/base/keccak.js
Normal 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
71
src/algo/base/sha3.js
Normal 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
227
src/algo/base/sha3hns.js
Normal 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;
|
39
src/algo/blake2bsha3Api.js
Normal file
39
src/algo/blake2bsha3Api.js
Normal 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
22
src/algo/lbryApi.js
Normal 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);
|
||||||
|
};
|
99
src/cryptocurrency/cryptocurrency.js
Normal file
99
src/cryptocurrency/cryptocurrency.js
Normal 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
226
src/cryptocurrency/hns.js
Normal 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
280
src/cryptocurrency/lbc.js
Normal 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
171
src/detect.js
Normal 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
113
src/index.js
Normal 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
111
src/log.js
Normal 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
1352
src/miner.js
Normal file
File diff suppressed because it is too large
Load diff
26
src/miner/config/minerconfig.js
Normal file
26
src/miner/config/minerconfig.js
Normal 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
129
src/miner/cpu.js
Normal 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, '; Miner:CPU; 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
873
src/miner/hs1.js
Normal 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
909
src/miner/hs1plus.js
Normal 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
922
src/miner/lb1.js
Normal 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
139
src/miner/minerApi.js
Normal 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
318
src/miner/unknow.js
Normal 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
74
src/ping.js
Normal 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
275
src/protocol/HnsStratum.js
Normal 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
283
src/protocol/LbcStratum.js
Normal 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
72
src/protocol/jsonrpc.js
Normal 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
83
src/protocol/protocol.js
Normal 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);
|
||||||
|
};
|
339
src/protocol/stratum/LICENSE
Normal file
339
src/protocol/stratum/LICENSE
Normal 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.
|
82
src/protocol/stratum/examples/client.js
Normal file
82
src/protocol/stratum/examples/client.js
Normal 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(){ });
|
||||||
|
;
|
11
src/protocol/stratum/lib/base.js
Normal file
11
src/protocol/stratum/lib/base.js
Normal 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);
|
335
src/protocol/stratum/lib/client.js
Normal file
335
src/protocol/stratum/lib/client.js
Normal 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;
|
||||||
|
};
|
24
src/protocol/stratum/lib/index.js
Normal file
24
src/protocol/stratum/lib/index.js
Normal 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;
|
175
src/protocol/stratum/lib/rpc.js
Normal file
175
src/protocol/stratum/lib/rpc.js
Normal 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;
|
||||||
|
};
|
630
src/protocol/stratum/lib/server.js
Normal file
630
src/protocol/stratum/lib/server.js
Normal 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;
|
||||||
|
};
|
61
src/protocol/stratum/package.json
Normal file
61
src/protocol/stratum/package.json
Normal 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
4
src/proxy/app.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "CHS",
|
||||||
|
"repository": "https://github.com/cazala/coin-hive-stratum"
|
||||||
|
}
|
62
src/proxy/bin/coin-hive-stratum
Normal file
62
src/proxy/bin/coin-hive-stratum
Normal 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
19
src/proxy/bin/help
Normal 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)
|
388
src/proxy/build/Connection.js
Normal file
388
src/proxy/build/Connection.js
Normal 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;
|
18
src/proxy/build/Metrics.js
Normal file
18
src/proxy/build/Metrics.js
Normal 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
303
src/proxy/build/Miner.js
Normal 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
141
src/proxy/build/Proxy.js
Normal 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
63
src/proxy/build/Queue.js
Normal 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
9
src/proxy/build/index.js
Normal 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
3
src/proxy/build/types.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"use strict";
|
||||||
|
// Misc
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
11
src/proxy/config/defaults.js
Normal file
11
src/proxy/config/defaults.js
Normal 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
218
src/proxy/package.json
Normal 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
10
src/proxy/server.js
Normal 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
275
src/proxy/src/Connection.ts
Normal 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
19
src/proxy/src/Metrics.ts
Normal 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
248
src/proxy/src/Miner.ts
Normal 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
310
src/proxy/src/Proxy.ts
Normal 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
50
src/proxy/src/Queue.ts
Normal 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
7
src/proxy/src/index.ts
Normal 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
178
src/proxy/src/types.ts
Normal 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
17
src/proxy/tsconfig.json
Normal 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
203
src/sn.js
Normal 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
39
src/translate/en.json
Normal 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
39
src/translate/zh.json
Normal 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
92
src/waitUntil.js
Normal 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
103
tesths1.js
Normal 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
106
tesths1plus.js
Normal 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
106
testlb1.js
Normal 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();
|
||||||
|
}
|
Loading…
Reference in a new issue