fcoin/lib/miner/minerblock.js
2016-10-02 23:24:11 -07:00

518 lines
12 KiB
JavaScript

/*!
* miner.js - inefficient miner for bcoin (because we can)
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var utils = require('../utils/utils');
var co = require('../utils/co');
var crypto = require('../crypto/crypto');
var assert = require('assert');
var constants = require('../protocol/constants');
var Network = require('../protocol/network');
var bn = require('bn.js');
var EventEmitter = require('events').EventEmitter;
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var TX = require('../primitives/tx');
var Address = require('../primitives/address');
var Block = require('../primitives/block');
var Input = require('../primitives/input');
var Output = require('../primitives/output');
var time = require('../net/timedata');
var ChainEntry = require('../chain/chainentry');
/**
* MinerBlock
* @exports MinerBlock
* @constructor
* @param {Object} options
* @param {ChainEntry} options.tip
* @param {Number} options.height
* @param {Number} options.target - Compact form.
* @param {Base58Address} options.address - Payout address.
* @param {Boolean} options.witness - Allow witness
* transactions, mine a witness block.
* @param {String} options.coinbaseFlags
* @property {Block} block
* @property {TX} coinbase
* @property {BN} hashes - Number of hashes attempted.
* @property {Number} rate - Hash rate.
* @emits MinerBlock#status
*/
function MinerBlock(options) {
if (!(this instanceof MinerBlock))
return new MinerBlock(options);
EventEmitter.call(this);
this.options = options;
this.workerPool = options.workerPool;
this.tip = options.tip;
this.height = options.tip.height + 1;
this.bits = options.target;
this.target = utils.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32);
this.extraNonce = new bn(0);
this.iterations = 0;
this.coinbaseFlags = options.coinbaseFlags;
this.witness = options.witness;
this.address = options.address;
this.network = Network.get(options.network);
this.timeout = null;
if (typeof this.coinbaseFlags === 'string')
this.coinbaseFlags = new Buffer(this.coinbaseFlags, 'utf8');
this.coinbase = new TX();
this.coinbase.mutable = true;
this.block = new Block();
this.block.mutable = true;
this._init();
}
utils.inherits(MinerBlock, EventEmitter);
/**
* Initialize the block.
* @private
*/
MinerBlock.prototype._init = function _init() {
var options = this.options;
var block = this.block;
var cb = this.coinbase;
var i, input, output, hash, witnessNonce;
// Coinbase input.
input = new Input();
// Height (required in v2+ blocks)
input.script.set(0, new bn(this.height));
// extraNonce - incremented when
// the nonce overflows.
input.script.set(1, this.extraNonce);
// 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).
input.script.set(2, utils.nonce());
// Let the world know this little
// miner succeeded.
input.script.set(3, this.coinbaseFlags);
input.script.compile();
cb.inputs.push(input);
// Reward output.
output = new Output();
output.script.fromAddress(this.address);
cb.outputs.push(output);
// If we're using segwit, we need to
// set up the nonce and commitment.
if (this.witness) {
// Our witness nonce is the hash256
// of the previous block hash.
hash = new Buffer(this.tip.hash, 'hex');
witnessNonce = crypto.hash256(hash);
// Set up the witness nonce.
input.witness.set(0, witnessNonce);
input.witness.compile();
// Commitment output.
cb.outputs.push(new Output());
}
// Setup our block.
block.version = options.version;
block.prevBlock = this.tip.hash;
block.merkleRoot = constants.NULL_HASH;
block.ts = Math.max(time.now(), this.tip.ts + 1);
block.bits = this.bits;
block.nonce = 0;
block.height = this.height;
block.addTX(cb);
if (options.txs) {
for (i = 0; i < options.txs.length; i++)
block.addTX(options.txs[i]);
}
// Update coinbase since our coinbase was added.
this.updateCoinbase();
// Create our merkle root.
this.updateMerkle();
};
/**
* Update the commitment output for segwit.
*/
MinerBlock.prototype.updateCommitment = function updateCommitment() {
var output = this.coinbase.outputs[1];
var flags = this.coinbaseFlags;
var hash;
// Recalculate witness merkle root.
hash = this.block.getCommitmentHash();
// Update commitment.
output.script.clear();
output.script.fromCommitment(hash, flags);
};
/**
* Update the extra nonce and coinbase reward.
*/
MinerBlock.prototype.updateCoinbase = function updateCoinbase() {
var input = this.coinbase.inputs[0];
var output = this.coinbase.outputs[0];
// Update extra nonce.
input.script.set(1, this.extraNonce);
input.script.compile();
// Update reward.
output.value = this.block.getReward(this.network);
};
/**
* Increment the extraNonce.
*/
MinerBlock.prototype.updateNonce = function updateNonce() {
this.block.ts = Math.max(time.now(), this.tip.ts + 1);
// Overflow the nonce and increment the extraNonce.
this.block.nonce = 0;
this.extraNonce.iaddn(1);
// We incremented the extraNonce, need to update coinbase.
this.updateCoinbase();
// We changed the coinbase, need to update merkleRoot.
this.updateMerkle();
};
/**
* Rebuild the merkle tree and update merkle root as well as the
* timestamp (also calls {@link MinerBlock#updateCommitment}
* if segwit is enabled).
*/
MinerBlock.prototype.updateMerkle = function updateMerkle() {
// Always update commitment before updating merkle root.
// The updated commitment output will change the merkle root.
if (this.witness)
this.updateCommitment();
// Update timestamp.
this.block.ts = Math.max(time.now(), this.tip.ts + 1);
// Recalculate merkle root.
this.block.merkleRoot = this.block.getMerkleRoot('hex');
};
/**
* Add a transaction to the block. Rebuilds the merkle tree,
* updates coinbase and commitment.
* @param {TX} tx
* @returns {Boolean} Whether the transaction was successfully added.
*/
MinerBlock.prototype.addTX = function addTX(tx) {
var weight;
assert(!tx.mutable, 'Cannot add mutable TX to block.');
weight = this.block.getWeight() + tx.getWeight();
if (weight > constants.block.MAX_WEIGHT)
return false;
if (this.block.hasTX(tx))
return false;
if (!this.witness && tx.hasWitness())
return false;
// Add the tx to our block
this.block.addTX(tx);
// Update coinbase value
this.updateCoinbase();
// Update merkle root for new coinbase and new tx
this.updateMerkle();
return true;
};
/**
* Hash until the nonce overflows, increment extraNonce, rebuild merkletree.
* @returns {Boolean} Whether the nonce was found.
*/
MinerBlock.prototype.findNonce = function findNonce() {
var tip = this.tip;
var block = this.block;
var target = this.target;
var data = block.abbr();
var now;
// Track how long we've been at it.
this.begin = utils.now();
assert(block.ts > tip.ts);
// The heart and soul of the miner: match the target.
while (block.nonce <= 0xffffffff) {
// Hash and test against the next target.
if (rcmp(crypto.hash256(data), target) <= 0) {
this.coinbase.mutable = false;
this.block.mutable = false;
return true;
}
// Increment the nonce to get a different hash
block.nonce++;
// Update the raw buffer (faster than
// constantly serializing the headers).
data.writeUInt32LE(block.nonce, 76, true);
// Send progress report every so often.
if (block.nonce % 500000 === 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 = time.now();
if (now > block.ts && now > tip.ts) {
block.ts = now;
// Overflow the nonce
block.nonce = 0;
return false;
}
// Overflow the nonce and increment the extraNonce.
this.updateNonce();
return false;
};
MinerBlock.prototype.__defineGetter__('hashes', function() {
return this.iterations * 0xffffffff + this.block.nonce;
});
MinerBlock.prototype.__defineGetter__('rate', function() {
return (this.block.nonce / (utils.now() - this.begin)) | 0;
});
/**
* Send a progress report (emits `status`).
*/
MinerBlock.prototype.sendStatus = function sendStatus() {
this.emit('status', {
block: this.block,
target: this.block.bits,
hashes: this.hashes,
hashrate: this.rate,
height: this.height,
best: utils.revHex(this.tip.hash)
});
};
/**
* Mine until the block is found. Will take a breather
* for 100ms every time the nonce overflows.
* @returns {Promise} - Returns {@link Block}.
*/
MinerBlock.prototype.mine = co(function* mine() {
yield this.wait(100);
// Try to find a block: do one iteration of extraNonce
if (!this.findNonce()) {
yield this.mine();
return;
}
return this.block;
});
/**
* Wait for a timeout.
* @param {Number} time
* @returns {Promise}
*/
MinerBlock.prototype.wait = function wait(time) {
var self = this;
return new Promise(function(resolve, reject) {
self.timeout = setTimeout(function() {
resolve();
}, time);
});
};
/**
* Mine synchronously until the block is found.
* @returns {Block}
*/
MinerBlock.prototype.mineSync = function mineSync() {
while (!this.findNonce());
return this.block;
};
/**
* Attempt to mine the block on the worker pool.
* @returns {Promise} - Returns {@link Block}.
*/
MinerBlock.prototype.mineAsync = co(function* mineAsync() {
var block;
if (!this.workerPool)
return yield this.mine();
block = yield this.workerPool.mine(this);
this.workerPool.destroy();
return block;
});
/**
* Destroy the minerblock. Stop mining. Clear timeout.
*/
MinerBlock.prototype.destroy = function destroy() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.block = null;
};
/**
* Serialize the miner block.
* @returns {Buffer}
*/
MinerBlock.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
var i;
p.writeU32(this.network.magic);
p.writeBytes(this.tip.toRaw());
p.writeU32(this.block.version);
p.writeU32(this.block.bits);
p.writeVarBytes(this.address.toRaw());
p.writeVarBytes(this.coinbaseFlags);
p.writeU8(this.witness ? 1 : 0);
p.writeVarint(this.block.txs.length - 1);
for (i = 1; i < this.block.txs.length; i++)
p.writeBytes(this.block.txs[i].toRaw());
if (!writer)
p = p.render();
return p;
};
/**
* Instantiate a miner block from serialized data.
* @params {Buffer} data
* @returns {MinerBlock}
*/
MinerBlock.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
var network = Network.fromMagic(p.readU32());
var tip = ChainEntry.fromRaw(null, p);
var version = p.readU32();
var bits = p.readU32();
var address = Address.fromRaw(p.readVarBytes());
var coinbaseFlags = p.readVarBytes();
var witness = p.readU8() === 1;
var count = p.readVarint();
var txs = [];
var i;
for (i = 0; i < count; i++)
txs.push(TX.fromRaw(p));
tip.network = network;
return new MinerBlock({
network: network,
tip: tip,
version: version,
target: bits,
address: address,
coinbaseFlags: coinbaseFlags,
witness: witness,
txs: txs
});
};
/**
* "Reverse" comparison so we don't have
* to waste time reversing the block hash.
* @memberof Miner
* @param {Buffer} a
* @param {Buffer} b
* @returns {Number}
*/
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 = MinerBlock;