562 lines
10 KiB
JavaScript
562 lines
10 KiB
JavaScript
/*!
|
|
* cpuminer.js - inefficient cpu miner for bcoin (because we can)
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var util = require('../utils/util');
|
|
var co = require('../utils/co');
|
|
var AsyncObject = require('../utils/asyncobject');
|
|
var workerPool = require('../workers/workerpool').pool;
|
|
var mine = require('./mine');
|
|
|
|
/**
|
|
* CPU miner.
|
|
* @alias module:mining.CPUMiner
|
|
* @constructor
|
|
* @param {Miner} miner
|
|
* @emits CPUMiner#block
|
|
* @emits CPUMiner#status
|
|
*/
|
|
|
|
function CPUMiner(miner) {
|
|
if (!(this instanceof CPUMiner))
|
|
return new CPUMiner(miner);
|
|
|
|
AsyncObject.call(this);
|
|
|
|
this.miner = miner;
|
|
this.network = this.miner.network;
|
|
this.logger = this.miner.logger;
|
|
this.chain = this.miner.chain;
|
|
|
|
this.running = false;
|
|
this.stopping = false;
|
|
this.job = null;
|
|
this.since = 0;
|
|
|
|
this._init();
|
|
}
|
|
|
|
util.inherits(CPUMiner, AsyncObject);
|
|
|
|
/**
|
|
* Nonce range interval.
|
|
* @const {Number}
|
|
* @default
|
|
*/
|
|
|
|
CPUMiner.INTERVAL = 0xffffffff / 1500 | 0;
|
|
|
|
/**
|
|
* Initialize the miner.
|
|
* @private
|
|
*/
|
|
|
|
CPUMiner.prototype._init = function _init() {
|
|
var self = this;
|
|
|
|
this.chain.on('tip', function(tip) {
|
|
if (!self.job)
|
|
return;
|
|
|
|
if (self.job.attempt.prevBlock === tip.prevBlock)
|
|
self.job.destroy();
|
|
});
|
|
|
|
this.on('block', function(block, entry) {
|
|
// Emit the block hex as a failsafe (in case we can't send it)
|
|
self.logger.info('Found block: %d (%s).', entry.height, entry.rhash());
|
|
self.logger.debug('Raw: %s', block.toRaw().toString('hex'));
|
|
});
|
|
|
|
this.on('status', function(stat) {
|
|
self.logger.info(
|
|
'CPUMiner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s',
|
|
stat.hashrate / 1000 | 0,
|
|
stat.hashes,
|
|
stat.target,
|
|
stat.height,
|
|
stat.best);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Open the miner.
|
|
* @method
|
|
* @alias module:mining.CPUMiner#open
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
CPUMiner.prototype._open = co(function* open() {
|
|
});
|
|
|
|
/**
|
|
* Close the miner.
|
|
* @method
|
|
* @alias module:mining.CPUMiner#close
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
CPUMiner.prototype._close = co(function* close() {
|
|
if (!this.running)
|
|
return;
|
|
|
|
if (this.stopping) {
|
|
yield this._onStop();
|
|
return;
|
|
}
|
|
|
|
yield this.stop();
|
|
});
|
|
|
|
/**
|
|
* Start mining.
|
|
* @method
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
CPUMiner.prototype.start = co(function* start() {
|
|
var block, entry;
|
|
|
|
assert(!this.running, 'CPUMiner is already running.');
|
|
|
|
this.running = true;
|
|
this.stopping = false;
|
|
|
|
for (;;) {
|
|
this.job = null;
|
|
|
|
try {
|
|
this.job = yield this.createJob();
|
|
} catch (e) {
|
|
if (this.stopping)
|
|
break;
|
|
this.emit('error', e);
|
|
continue;
|
|
}
|
|
|
|
if (this.stopping)
|
|
break;
|
|
|
|
try {
|
|
block = yield this.mineAsync(this.job);
|
|
} catch (e) {
|
|
if (this.stopping)
|
|
break;
|
|
this.emit('error', e);
|
|
continue;
|
|
}
|
|
|
|
if (this.stopping)
|
|
break;
|
|
|
|
if (!block)
|
|
continue;
|
|
|
|
try {
|
|
entry = yield this.chain.add(block);
|
|
} catch (e) {
|
|
if (this.stopping)
|
|
break;
|
|
this.emit('error', e);
|
|
continue;
|
|
}
|
|
|
|
if (!entry) {
|
|
this.logger.warning('Mined a bad-prevblk (race condition?)');
|
|
continue;
|
|
}
|
|
|
|
if (this.stopping)
|
|
break;
|
|
|
|
this.emit('block', block, entry);
|
|
}
|
|
|
|
this.emit('done');
|
|
});
|
|
|
|
/**
|
|
* Stop mining.
|
|
* @method
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
CPUMiner.prototype.stop = co(function* stop() {
|
|
assert(this.running, 'CPUMiner is not running.');
|
|
assert(!this.stopping, 'CPUMiner is already stopping.');
|
|
|
|
this.stopping = true;
|
|
|
|
yield this._onDone();
|
|
|
|
this.running = false;
|
|
this.stopping = false;
|
|
this.job = null;
|
|
|
|
this.emit('stop');
|
|
});
|
|
|
|
/**
|
|
* Wait for `done` event.
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
CPUMiner.prototype._onDone = function _onDone() {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.once('done', resolve);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Wait for `stop` event.
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
CPUMiner.prototype._onStop = function _onStop() {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.once('stop', resolve);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Create a mining job.
|
|
* @method
|
|
* @param {ChainEntry?} tip
|
|
* @param {Address?} address
|
|
* @returns {Promise} - Returns {@link Job}.
|
|
*/
|
|
|
|
CPUMiner.prototype.createJob = co(function* createJob(tip, address) {
|
|
var attempt = yield this.miner.createBlock(tip, address);
|
|
return new CPUJob(this, attempt);
|
|
});
|
|
|
|
/**
|
|
* Mine a single block.
|
|
* @method
|
|
* @param {ChainEntry?} tip
|
|
* @param {Address?} address
|
|
* @returns {Promise} - Returns [{@link Block}].
|
|
*/
|
|
|
|
CPUMiner.prototype.mineBlock = co(function* mineBlock(tip, address) {
|
|
var job = yield this.createJob(tip, address);
|
|
return yield this.mineAsync(job);
|
|
});
|
|
|
|
/**
|
|
* Notify the miner that a new
|
|
* tx has entered the mempool.
|
|
*/
|
|
|
|
CPUMiner.prototype.notifyEntry = function notifyEntry() {
|
|
if (!this.running)
|
|
return;
|
|
|
|
if (!this.job)
|
|
return;
|
|
|
|
if (++this.since > 20) {
|
|
this.since = 0;
|
|
this.job.destroy();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Hash until the nonce overflows.
|
|
* @param {CPUJob} job
|
|
* @returns {Number} nonce
|
|
*/
|
|
|
|
CPUMiner.prototype.findNonce = function findNonce(job) {
|
|
var data = job.getHeader(0);
|
|
var target = job.attempt.target;
|
|
var interval = CPUMiner.INTERVAL;
|
|
var min = 0;
|
|
var max = interval;
|
|
var nonce;
|
|
|
|
while (max <= 0xffffffff) {
|
|
nonce = mine(data, target, min, max);
|
|
|
|
if (nonce !== -1)
|
|
break;
|
|
|
|
this.sendStatus(job, max);
|
|
|
|
min += interval;
|
|
max += interval;
|
|
}
|
|
|
|
return nonce;
|
|
};
|
|
|
|
/**
|
|
* Hash until the nonce overflows.
|
|
* @method
|
|
* @param {CPUJob} job
|
|
* @returns {Promise} Returns Number.
|
|
*/
|
|
|
|
CPUMiner.prototype.findNonceAsync = co(function* findNonceAsync(job) {
|
|
var data = job.getHeader(0);
|
|
var target = job.attempt.target;
|
|
var interval = CPUMiner.INTERVAL;
|
|
var min = 0;
|
|
var max = interval;
|
|
var nonce;
|
|
|
|
while (max <= 0xffffffff) {
|
|
nonce = yield workerPool.mine(data, target, min, max);
|
|
|
|
if (nonce !== -1)
|
|
break;
|
|
|
|
if (job.destroyed)
|
|
return nonce;
|
|
|
|
this.sendStatus(job, max);
|
|
|
|
min += interval;
|
|
max += interval;
|
|
}
|
|
|
|
return nonce;
|
|
});
|
|
|
|
/**
|
|
* Mine synchronously until the block is found.
|
|
* @param {CPUJob} job
|
|
* @returns {Block}
|
|
*/
|
|
|
|
CPUMiner.prototype.mine = function mine(job) {
|
|
var nonce;
|
|
|
|
// Track how long we've been at it.
|
|
job.begin = util.now();
|
|
|
|
for (;;) {
|
|
nonce = this.findNonce(job);
|
|
|
|
if (nonce !== -1)
|
|
break;
|
|
|
|
this.iterate(job);
|
|
}
|
|
|
|
return job.commit(nonce);
|
|
};
|
|
|
|
/**
|
|
* Mine asynchronously until the block is found.
|
|
* @method
|
|
* @param {CPUJob} job
|
|
* @returns {Promise} - Returns {@link Block}.
|
|
*/
|
|
|
|
CPUMiner.prototype.mineAsync = co(function* mineAsync(job) {
|
|
var nonce;
|
|
|
|
// Track how long we've been at it.
|
|
job.begin = util.now();
|
|
|
|
for (;;) {
|
|
nonce = yield this.findNonceAsync(job);
|
|
|
|
if (nonce !== -1)
|
|
break;
|
|
|
|
if (job.destroyed)
|
|
return;
|
|
|
|
this.iterate(job);
|
|
}
|
|
|
|
return job.commit(nonce);
|
|
});
|
|
|
|
/**
|
|
* Increment extraNonce and send status.
|
|
* @param {CPUJob} job
|
|
*/
|
|
|
|
CPUMiner.prototype.iterate = function iterate(job) {
|
|
job.iterations++;
|
|
job.updateNonce();
|
|
this.sendStatus(job, 0);
|
|
};
|
|
|
|
/**
|
|
* Send a progress report (emits `status`).
|
|
* @param {CPUJob} job
|
|
* @param {Number} nonce
|
|
*/
|
|
|
|
CPUMiner.prototype.sendStatus = function sendStatus(job, nonce) {
|
|
this.emit('status', {
|
|
target: job.attempt.bits,
|
|
hashes: job.getHashes(),
|
|
hashrate: job.getRate(nonce),
|
|
height: job.attempt.height,
|
|
best: util.revHex(job.attempt.prevBlock)
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Mining Job
|
|
* @constructor
|
|
* @ignore
|
|
* @param {CPUMiner} miner
|
|
* @param {BlockTemplate} attempt
|
|
*/
|
|
|
|
function CPUJob(miner, attempt) {
|
|
this.miner = miner;
|
|
this.attempt = attempt;
|
|
this.destroyed = false;
|
|
this.committed = false;
|
|
this.iterations = 0;
|
|
this.begin = 0;
|
|
this.nonce1 = 0;
|
|
this.nonce2 = 0;
|
|
this.refresh();
|
|
}
|
|
|
|
/**
|
|
* Get the raw block header.
|
|
* @param {Number} nonce
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
CPUJob.prototype.getHeader = function getHeader(nonce) {
|
|
var attempt = this.attempt;
|
|
var n1 = this.nonce1;
|
|
var n2 = this.nonce2;
|
|
var ts = attempt.ts;
|
|
return this.attempt.getHeader(n1, n2, ts, nonce);
|
|
};
|
|
|
|
/**
|
|
* Commit job and return a block.
|
|
* @param {Number} nonce
|
|
* @returns {Block}
|
|
*/
|
|
|
|
CPUJob.prototype.commit = function commit(nonce) {
|
|
var attempt = this.attempt;
|
|
var n1 = this.nonce1;
|
|
var n2 = this.nonce2;
|
|
var ts = attempt.ts;
|
|
|
|
assert(!this.committed, 'Job already committed.');
|
|
this.committed = true;
|
|
|
|
return this.attempt.commit(n1, n2, ts, nonce);
|
|
};
|
|
|
|
/**
|
|
* Mine block synchronously.
|
|
* @returns {Block}
|
|
*/
|
|
|
|
CPUJob.prototype.mine = function mine() {
|
|
return this.miner.mine(this);
|
|
};
|
|
|
|
/**
|
|
* Mine block asynchronously.
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
CPUJob.prototype.mineAsync = function mineAsync() {
|
|
return this.miner.mineAsync(this);
|
|
};
|
|
|
|
/**
|
|
* Refresh the block template.
|
|
*/
|
|
|
|
CPUJob.prototype.refresh = function refresh() {
|
|
return this.attempt.refresh();
|
|
};
|
|
|
|
/**
|
|
* Increment the extraNonce.
|
|
*/
|
|
|
|
CPUJob.prototype.updateNonce = function() {
|
|
// Overflow the nonce and increment the extraNonce.
|
|
this.nonce1++;
|
|
|
|
// Wrap at 4 bytes.
|
|
if (this.nonce1 === 0xffffffff) {
|
|
this.nonce1 = 0;
|
|
this.nonce2++;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Destroy the job.
|
|
*/
|
|
|
|
CPUJob.prototype.destroy = function() {
|
|
assert(!this.destroyed, 'Job already destroyed.');
|
|
this.destroyed = true;
|
|
};
|
|
|
|
/**
|
|
* Calculate number of hashes.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
CPUJob.prototype.getHashes = function() {
|
|
return this.iterations * 0xffffffff + this.block.nonce;
|
|
};
|
|
|
|
/**
|
|
* Calculate hashrate.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
CPUJob.prototype.getRate = function(nonce) {
|
|
return (nonce / (util.now() - this.begin)) | 0;
|
|
};
|
|
|
|
/**
|
|
* Add a transaction to the block.
|
|
* @param {TX} tx
|
|
* @param {CoinView} view
|
|
*/
|
|
|
|
CPUJob.prototype.addTX = function(tx, view) {
|
|
return this.attempt.addTX(tx, view);
|
|
};
|
|
|
|
/**
|
|
* Add a transaction to the block
|
|
* (less verification than addTX).
|
|
* @param {TX} tx
|
|
* @param {CoinView?} view
|
|
*/
|
|
|
|
CPUJob.prototype.pushTX = function(tx, view) {
|
|
return this.attempt.pushTX(tx, view);
|
|
};
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = CPUMiner;
|