Browse Source

CPU miner

master
mortie 6 years ago
parent
commit
54afd4e436
6 changed files with 314 additions and 43 deletions
  1. 76
    0
      js/cryptutil.js
  2. 2
    2
      js/miner-asic.js
  3. 142
    0
      js/miner-cpu/index.js
  4. 54
    0
      js/miner-cpu/mining-process.js
  5. 33
    37
      js/stratum-client.js
  6. 7
    4
      script.js

+ 76
- 0
js/cryptutil.js View File

@@ -0,0 +1,76 @@
var crypto = require("crypto");

exports.createCoinbase = createCoinbase;
exports.createCbHash = createCbHash;
exports.buildMerkleRoot = buildMerkleRoot;
exports.doublesha = doublesha;
exports.incbufBE = incbufBE;

/* Create a coinbase buffer.
*
* Args:
* cb1, cb2: Buffer, coinbase parts 1 and 2
* ex1, ex2: Buffer, extranounce 1 and 2
*
* Returns:
* Buffer, a complete coinbase
*/
function createCoinbase(ex1, ex2, cb1, cb2) {
return Buffer.concat([ cb1, ex1, ex2, cb2 ]);
}

/* Create a coinbase hash.
*
* Args:
* coinbase: Buffer, the coinbase
*
* Returns:
* Buffer, the cbHashBin
*/
function createCbHash(coinbase) {
return doublesha(coinbase);
}

/* Build a merkle root from a merkle branch and a coinbase hash.
*
* Returns:
* Buffer, the merkle root
*
* Args:
* merkleBranch: Array, hex encoded hashes
* cbHash: Buffer, the coinbase hash
*/
function buildMerkleRoot(merkleBranch, cbHash) {
var root = cbHashBin
for (var i in merkleBranch) {
var h = Buffer.from(merkleBranch[i], "hex");
root = doublesha(Buffer.concat(root, h));
}

return root;
}

/* Run sha256 twice on a buffer.
*
* Returns:
* Buffer, the double-sha256'd buffer
*
* Args:
* buf: Buffer, will be double-sha256'd
*/
function doublesha(buf) {
var tmp = crypto.createHash("sha256").update(buf).digest();
return crypto.createHash("sha256").update(tmp).digest();
}

/* Increment a buffer.
*
* Args:
* buf: Buffer, will be incremented.
*/
function incbufBE(buf) {
for (var i = buf.length - 1; i >= 0; --i) {
if (buf[i]++ !== 255)
break;
}
}

js/asicconn.js → js/miner-asic.js View File

@@ -1,6 +1,6 @@
var SerialPort = require("serialport");

class ASICConn {
class AsicMiner {
constructor(dev, baud) {
this.ready = false;
this.error = null;
@@ -34,4 +34,4 @@ class ASICConn {
}
}

module.exports = ASICConn;
module.exports = AsicMiner;

+ 142
- 0
js/miner-cpu/index.js View File

@@ -0,0 +1,142 @@
var spawn = require("child_process").spawn;

function hps(n) {
var suffix = "Hash/s";
if (n > 1000000) {
n = n / 1000000;
suffix ="M"+suffix;
} else if (n > 1000) {
n = n / 1000;
suffix = "k"+suffix;
}

return (n.toFixed(2))+" "+suffix;
}

function mineProc(cb1, cb2, merk, dif, en1, en2, cnt) {
var obj = {
coinb1: cb1, coinb2: cb2, merkleBranch: merk,
difficulty: dif, exnounce1: en1, exnounce2: en2, iters: cnt
};

var child = spawn(
"node", [ __dirname+"/mining-process.js", JSON.stringify(obj) ]);

child.output = "";
child.hps = 0;

child.stderr.on("data", d => process.stderr.write(d));
child.stdout.on("data", d => {
var s = d.toString();
if (s[0] === "o")
child.output = s.substr(1);
else if (s[0] === "h")
child.hps = parseInt(s.substr(1));
else
console.error("Warning: Unexpected child output,", s);
});

return child;
}

function mine(
coinb1, coinb2, merkleBranch,
difficulty, exnounce1, exnounce2_len) {

var cores = 4;

var max = Math.pow(2, exnounce2_len * 8) - 2;
var parts = Math.ceil(max / cores);


return new Promise((resolve, reject) => {
var childs = [];
var childsLeft = cores;
var inter = null;

function rej(hash) {
clearInterval(inter);
resolve(hash);
}

for (var i = 0; i < cores; ++i) {
var num = parts * i;
if (num + parts > max)
max - parts;
var buf = Buffer.alloc(exnounce2_len, '\0');
buf.writeUInt32BE(num);
var exnounce2 = buf.toString("hex");

var child = mineProc(
coinb1, coinb2, merkleBranch,
difficulty, exnounce1, exnounce2, parts);

var obj = {
coinb1, coinb2, merkleBranch,
difficulty, exnounce1, exnounce2_len
};

childs[i] = child;
}

childs.forEach(child => {
child.on("exit", code => {
console.error("Child exited with code", code);
if (code === 0) {
childsLeft -= 1;
childs.forEach(x => x !== child && x.kill());
rej(child.output.trim());
} else {
childsLeft -= 1;
if (childsLeft <= 0)
rej(false);
}
});
});

inter = setInterval(() => {
var sum = 0;
childs.forEach(c => sum += c.hps);
console.log(hps(sum));
}, 2000);
});
}

class CPUMiner {
constructor() {
this.work = null;
this.difficulty = 1;
this.exnounce1 = null;
this.exnounce2_len = null;
}

async startWork(work) {
if (this.exnounce1 == null) {
console.log("Ignoring work because extranounce is null.");
return false;
}

this.work = work;
return await mine(
this.work.coinb1,
this.work.coinb2,
this.work.berkleBranch,
this.difficulty,
this.exnounce1,
this.exnounce2_len);
}

setDifficulty(difficulty) {
this.difficulty = difficulty;
}

setNounce(en1, en2_len) {
this.exnounce1 = en1;
this.exnounce2_len = en2_len;
}

async wait() {
}
}

module.exports = CPUMiner;

+ 54
- 0
js/miner-cpu/mining-process.js View File

@@ -0,0 +1,54 @@
var cryptutil = require("../cryptutil");

var {
coinb1, coinb2, merkleBranch,
difficulty, exnounce1, exnounce2, iters
} = JSON.parse(process.argv[2]);

var start = exnounce2.toString("hex");

coinb1 = Buffer.from(coinb1, "hex");
coinb2 = Buffer.from(coinb2, "hex");
exnounce1 = Buffer.from(exnounce1, "hex");
exnounce2 = Buffer.from(exnounce2, "hex");

difficulty = 3;

var hashes = 0;
var sd = new Date().getTime();
for (var i = 0; i < iters; ++i) {
var coinbase =
cryptutil.createCoinbase(exnounce1, exnounce2, coinb1, coinb2);
var cbHash = cryptutil.createCbHash(coinbase);

var success = true;
for (var i = 0; i < difficulty; ++i) {
if (cbHash[i] !== 0) {
success = false;
break;
}
}

hashes += 1;
var d = new Date().getTime();
if (d - 2000 >= sd) {
console.log("h"+Math.floor(hashes / 2));

sd = d;
hashes = 0;
}

if (success) {
console.log("o"+cbHash.toString("hex"));
process.exit(0);
}

cryptutil.incbufBE(exnounce2);
if (exnounce2[0] == 255) {
console.error("exnounce2[0] reached 255");
process.exit(1);
}
}

console.error("iterated through "+iters);
process.exit(1);

js/miner.js → js/stratum-client.js View File

@@ -1,31 +1,36 @@
var RPCConn = require("./rpcconn");
var ASICConn = require("./asicconn");

class Miner {
constructor(ip, port, dev, baud) {
this.name = dev
this.work = null;
this.nextDifficulty = 1;
this.exnounce1 = null;
this.exnounce2_len = null;
class StratumClient {
constructor(ip, port, miner) {
this.name = ip+":"+port;

this.asic = new ASICConn(dev, baud);
this.waitFor = 2;
this.miner = miner;
this.rpc = new RPCConn(ip, port);
}

log(...msgs) {
process.stdout.write(this.name+": ");
console.log(msgs);
console.log.apply(console, msgs);
}

async startWork() {
this.log("Starting work");
var res;
try {
res = await this.miner.startWork(this.work);
} catch (err) {
console.trace(err);
return;
}
this.log("Work done,", res.toString("hex"));
}

async connect() {
await this.asic.wait();
await this.rpc.wait();
await this.miner.wait();

if (this.asic.error)
throw this.asic.error;

this.rpc.on("mining.notify", params => {
this.rpc.on("mining.notify", async params => {
var work = {
id: params[0], prevHash: params[1],
coinb1: params[2], coinb2: params[3],
@@ -33,39 +38,30 @@ class Miner {
nBits: params[6], nTime: params[7], cleanJobs: params[8],
};

this.notify(work);
this.work = work;
if (this.ready <= 0)
this.startWork();
});
this.rpc.on("mining.set_difficulty", params => {
this.log("difficulty", params);
this.difficulty(params[0]);
this.log("difficulty", params[0]);
this.miner.setDifficulty(params[0]);

if (--this.waitFor == 0)
this.startWork();
});

var sub = await this.rpc.call("mining.subscribe");
this.exnounce1 = sub[1];
this.exnounce2_len = sub[2];
this.miner.setNounce(sub[1], sub[2]);
if (--this.waitFor == 0)
this.startWork();
}

async auth(user, pass) {
var success = await this.rpc.call("mining.authorize", user, pass);
if (!success)
throw new Error("Incorrect username/password.");
console.log("Authenticated "+user+".");
}

notify(work) {
if (this.exnounce1 == null)
return console.log("Ignoring work because extranounce is null.");

this.log("Notification");
this.log(work);
this.work = work;
this.log(
"Using extranounce1 '"+this.exnounce1+
"', and exnounce2 length "+this.exnounce2_len);
}

difficulty(diff) {
this.nextDifficulty = diff;
console.log("Authenticated "+user+".");
}

writePayload() {
@@ -85,4 +81,4 @@ class Miner {
}
}

module.exports = Miner;
module.exports = StratumClient;

+ 7
- 4
script.js View File

@@ -1,7 +1,9 @@
#!/usr/bin/env node

var dns = require("dns");
var Miner = require("./js/miner");
var StratumClient = require("./js/stratum-client");
var CPUMiner = require("./js/miner-cpu");
var ASICMiner = require("./js/miner-asic");

var domain = "stratum.slushpool.com"
var port = 3333;
@@ -10,10 +12,11 @@ async function main() {
dns.lookup(domain, async (err, ip) => {
if (err) throw err;

var m = new Miner(ip, port, "/dev/ttyACM0", 115200);
var m = new CPUMiner();
var c = new StratumClient(ip, port, m);
try {
await m.connect();
await m.auth("mort96.worker1", "test");
await c.connect();
await c.auth("mort96.worker1", "test");
} catch (err) {
console.log(err);
process.exit(1);

Loading…
Cancel
Save