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