diff --git a/lib/bcoin/fullchain.js b/lib/bcoin/fullchain.js index 2fd7af43..fad1f171 100644 --- a/lib/bcoin/fullchain.js +++ b/lib/bcoin/fullchain.js @@ -498,7 +498,7 @@ Chain.prototype.height = function height() { return this.getTip().height; }; -Chain.prototype.target = function target(last) { +Chain.prototype.target = function target(last, block) { var proofOfWorkLimit = utils.toCompact(network.powLimit); var adjustmentInterval = network.powTargetTimespan / network.powTargetSpacing; var newBlockTs, heightFirst, first; @@ -512,7 +512,7 @@ Chain.prototype.target = function target(last) { if ((last.height + 1) % adjustmentInterval) { if (network.powAllowMinDifficultyBlocks) { // Special behavior for testnet: - newBlockTs = utils.now(); + newBlockTs = block ? block.ts : utils.now(); if (newBlockTs > last.ts + network.powTargetSpacing * 2) return proofOfWorkLimit; diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js new file mode 100644 index 00000000..2dbc25b8 --- /dev/null +++ b/lib/bcoin/miner.js @@ -0,0 +1,206 @@ +/** + * miner.js - simple miner for bcoin + * 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 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 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(); + } + } + + function addTX(tx) { + var s; + if (tx.height !== -1) + return; + s = tx.render().length; + if (block.size() + s > constants.blocks.maxSize) + return; + nextBlock.txs.push(tx); + fee.iadd(tx.getFee()); + // Update coinbase value + updateCoinbase(nextBlock); + // Update merkle root for new coinbase and new tx + updateMerkle(nextBlock); + } + + function newBlock() { + var coinbase, block, target; + + coinbase = bcoin.tx(); + + 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 + }); + + coinbase.output({ + address: options.address, + value: new bn(0) + }); + + 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, hps; + + 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++; + } + + hps = (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: nextBlock, + extraNonce: block.extraNonce, + hps: hps, + target: block.bits + }); + + 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; +}; + +module.exports = function mine(pool, options) { + var options, miner; + + options = { + address: options.address, + msg: options.msg || 'mined by bcoin', + log: options.log + }; + + miner = exports.miner(options); + + pool.on('tx', function(tx) { + miner.addTX(tx); + }); + + pool.on('block', function(block) { + miner.addBlock(block); + }); + + miner.on('block', function(block) { + pool.sendBlock(block); + }); + + miner.on('status', function(stat) { + if (options.log) + console.log('Hashes per second: %s', stat.hps); + }); + + miner.start(); + + return miner; +};