diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 08950072..0c8c2558 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -381,6 +381,13 @@ Block.prototype.__defineGetter__('fee', function() { return this.getReward().fee; }); +Block.prototype.__defineGetter__('coinbase', function() { + var tx = this.txs[0]; + if (!tx || !tx.isCoinbase()) + return; + return tx; +}); + Block.prototype.inspect = function inspect() { var copy = bcoin.block(this, this.subtype); copy.__proto__ = null; diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index 2e7772a5..3e0cbdb4 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -1,212 +1,310 @@ /** - * miner.js - simple miner for bcoin + * 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 network = bcoin.protocol.network; -var constants = bcoin.protocol.constants; var utils = bcoin.utils; +var assert = utils.assert; +var constants = bcoin.protocol.constants; + var bn = require('bn.js'); var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; -var assert = utils.assert; /** * Miner */ -exports.miner = function miner(options, callback) { - var chain = bcoin.chain.global; - var e = new EventEmitter; - var fee = new bn(0); - var last = chain.getTip(); - var nextBlock, timeout, blockChecker; +function Miner(options) { + if (!(this instanceof Miner)) + return new Miner(options); - function addBlock(b) { - var block = b || chain.getTip(); - if (block.height > last.height) { - last = block; - // Somebody found the next block before us, - // start over with new target. - stop(); - start(); - } - } + if (!options) + options = {}; - function addTX(tx) { - if (tx.inputs[0].output && !tx.verify()) - return; + this.options = options; + this.address = this.options.address; + this.msg = this.options.msg || 'mined by bcoin'; - if (tx.height !== -1) - return; + this.chain = options.chain || bcoin.chain.global; + this.pool = options.pool || bcoin.pool.global; - if (block.size() + tx.size() > constants.blocks.maxSize) - return; + this.running = false; + this.timeout = null; + this.interval = null; - nextBlock.txs.push(tx); + this.fee = new bn(0); + this.last = this.chain.getTip(); + this.block = null; + this.rate = 0; +} - fee.iadd(tx.getFee()); +inherits(Miner, EventEmitter); - // Update coinbase value - updateCoinbase(nextBlock); - // Update merkle root for new coinbase and new tx - updateMerkle(nextBlock); - } +Miner.prototype._init = function _init() { + var self = this; - function newBlock() { - var coinbase, block, target; + this.pool.on('tx', function(tx) { + self.addTX(tx); + }); - coinbase = bcoin.tx(); + this.chain.on('block', function(block) { + self.addBlock(block); + }); - coinbase.input({ - out: { - hash: constants.zeroHash, - index: 0xffffffff - }, - script: [ - new bn(last.height + 1).toArray().reverse(), - [], - utils.ascii2array(options.msg || 'mined by bcoin') - ], - seq: 0xffffffff - }); + // this.chain.on('tip', function(entry) { + // self.addBlock(entry); + // }); - coinbase.output({ - address: options.address, - value: new bn(0) - }); + this.on('block', function(block) { + self.emit('debug', + 'Found block: %d (%s)', + self.last.height + 1, + block.hash('hex')); + self.pool.sendBlock(block); + }); - target = chain.target(last); - - block = { - version: 4, - prevBlock: last.verify ? last.hash('hex') : last.hash, - merkleRoot: constants.zeroHash.slice(), - ts: utils.now(), - bits: utils.toCompact(target), - nonce: 0 - }; - - block = bcoin.block(block, 'block'); - delete block.valid; - block.verify = block._verify; - - block.target = target; - block.extraNonce = new bn(0); - - block.txs.push(coinbase); - - // Update coinbase since our coinbase was added. - updateCoinbase(block); - // Create our merkle root. - updateMerkle(block); - - return block; - } - - function updateCoinbase(block) { - var coinbase = block.txs[0]; - coinbase.inputs[0].script[1] = block.extraNonce.toArray(); - coinbase.outputs[0].value = bcoin.block.reward(last.height + 1).add(fee); - } - - function updateMerkle(block) { - block.ts = utils.now(); - block.merkleRoot = block.getMerkleRoot(); - } - - function iter(block) { - timeout = setTimeout(function() { - var hash, begin, rate; - - begin = utils.now(); - - while (block.nonce <= 0xffffffff) { - if (utils.testTarget(block.target, block.hash())) { - e.emit('block', block); - last = block; - nextBlock = newBlock(); - return iter(nextBlock); - } - block.nonce++; - } - - rate = (0xffffffff / (utils.now() - begin)) * 2; - - block.nonce = 0; - block.extraNonce.iaddn(1); - - // We incremented the extraNonce, need to update coinbase. - updateCoinbase(block); - // We changed the coinbase, need to update merkleRoot. - updateMerkle(block); - - e.emit('status', { - block: block, - target: utils.fromCompact(block.bits), - hashes: block.extraNonce.mul(0xffffffff), - hashrate: rate - }); - - return iter(block); - }, 10); - } - - function start() { - blockChecker = setInterval(addBlock, 1000); - nextBlock = newBlock(); - iter(nextBlock); - } - - function stop() { - if (!timeout) - return; - clearTimeout(timeout); - timeout = null; - clearInterval(blockChecker); - blockChecker = null; - } - - e.addBlock = addBlock; - e.add = e.addTX = addTX; - e.start = start; - e.stop = stop; - - return e; + this.on('status', function(stat) { + self.emit('debug', 'Hashes per second: %s', stat.hashrate); + }); }; -module.exports = function mine(pool, options) { - var options, miner; +Miner.prototype.start = function start() { + var mempool = this.pool.loadMempool.bind(this.pool); - options = { - address: options.address, - msg: options.msg || 'mined by bcoin', - log: options.log + this.stop(); + + this.running = true; + + // Ask our peers for mempool txs every so often. + this.interval = setInterval(mempool, 60 * 1000); + + // Create a new block and start hashing + this.block = this.createBlock(); + this.iterate(); +}; + +Miner.prototype.stop = function stop() { + if (!this.running) + return; + + this.running = false; + + 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.getTip(); + + // Somebody found the next block before + // us, start over with the new target. + if (block.height > this.last.height) { + this.last = block.verify + ? this.chain.getBlock(block) + : block; + assert(this.last); + this.start(); + } +}; + +Miner.prototype.addTX = function addTX(tx) { + var full = this.index.inputs.every(function(input) { + return !!input.out.tx; + }); + + // Cannot calculate fee if we don't have the prev_out. + // Could possibly just burn some coins. + if (this.options.burn === false) { + if (!full) + return; + } + + // Pretty important + if (!tx.verify()) + return; + + // Ignore if it's already in a block + if (tx.height !== -1) + return; + + // Deliver me from the block size debate, please + if (this.block.size() + tx.size() > constants.blocks.maxSize) + return; + + // Add the tx to our block + this.block.txs.push(tx); + + // Calulcate our new reward fee + if (full) + this.fee.iadd(tx.getFee()); + + // Update coinbase value + this.updateCoinbase(); + + // Update merkle root for new coinbase and new tx + this.updateMerkle(); +}; + +Miner.prototype.createBlock = function createBlock(tx) { + var target, coinbase, headers, block; + + // Update target + target = this.chain.target(this.last); + + // Create a coinbase + coinbase = bcoin.tx(); + + coinbase.input({ + out: { + hash: utils.toHex(constants.zeroHash), + index: 0xffffffff + }, + script: [ + new bn(this.last.height + 1).toArray().reverse(), + [], + utils.ascii2array(this.msg || 'mined by bcoin') + ], + seq: 0xffffffff + }); + + coinbase.output({ + address: this.address, + value: new bn(0) + }); + + // Create our block + headers = { + version: 4, + prevBlock: this.last.verify + ? this.last.hash('hex') + : this.last.hash, + merkleRoot: utils.toHex(constants.zeroHash.slice()), + ts: utils.now(), + bits: utils.toCompact(target), + nonce: 0 }; - miner = exports.miner(options); + block = bcoin.block(headers, 'block'); - // Use mempool - pool.on('tx', function(tx) { - miner.addTX(tx); - }); + delete block.valid; - pool.chain.on('tip', function(block) { - miner.addBlock(block); - }); + block.txs.push(coinbase); - miner.on('block', function(block) { - pool.sendBlock(block); - }); + // Update coinbase since our coinbase was added. + this.updateCoinbase(block); - miner.on('status', function(stat) { - if (options.log) - console.log('Hashes per second: %s', stat.hashrate); - }); + // Create our merkle root. + this.updateMerkle(block); - miner.start(); + block.target = target; + block.extraNonce = new bn(0); - return miner; + return block; }; + +Miner.prototype.updateCoinbase = function updateCoinbase(block) { + var coinbase = block.coinbase; + + assert(coinbase); + + if (!block) + block = this.block; + + coinbase.inputs[0].script[1] = block.extraNonce.toArray(); + coinbase.outputs[0].value = bcoin.block.reward(this.last.height + 1).add(fee); +}; + +Miner.prototype.updateMerkle = function updateMerkle(block) { + if (!block) + block = this.block; + + block.ts = utils.now(); + block.merkleRoot = block.getMerkleRoot(); +}; + +Miner.prototype.iterate = function iterate() { + var self = this; + + this.timeout = setTimeout(function() { + var hash; + + // Try to find a block: do one iteration of extraNonce + if (!self.findNonce()) + return self.iterate(); + + hash = self.block.hash('hex'); + + // Make sure our block is valid + if (!self.block.verify()) + return self.emit('debug', '%s did not verify.', hash); + + // Add our block to the chain + res = self.chain.add(self.block); + if (res > 0) + return self.emit('debug', '%s could not be added to chain.', hash); + + // Emit our newly found block + self.emit('block', self.block); + + // Try to find a new block + self.last = self.chain.getBlock(self.block); + assert(self.last); + self.block = self.createBlock(); + + return self.iterate(); + }, 10); +}; + +Miner.prototype.findNonce = function findNonce() { + var begin = utils.now(); + + // The heart and soul of the miner: match the target. + while (this.block.nonce <= 0xffffffff) { + if (utils.testTarget(this.block.target, this.block.hash())) + return true; + + this.block.nonce++; + } + + // Calculate our terrible hashrate + this.rate = (0xffffffff / (utils.now() - begin)) * 2; + + // 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(); + + // Send progress report + this.emit('status', { + block: this.block, + target: this.block.target, + hashes: this.block.extraNonce.mul(0xffffffff).toString(10), + hashrate: this.rate + }); + + return false; +}; + +/** + * Expose + */ + +module.exports = Miner; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 6650f492..e7296264 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -563,6 +563,13 @@ Peer.prototype.loadItems = function loadItems(hashes, stop) { return this.loadBlocks(hashes, stop); }; +Peer.prototype.loadMempool = function loadMempool() { + this.emit('debug', + 'Requesting inv packet from %s with mempool', + this.host); + this._write(this.framer.mempool()); +}; + /** * Expose */ diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 116248f0..88e74c04 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -596,6 +596,12 @@ Pool.prototype._load = function _load() { return true; }; +Pool.prototype.loadMempool = function loadMempool() { + this.pool.peers.block.forEach(function(peer) { + peer.loadMempool(); + }); +}; + Pool.prototype._addPeer = function _addPeer(backoff) { var self = this; var peer; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index c536be9c..dfb49abf 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -390,6 +390,10 @@ Framer.prototype.addr = function addr(peers) { return this.packet('addr', p); }; +Framer.prototype.mempool = function mempool() { + return this.packet('mempool', []); +}; + /** * Expose */ diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index ed470489..868cb0da 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -515,6 +515,13 @@ Parser.prototype.parseAddr = function parseAddr(p) { return addrs; }; +Parser.prototype.parseMempool = function parseMempool(p) { + if (p.length > 0) + return this._error('Invalid mempool size'); + + return {}; +}; + /** * Expose */