diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 5f4ee2d9..71a28a54 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -169,6 +169,7 @@ Chain.prototype._init = function _init() { self.loaded = true; self.emit('open'); + self.emit('tip', tip); if (self.isFull()) self.emit('full'); @@ -888,6 +889,7 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) { self.tip = entry; self.height = entry.height; + self.emit('tip', entry); return callback(); }); diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index bc2088da..ffa1ea5d 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -28,6 +28,7 @@ function Miner(node, options) { this.options = options; this.address = this.options.address; this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin'; + this.dsha256 = this.options.dsha256 || utils.dsha256; this.node = node; this.pool = node.pool; @@ -36,7 +37,6 @@ function Miner(node, options) { this.running = false; this.timeout = null; - this.interval = null; this.fee = new bn(0); this.last = this.node.chain.tip; @@ -66,10 +66,11 @@ Miner.prototype._init = function _init() { }); } - this.chain.on('add block', function(block) { + this.chain.on('tip', function(tip) { if (!self.running) return; - self.addBlock(block); + self.last = tip; + self.start(); }); this.on('block', function(block) { @@ -78,7 +79,7 @@ Miner.prototype._init = function _init() { block.height, block.hash('hex')); // Emit the block hex as a failsafe (in case we can't send it) - utils.debug('Block: %s', utils.toHex(block.render())); + utils.debug('Raw: %s', utils.toHex(block.render())); self.pool.sendBlock(block); }); @@ -94,21 +95,54 @@ Miner.prototype._init = function _init() { }; Miner.prototype.start = function start() { - var mempool = this.pool.loadMempool.bind(this.pool); + var self = this; + + // Wait for `tip`. + if (!this.last) + return; this.stop(); this.running = true; - // Ask our peers for mempool txs every so often. - this.interval = setInterval(mempool, 60 * 1000); - // Reset iterations this.iterations = 0; // Create a new block and start hashing - this.block = this.createBlock(); - this.iterate(); + 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.fillCoins(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() { @@ -119,152 +153,110 @@ Miner.prototype.stop = function stop() { clearTimeout(this.timeout); this.timeout = null; - - clearInterval(this.interval); - this.interval = null; -}; - -Miner.prototype.add = function add(msg) { - if (msg.type === 'tx') - return this.addTX(msg); - return this.addBlock(msg); -}; - -Miner.prototype.addBlock = function addBlock(block) { - if (!block) - block = this.chain.tip; - - if (!this.last) - this.last = this.chain.tip; - - // Somebody found the next block before - // us, start over with the new target. - if (block.height > this.last.height) { - this.last = block.type === 'block' - ? this.chain.getEntry(block) - : block; - assert(this.last); - this.start(); - } }; Miner.prototype.addTX = function addTX(tx) { - var flags, height, ts; - - flags = constants.flags.MANDATORY_VERIFY_FLAGS - | constants.flags.VERIFY_DERSIG - | constants.flags.VERIFY_CHECKLOCKTIMEVERIFY; - - // Cannot calculate fee if we don't have the prev_out. - // Could possibly just burn some coins. - if (this.options.burn === false) { - if (!tx.hasCoins()) - return false; - } - - // Ignore if it's already in a block - if (tx.height !== -1) - return false; - - if (!tx.verify(null, true, flags)) - return false; - - if (tx.isCoinbase()) - return false; - - // Get timestamp for tx.isFinal() - bip113 - height = this.last.height + 1; - ts = this.block.version >= 8 - ? this.last.getMedianTime() - : this.block.ts; - - if (!tx.isFinal(height, ts)) - return false; + var size = this.block.getVirtualSize() + tx.getVirtualSize(); // Deliver me from the block size debate, please - if (this.block.getSize() + tx.getSize() > constants.blocks.maxSize) + if (size > constants.blocks.maxSize) + return false; + + if (this.block._txMap[tx.hash('hex')]) return false; // Add the tx to our block this.block.txs.push(tx); + this.block._txMap[tx.hash('hex')] = true; // Calculate our new reward fee - if (tx.hasCoins()) - this.fee.iadd(tx.getFee()); + this.fee.iadd(tx.getFee()); // Update coinbase value this.updateCoinbase(); // Update merkle root for new coinbase and new tx this.updateMerkle(); + + return true; }; -Miner.prototype.createBlock = function createBlock() { +Miner.prototype.createBlock = function createBlock(callback) { + var self = this; var ts, target, coinbase, headers, block; ts = Math.max(utils.now(), this.last.ts + 1); // Find target - target = this.chain.getTarget(this.last, ts); + this.last.ensureAncestors(function(err) { + if (err) { + self.last.free(); + return callback(err); + } - // Create a coinbase - coinbase = bcoin.tx(); + target = self.chain.getTarget(self.last, ts); - coinbase.input({ - prevout: { - hash: utils.toHex(constants.zeroHash), - index: 0xffffffff - }, - script: [ - // Height (required in v2+ blocks) - bcoin.script.array(this.last.height + 1), - // extraNonce - incremented when - // the nonce overflows. - 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). - utils.nonce().toBuffer(), - // Let the world know this little - // miner succeeded. - new Buffer(this.coinbaseFlags || 'mined by bcoin', 'ascii') - ], - sequence: 0xffffffff + // Create a coinbase + coinbase = bcoin.mtx(); + + coinbase.addInput({ + prevout: { + hash: utils.toHex(constants.zeroHash), + index: 0xffffffff + }, + script: new bcoin.script([ + // Height (required in v2+ blocks) + bcoin.script.array(self.last.height + 1), + // extraNonce - incremented when + // the nonce overflows. + new Buffer([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). + utils.nonce().toBuffer(), + // Let the world know this little + // miner succeeded. + new Buffer(self.coinbaseFlags || 'mined by bcoin', 'ascii') + ]), + witness: new bcoin.script.witness([]), + sequence: 0xffffffff + }); + + coinbase.addOutput({ + address: self.address, + value: new bn(0) + }); + + // Create our block + headers = { + version: 4, + prevBlock: self.last.hash, + merkleRoot: utils.toHex(constants.zeroHash), + ts: ts, + bits: target, + nonce: 0 + }; + + block = bcoin.block(headers, 'block'); + block._txMap = {}; + + block.txs.push(coinbase); + + block.target = utils.fromCompact(target); + block.extraNonce = new bn(0, 'le'); + + // Update coinbase since our coinbase was added. + self.updateCoinbase(block); + + // Create our merkle root. + self.updateMerkle(block); + + self.last.free(); + + return callback(null, block); }); - - coinbase.addOutput({ - address: this.address, - value: new bn(0) - }); - - // Create our block - headers = { - version: 4, - prevBlock: this.last.hash, - merkleRoot: utils.toHex(constants.zeroHash), - ts: ts, - bits: target, - nonce: 0 - }; - - block = bcoin.block(headers, 'block'); - - delete block.valid; - - block.txs.push(coinbase); - - block.target = utils.fromCompact(target); - block.extraNonce = new bn(0, 'le'); - - // Update coinbase since our coinbase was added. - this.updateCoinbase(block); - - // Create our merkle root. - this.updateMerkle(block); - - return block; }; Miner.prototype.updateCoinbase = function updateCoinbase(block) { @@ -276,8 +268,12 @@ Miner.prototype.updateCoinbase = function updateCoinbase(block) { if (!block) block = this.block; + delete block._txMap[coinbase.hash('hex')]; + coinbase.inputs[0].script[1] = block.extraNonce.toBuffer(); coinbase.outputs[0].value = reward.add(this.fee); + + block._txMap[coinbase.hash('hex')] = true; }; Miner.prototype.updateMerkle = function updateMerkle(block) { @@ -301,24 +297,29 @@ Miner.prototype.iterate = function iterate() { hash = self.block.hash('hex'); // Make sure our block is valid - if (!self.block.verify()) - return utils.debug('%s did not verify.', hash); + if (!self.block.verify()) { + utils.debug('%s did not verify.', hash); + return self.emit('error', new Error(hash + ' did not verify.')); + } // Add our block to the chain - res = self.chain.add(self.block); - if (res > 0) - return utils.debug('%s could not be added to chain.', hash); + self.chain.add(self.block, self.pool.peers.load, function(err, total) { + if (err) + return self.emit('error', err); - // Emit our newly found block - self.emit('block', self.block); + if (total === 0) { + utils.debug('%s could not be added to chain.', hash); + return self.emit('error', + new Error(hash + ' could not be added to chain.')); + } - // Try to find a new block - self.last = self.chain.getEntry(self.block); - assert(self.last); - self.block = self.createBlock(); + // Emit our newly found block + self.emit('block', self.block); - return self.iterate(); - }, 10); + // `tip` will now be emitted by chain + // and the whole process starts over. + }); + }, 100); }; Miner.prototype.__defineGetter__('hashes', function() { @@ -344,7 +345,7 @@ Miner.prototype.sendStatus = function sendStatus() { }; Miner.prototype.findNonce = function findNonce() { - var data = new Buffer(this.block.render()); + var data = this.block.abbr(); var now; // Track how long we've been at it. @@ -353,7 +354,7 @@ Miner.prototype.findNonce = function findNonce() { // The heart and soul of the miner: match the target. while (this.block.nonce <= 0xffffffff) { // Hash and test against the next target - if (utils.testTarget(this.block.target, utils.dsha256(data))) + if (utils.testTarget(this.block.target, this.dsha256(data))) return true; // Increment the nonce to get a different hash