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; | |||||
} | |||||
} |
var SerialPort = require("serialport"); | var SerialPort = require("serialport"); | ||||
class ASICConn { | |||||
class AsicMiner { | |||||
constructor(dev, baud) { | constructor(dev, baud) { | ||||
this.ready = false; | this.ready = false; | ||||
this.error = null; | this.error = null; | ||||
} | } | ||||
} | } | ||||
module.exports = ASICConn; | |||||
module.exports = AsicMiner; |
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; |
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); |
var RPCConn = require("./rpcconn"); | 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); | this.rpc = new RPCConn(ip, port); | ||||
} | } | ||||
log(...msgs) { | log(...msgs) { | ||||
process.stdout.write(this.name+": "); | 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() { | async connect() { | ||||
await this.asic.wait(); | |||||
await this.rpc.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 = { | var work = { | ||||
id: params[0], prevHash: params[1], | id: params[0], prevHash: params[1], | ||||
coinb1: params[2], coinb2: params[3], | coinb1: params[2], coinb2: params[3], | ||||
nBits: params[6], nTime: params[7], cleanJobs: params[8], | 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.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"); | 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) { | async auth(user, pass) { | ||||
var success = await this.rpc.call("mining.authorize", user, pass); | var success = await this.rpc.call("mining.authorize", user, pass); | ||||
if (!success) | if (!success) | ||||
throw new Error("Incorrect username/password."); | 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() { | writePayload() { | ||||
} | } | ||||
} | } | ||||
module.exports = Miner; | |||||
module.exports = StratumClient; |
#!/usr/bin/env node | #!/usr/bin/env node | ||||
var dns = require("dns"); | 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 domain = "stratum.slushpool.com" | ||||
var port = 3333; | var port = 3333; | ||||
dns.lookup(domain, async (err, ip) => { | dns.lookup(domain, async (err, ip) => { | ||||
if (err) throw err; | 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 { | try { | ||||
await m.connect(); | |||||
await m.auth("mort96.worker1", "test"); | |||||
await c.connect(); | |||||
await c.auth("mort96.worker1", "test"); | |||||
} catch (err) { | } catch (err) { | ||||
console.log(err); | console.log(err); | ||||
process.exit(1); | process.exit(1); |