455 lines
10 KiB
JavaScript
455 lines
10 KiB
JavaScript
/**
|
|
* miner.js - inefficient miner for bcoin (because we can)
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* https://github.com/indutny/bcoin
|
|
*/
|
|
|
|
var bcoin = require('../bcoin');
|
|
var utils = require('./utils');
|
|
var assert = utils.assert;
|
|
var constants = bcoin.protocol.constants;
|
|
var network = bcoin.protocol.network;
|
|
|
|
var bn = require('bn.js');
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
/**
|
|
* Miner
|
|
*/
|
|
|
|
function Miner(node, options) {
|
|
if (!(this instanceof Miner))
|
|
return new Miner(node, options);
|
|
|
|
EventEmitter.call(this);
|
|
|
|
if (!options)
|
|
options = {};
|
|
|
|
this.options = options;
|
|
this.address = this.options.address;
|
|
this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin';
|
|
|
|
// Allow a dsha256 option in case someone
|
|
// wants to pass in a faster linked in function.
|
|
this.dsha256 = this.options.dsha256 || utils.dsha256;
|
|
|
|
this.node = node;
|
|
this.pool = node.pool;
|
|
this.chain = node.chain;
|
|
this.mempool = node.mempool;
|
|
|
|
this.running = false;
|
|
this.timeout = null;
|
|
|
|
this.block = null;
|
|
this.iterations = 0;
|
|
this._begin = utils.now();
|
|
|
|
this._init();
|
|
}
|
|
|
|
utils.inherits(Miner, EventEmitter);
|
|
|
|
Miner.prototype.open = function open(callback) {
|
|
return utils.nextTick(callback);
|
|
};
|
|
|
|
Miner.prototype.close =
|
|
Miner.prototype.destroy = function destroy(callback) {
|
|
return utils.nextTick(callback);
|
|
};
|
|
|
|
Miner.prototype._init = function _init() {
|
|
var self = this;
|
|
|
|
if (this.mempool) {
|
|
this.mempool.on('tx', function(tx) {
|
|
if (!self.running)
|
|
return;
|
|
self.addTX(tx);
|
|
});
|
|
} else {
|
|
this.pool.on('tx', function(tx) {
|
|
if (!self.running)
|
|
return;
|
|
self.addTX(tx);
|
|
});
|
|
}
|
|
|
|
this.chain.on('tip', function(tip) {
|
|
if (!self.running)
|
|
return;
|
|
self.stop();
|
|
setTimeout(function() {
|
|
self.start();
|
|
}, network.type === 'regtest' ? 100 : 5000);
|
|
});
|
|
|
|
this.on('block', function(block) {
|
|
utils.debug(
|
|
'Found block: %d (%s)',
|
|
block.height,
|
|
block.hash('hex'));
|
|
// Emit the block hex as a failsafe (in case we can't send it)
|
|
utils.debug('Raw: %s', utils.toHex(block.render()));
|
|
self.pool.sendBlock(block);
|
|
});
|
|
|
|
this.on('status', function(stat) {
|
|
utils.debug(
|
|
'hashrate=%dkhs hashes=%d target=%d height=%d best=%s',
|
|
stat.hashrate / 1000 | 0,
|
|
stat.hashes,
|
|
stat.target,
|
|
stat.height,
|
|
stat.best);
|
|
});
|
|
};
|
|
|
|
Miner.prototype.start = function start() {
|
|
var self = this;
|
|
|
|
// Wait for `tip`.
|
|
if (!this.chain.tip) {
|
|
this.chain.on('tip', function(tip) {
|
|
self.start();
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.stop();
|
|
|
|
this.running = true;
|
|
|
|
// Reset iterations
|
|
this.iterations = 0;
|
|
|
|
// Create a new block and start hashing
|
|
this.createBlock(function(err, block) {
|
|
if (err)
|
|
return self.emit('error', err);
|
|
|
|
self.block = block;
|
|
|
|
if (!self.mempool)
|
|
return self.iterate();
|
|
|
|
self.mempool.getSnapshot(function(err, hashes) {
|
|
if (err)
|
|
return self.emit('error', err);
|
|
|
|
utils.forEachSerial(hashes, function(hash, next) {
|
|
self.mempool.getTX(hash, function(err, tx) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
self.mempool.fillAllCoins(tx, function(err) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
self.addTX(tx);
|
|
next();
|
|
});
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return self.emit('error', err);
|
|
|
|
self.iterate();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
Miner.prototype.stop = function stop() {
|
|
if (!this.running)
|
|
return;
|
|
|
|
this.running = false;
|
|
|
|
clearTimeout(this.timeout);
|
|
this.timeout = null;
|
|
};
|
|
|
|
Miner.prototype.addTX = function addTX(tx) {
|
|
var size = this.block.getVirtualSize() + tx.getVirtualSize();
|
|
|
|
// Deliver me from the block size debate, please
|
|
if (size > constants.blocks.maxSize)
|
|
return false;
|
|
|
|
if (this.block.hasTX(tx))
|
|
return false;
|
|
|
|
if (!this.block.witness && tx.hasWitness())
|
|
return false;
|
|
|
|
// Add the tx to our block
|
|
this.block.txs.push(tx);
|
|
|
|
// Update coinbase value
|
|
this.updateCoinbase();
|
|
|
|
// Update merkle root for new coinbase and new tx
|
|
this.updateMerkle();
|
|
|
|
return true;
|
|
};
|
|
|
|
Miner.prototype.createBlock = function createBlock(callback) {
|
|
var self = this;
|
|
var ts = Math.max(utils.now(), this.chain.tip.ts + 1);
|
|
var coinbase, headers, block;
|
|
|
|
// Find target
|
|
this.chain.getTargetAsync(this.chain.tip, ts, function(err, target) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
// Calculate version with versionbits
|
|
self.chain.computeBlockVersion(self.chain.tip, function(err, version) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
// Create a coinbase
|
|
coinbase = bcoin.mtx();
|
|
|
|
coinbase.addInput({
|
|
prevout: {
|
|
hash: utils.toHex(constants.zeroHash),
|
|
index: 0xffffffff
|
|
},
|
|
coin: null,
|
|
script: new bcoin.script([
|
|
// Height (required in v2+ blocks)
|
|
bcoin.script.array(self.chain.height + 1),
|
|
// extraNonce - incremented when
|
|
// the nonce overflows.
|
|
bcoin.script.array(0),
|
|
// Add a nonce to ensure we don't
|
|
// collide with a previous coinbase
|
|
// of ours. This isn't really
|
|
// necessary nowdays due to bip34
|
|
// (used above).
|
|
bcoin.script.array(utils.nonce()),
|
|
// Let the world know this little
|
|
// miner succeeded.
|
|
new Buffer(self.coinbaseFlags, 'ascii')
|
|
]),
|
|
witness: new bcoin.script.witness([]),
|
|
sequence: 0xffffffff
|
|
});
|
|
|
|
coinbase.addOutput({
|
|
address: self.address,
|
|
value: new bn(0)
|
|
});
|
|
|
|
// Create our block
|
|
headers = {
|
|
version: version,
|
|
prevBlock: self.chain.tip.hash,
|
|
merkleRoot: constants.zeroHash,
|
|
ts: ts,
|
|
bits: target,
|
|
nonce: 0
|
|
};
|
|
|
|
block = bcoin.block(headers);
|
|
|
|
block.txs.push(coinbase);
|
|
|
|
block.height = self.chain.height + 1;
|
|
block.target = utils.fromCompact(target).toBuffer('le', 32);
|
|
block.extraNonce = new bn(0);
|
|
|
|
if (self.chain.segwitActive) {
|
|
// Set up the witness nonce and
|
|
// commitment output for segwit.
|
|
block.witness = true;
|
|
block.witnessNonce = utils.nonce().toBuffer('le', 8);
|
|
coinbase.inputs[0].witness.items[0] = block.witnessNonce;
|
|
coinbase.addOutput({
|
|
script: new bcoin.script([]),
|
|
value: new bn(0)
|
|
});
|
|
}
|
|
|
|
// Update coinbase since our coinbase was added.
|
|
self.updateCoinbase(block);
|
|
|
|
// Create our merkle root.
|
|
self.updateMerkle(block);
|
|
|
|
return callback(null, block);
|
|
});
|
|
});
|
|
};
|
|
|
|
Miner.prototype.updateCommitment = function updateCommitment(block) {
|
|
var coinbase = block.txs[0];
|
|
var hash;
|
|
|
|
assert(coinbase);
|
|
|
|
if (!block)
|
|
block = this.block;
|
|
|
|
hash = block.getCommitmentHash();
|
|
coinbase.outputs[1].script = bcoin.script.createCommitment(hash);
|
|
};
|
|
|
|
Miner.prototype.updateCoinbase = function updateCoinbase(block) {
|
|
var coinbase = block.txs[0];
|
|
|
|
assert(coinbase);
|
|
|
|
if (!block)
|
|
block = this.block;
|
|
|
|
coinbase.inputs[0].script[1] = block.extraNonce.toBuffer();
|
|
coinbase.outputs[0].value = block.getReward();
|
|
};
|
|
|
|
Miner.prototype.updateMerkle = function updateMerkle(block) {
|
|
if (!block)
|
|
block = this.block;
|
|
|
|
// Always update commitment before updating merkle root.
|
|
// The updated commitment output will change the merkle root.
|
|
if (block.witness)
|
|
this.updateCommitment(block);
|
|
|
|
block.ts = Math.max(utils.now(), this.chain.tip.ts + 1);
|
|
block.merkleRoot = block.getMerkleRoot('hex');
|
|
};
|
|
|
|
Miner.prototype.iterate = function iterate() {
|
|
var self = this;
|
|
|
|
this.timeout = setTimeout(function() {
|
|
// Try to find a block: do one iteration of extraNonce
|
|
if (!self.findNonce())
|
|
return self.iterate();
|
|
|
|
// Add our block to the chain
|
|
self.chain.add(self.block, function(err) {
|
|
if (err) {
|
|
if (err.type === 'VerifyError')
|
|
utils.debug('%s could not be added to chain.', self.block.rhash);
|
|
self.emit('error', err);
|
|
return self.start();
|
|
}
|
|
|
|
// Emit our newly found block
|
|
self.emit('block', self.block);
|
|
|
|
// `tip` will now be emitted by chain
|
|
// and the whole process starts over.
|
|
});
|
|
}, 100);
|
|
};
|
|
|
|
Miner.prototype.__defineGetter__('hashes', function() {
|
|
return new bn(this.iterations).mul(utils.U32).addn(this.block.nonce);
|
|
});
|
|
|
|
Miner.prototype.__defineGetter__('rate', function() {
|
|
if (!this.block.nonce)
|
|
return 0;
|
|
// Calculate our terrible hashrate
|
|
return (this.block.nonce / (utils.now() - this._begin)) * 2 | 0;
|
|
});
|
|
|
|
Miner.prototype.sendStatus = function sendStatus() {
|
|
this.emit('status', {
|
|
block: this.block,
|
|
target: this.block.bits,
|
|
hashes: this.hashes.toString(10),
|
|
hashrate: this.rate,
|
|
height: this.chain.height + 1,
|
|
best: utils.revHex(this.chain.tip.hash)
|
|
});
|
|
};
|
|
|
|
Miner.prototype.findNonce = function findNonce() {
|
|
var data = this.block.abbr();
|
|
var now;
|
|
|
|
// Track how long we've been at it.
|
|
this._begin = utils.now();
|
|
|
|
assert(this.block.ts > this.chain.tip.ts);
|
|
|
|
// The heart and soul of the miner: match the target.
|
|
while (this.block.nonce <= 0xffffffff) {
|
|
// Hash and test against the next target
|
|
if (rcmp(this.dsha256(data), this.block.target) < 0)
|
|
return true;
|
|
|
|
// Increment the nonce to get a different hash
|
|
this.block.nonce++;
|
|
|
|
// Update the raw buffer (faster than
|
|
// constantly serializing the block)
|
|
utils.writeU32(data, this.block.nonce, 76);
|
|
|
|
// Send progress report every so often
|
|
if (this.block.nonce % 100000 === 0)
|
|
this.sendStatus();
|
|
}
|
|
|
|
// Keep track of our iterations
|
|
this.iterations++;
|
|
|
|
// Send progress report
|
|
this.sendStatus();
|
|
|
|
// If we took more a second or more (likely),
|
|
// skip incrementing the extra nonce and just
|
|
// update the timestamp. This improves
|
|
// performance because we do not have to
|
|
// recalculate the merkle root.
|
|
now = utils.now();
|
|
if (now > this.block.ts && now > this.chain.tip.ts) {
|
|
this.block.ts = now;
|
|
// Overflow the nonce
|
|
this.block.nonce = 0;
|
|
return false;
|
|
}
|
|
|
|
// Overflow the nonce and increment the extraNonce.
|
|
this.block.nonce = 0;
|
|
this.block.extraNonce.iaddn(1);
|
|
|
|
// We incremented the extraNonce, need to update coinbase.
|
|
this.updateCoinbase();
|
|
|
|
// We changed the coinbase, need to update merkleRoot.
|
|
this.updateMerkle();
|
|
|
|
return false;
|
|
};
|
|
|
|
function rcmp(a, b) {
|
|
var i;
|
|
|
|
assert(a.length === b.length);
|
|
|
|
for (i = a.length - 1; i >= 0; i--) {
|
|
if (a[i] < b[i])
|
|
return -1;
|
|
if (a[i] > b[i])
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = Miner;
|