Skip to content

Commit

Permalink
Added dumb mode for SushiPool (#3)
Browse files Browse the repository at this point in the history
Version 2.0.0:
* Introduced dumb mode.
* Code refactoring.
  • Loading branch information
joewandy authored and tomkha committed Mar 30, 2019
1 parent d2e6d8c commit 56dbc52
Show file tree
Hide file tree
Showing 8 changed files with 403 additions and 48 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -2,4 +2,5 @@
/build
/peer-key
/miner.conf
dist/
dist/
.vscode/
5 changes: 5 additions & 0 deletions README.md
Expand Up @@ -44,6 +44,11 @@ host Pool server address
port Pool server port
Example: "port": "443"
Default: 443 [number]
consensus Consensus method used
Possible values are "dumb" or "nano"
Note that "dumb" mode (i.e. no consensus) only works with SushiPool.
Example: "consensus": "nano" [string]
name Device name to show in the dashboard [string]
Example: "name": "My Miner"
Expand Down
129 changes: 83 additions & 46 deletions index.js
@@ -1,70 +1,63 @@
const fs = require('fs');
const os = require('os');
const JSON5 = require('json5');
const pjson = require('./package.json');
const Nimiq = require('@nimiq/core');
const Utils = require('./src/Utils');
const NanoPoolMiner = require('./src/NanoPoolMiner');
const SushiPoolMiner = require('./src/SushiPoolMiner');
const crypto = require('crypto');
const Log = Nimiq.Log;

const TAG = 'SushiPoolMiner';
const TAG = 'Nimiq OpenCL Miner';
const $ = {};

Log.instance.level = 'info';

function humanHashrate(hashes) {
let thresh = 1000;
if (Math.abs(hashes) < thresh) {
return hashes + ' H/s';
}
let units = ['kH/s', 'MH/s', 'GH/s', 'TH/s', 'PH/s', 'EH/s', 'ZH/s', 'YH/s'];
let u = -1;
do {
hashes /= thresh;
++u;
} while (Math.abs(hashes) >= thresh && u < units.length - 1);
return hashes.toFixed(1) + ' ' + units[u];
const config = Utils.readConfigFile('./miner.conf');
if (!config) {
process.exit(1);
}

function readConfigFile(fileName) {
try {
const config = JSON5.parse(fs.readFileSync(fileName));
// TODO: Validate
return config;
} catch (e) {
Log.e(TAG, `Failed to read config file ${fileName}: ${e.message}`);
return false;
(async () => {
const address = config.address;
const deviceName = config.name || os.hostname();
const hashrate = (config.hashrate > 0) ? config.hashrate : 100; // 100 kH/s by default
const desiredSps = 5;
const startDifficulty = (1e3 * hashrate * desiredSps) / (1 << 16);
const minerVersion = 'OpenCL Miner ' + pjson.version;
const deviceData = { deviceName, startDifficulty, minerVersion };

Log.i(TAG, `Nimiq ${minerVersion} starting`);
Log.i(TAG, `- pool server = ${config.host}:${config.port}`);
Log.i(TAG, `- address = ${address}`);
Log.i(TAG, `- device name = ${deviceName}`);

// if not specified in the config file, defaults to dumb to make LTD happy :)
const consensusType = config.consensus || 'dumb';
const setup = { // can add other miner types here
'dumb': setupSushiPoolMiner,
'nano': setupNanoPoolMiner
}
}
const createMiner = setup[consensusType];
createMiner(address, config, deviceData);

const config = readConfigFile('./miner.conf');
if (!config) {
})().catch(e => {
console.error(e);
process.exit(1);
}
});

(async () => {
async function setupNanoPoolMiner(addr, config, deviceData) {
Log.i(TAG, `Setting up NanoPoolMiner`);

Nimiq.GenesisConfig.main();
const networkConfig = new Nimiq.DumbNetworkConfig();
$.consensus = await Nimiq.Consensus.nano(networkConfig);

$.blockchain = $.consensus.blockchain;
$.network = $.consensus.network;

const address = Nimiq.Address.fromUserFriendlyAddress(config.address);
const deviceName = config.name || os.hostname();
const deviceId = Nimiq.BasePoolMiner.generateDeviceId(networkConfig);
const hashrate = (config.hashrate > 0) ? config.hashrate : 100; // 100 kH/s by default
const desiredSps = 5;
const startDifficulty = (1e3 * hashrate * desiredSps) / (1 << 16);
const minerVersion = 'GPU Miner ' + pjson.version;
const deviceData = { deviceName, startDifficulty, minerVersion };

Log.i(TAG, `Sushipool ${minerVersion} starting`);
Log.i(TAG, `- pool server = ${config.host}:${config.port}`);
Log.i(TAG, `- address = ${address.toUserFriendlyAddress()}`);
Log.i(TAG, `- device name = ${deviceName}`);
Log.i(TAG, `- device id = ${deviceId}`);

const address = Nimiq.Address.fromUserFriendlyAddress(addr);
$.miner = new NanoPoolMiner($.blockchain, $.network.time, address, deviceId, deviceData,
config.devices, config.memory, config.threads);

Expand All @@ -73,7 +66,7 @@ if (!config) {
});
$.miner.on('hashrates-changed', hashrates => {
const totalHashRate = hashrates.reduce((a, b) => a + b, 0);
Log.i(TAG, `Hashrate: ${humanHashrate(totalHashRate)} | ${hashrates.map((hr, idx) => `GPU${idx}: ${humanHashrate(hr)}`).filter(hr => hr).join(' | ')}`);
Log.i(TAG, `Hashrate: ${Utils.humanHashrate(totalHashRate)} | ${hashrates.map((hr, idx) => `GPU${idx}: ${Utils.humanHashrate(hr)}`).filter(hr => hr).join(' | ')}`);
});

$.consensus.on('established', () => {
Expand All @@ -99,8 +92,52 @@ if (!config) {

Log.i(TAG, 'Connecting to Nimiq network');
$.network.connect();
}

})().catch(e => {
console.error(e);
process.exit(1);
});
async function setupSushiPoolMiner(address, config, deviceData) {
Log.i(TAG, `Setting up SushiPoolMiner`);

const poolMining = {
host: config.host,
port: config.port
}
$.miner = new SushiPoolMiner(poolMining, address, deviceData.deviceName, deviceData,
config.devices, config.memory, config.threads);

$.miner.on('connected', () => {
Log.i(TAG,'Connected to pool');
});

$.miner.on('balance', (balance, confirmedBalance) => {
Log.i(TAG,`Balance: ${balance}, confirmed balance: ${confirmedBalance}`);
});

$.miner.on('settings', (address, extraData, targetCompact) => {
const difficulty = Nimiq.BlockUtils.compactToDifficulty(targetCompact);
Nimiq.Log.i(SushiPoolMiner, `Set share difficulty: ${difficulty.toFixed(2)} (${targetCompact.toString(16)})`);
$.miner.currentTargetCompact = targetCompact;
$.miner.mineBlock(false);
});

$.miner.on('new-block', (blockHeader) => {
const height = blockHeader.readUInt32BE(134);
const hex = blockHeader.toString('hex');
Log.i(TAG,`New block #${height}: ${hex}`);

// Workaround duplicated blocks
if ($.miner.currentBlockHeader != undefined
&& $.miner.currentBlockHeader.equals(blockHeader)) {
Log.w(TAG,'The same block arrived once again!');
return;
}

$.miner.currentBlockHeader = blockHeader;
$.miner.mineBlock(true);
});
$.miner.on('disconnected', () => {
$.miner._miner.stop();
});
$.miner.on('error', (reason) => {
Log.w(TAG,`Pool error: ${reason}`);
});
}
3 changes: 3 additions & 0 deletions miner.sample.conf
Expand Up @@ -11,6 +11,9 @@
// Device name to show in the dashboard
"name": "My Miner",

// Consensus type to use
// "consensus": "nano",

// Expected hashrate in kH/s
// "hashrate": 100,

Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "sushipool-opencl-miner",
"version": "1.0.8",
"version": "2.0.0",
"main": "index.js",
"homepage": "https://sushipool.com/",
"description": "",
Expand Down
142 changes: 142 additions & 0 deletions src/DumbGpuMiner.js
@@ -0,0 +1,142 @@
const Nimiq = require('@nimiq/core');
const NativeMiner = require('bindings')('nimiq_miner.node');

const INITIAL_SEED_SIZE = 256;
const MAX_NONCE = 2 ** 32;
const HASHRATE_MOVING_AVERAGE = 5; // seconds
const HASHRATE_REPORT_INTERVAL = 5; // seconds

const ARGON2_ITERATIONS = 1;
const ARGON2_LANES = 1;
const ARGON2_MEMORY_COST = 512;
const ARGON2_VERSION = 0x13;
const ARGON2_TYPE = 0; // Argon2D
const ARGON2_SALT = 'nimiqrocks!';
const ARGON2_HASH_LENGTH = 32;

class DumbGpuMiner extends Nimiq.Observable {

constructor(allowedDevices, memorySizes, threads) {
super();

this._miningEnabled = false;
this._nonce = 0;
this._workId = 0;

allowedDevices = Array.isArray(allowedDevices) ? allowedDevices : [];
memorySizes = Array.isArray(memorySizes) ? memorySizes : [];
threads = Array.isArray(threads) ? threads : [];

const miner = new NativeMiner.Miner(allowedDevices, memorySizes, threads);
const workers = miner.getWorkers();

this._hashes = new Array(workers.length).fill(0);
this._lastHashRates = this._hashes.map(_ => []);

this._miner = miner;
this._workers = workers.map((w, idx) => {
const noncesPerRun = w.noncesPerRun;

return (blockHeader, shareCompact) => {
const workId = this._workId;
const next = () => {
//console.log(`@Nonce ${this._nonce}`);
const startNonce = this._nonce;
this._nonce += noncesPerRun;
w.mineNonces((error, nonce) => {
if (error) {
throw error;
}
this._hashes[idx] += noncesPerRun;
// Another block arrived
if (workId !== this._workId) {
//console.log(`Stopped working on stale block, work id #${workId}`);
return;
}
if (nonce !== 0) {
this.fire('share', nonce);
}
if (this._miningEnabled && this._nonce < MAX_NONCE) {
next();
}
}, startNonce, shareCompact);
};

w.setup(this._getInitialSeed(blockHeader), shareCompact);
next();
};
});
this._gpuInfo = workers.map(w => {
return {
idx: w.deviceIndex,
name: w.deviceName,
vendor: w.deviceVendor,
driver: w.driverVersion,
computeUnits: w.maxComputeUnits,
clockFrequency: w.maxClockFrequency,
memSize: w.globalMemSize,
type: 'GPU'
};
});
}

_getInitialSeed(blockHeader) {
const seed = Buffer.alloc(INITIAL_SEED_SIZE);
seed.writeUInt32LE(ARGON2_LANES, 0);
seed.writeUInt32LE(ARGON2_HASH_LENGTH, 4);
seed.writeUInt32LE(ARGON2_MEMORY_COST, 8);
seed.writeUInt32LE(ARGON2_ITERATIONS, 12);
seed.writeUInt32LE(ARGON2_VERSION, 16);
seed.writeUInt32LE(ARGON2_TYPE, 20);
seed.writeUInt32LE(blockHeader.length, 24);
blockHeader.copy(seed, 28);
seed.writeUInt32LE(ARGON2_SALT.length, 174);
seed.write(ARGON2_SALT, 178, 'ascii');
seed.writeUInt32LE(0, 189);
seed.writeUInt32LE(0, 193);
// zero padding 59 bytes
return seed;
}

_reportHashRate() {
this._lastHashRates.forEach((hashRates, idx) => {
const hashRate = this._hashes[idx] / HASHRATE_REPORT_INTERVAL;
hashRates.push(hashRate);
if (hashRates.length > HASHRATE_MOVING_AVERAGE) {
hashRates.shift();
}
});
this._hashes.fill(0);
const averageHashRates = this._lastHashRates.map(hashRates => hashRates.reduce((sum, val) => sum + val, 0) / hashRates.length);
this.fire('hashrate', averageHashRates);
}

mine(blockHeader, shareCompact, resetNonce = true) {
this._workId++;
if (resetNonce) {
this._nonce = 0;
}
this._miningEnabled = true;

if (!this._hashRateTimer) {
this._hashRateTimer = setInterval(() => this._reportHashRate(), 1000 * HASHRATE_REPORT_INTERVAL);
}

//console.log(`Started miner on block #${blockHeader.readUInt32BE(134)}, work id #${this._workId}`);
this._workers.forEach(worker => worker(blockHeader, shareCompact));
}

stop() {
this._miningEnabled = false;
if (this._hashRateTimer) {
clearInterval(this._hashRateTimer);
delete this._hashRateTimer;
}
}

get gpuInfo() {
return this._gpuInfo;
}
}

module.exports = DumbGpuMiner;

0 comments on commit 56dbc52

Please sign in to comment.