fcoin/lib/mining/cpuminer.js
Christopher Jeffrey 1b098b61ba
template: minor.
2017-03-14 06:10:34 -07:00

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;