@@ -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; | |||
} | |||
} |
@@ -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; |
@@ -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; |
@@ -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); |
@@ -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; |
@@ -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); |