miner: refactor jobs and pool.

This commit is contained in:
Christopher Jeffrey 2016-10-05 16:16:08 -07:00
parent 0e8f4712e3
commit acae838059
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
9 changed files with 254 additions and 265 deletions

View File

@ -1,4 +1,5 @@
'use strict';
exports.mine = require('./mine');
exports.Miner = require('./miner');
exports.MinerBlock = require('./minerblock');

69
lib/miner/mine.js Normal file
View File

@ -0,0 +1,69 @@
/*!
* mine.js - mining function for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
var crypto = require('../crypto/crypto');
var assert = require('assert');
/**
* Hash until the nonce overflows.
* @param {Buffer} data
* @param {Buffer} target - Big endian.
* @param {Number} min
* @param {Number} max
* @returns {Number} Nonce or -1.
*/
function mine(data, target, min, max) {
var nonce = min;
data.writeUInt32LE(nonce, 76, true);
// The heart and soul of the miner: match the target.
while (nonce <= max) {
// Hash and test against the next target.
if (rcmp(crypto.hash256(data), target) <= 0)
return nonce;
// Increment the nonce to get a different hash
nonce++;
// Update the raw buffer (faster than
// constantly serializing the headers).
data.writeUInt32LE(nonce, 76, true);
}
return -1;
}
/**
* "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 = mine;

View File

@ -13,7 +13,6 @@ var assert = require('assert');
var AsyncObject = require('../utils/async');
var MinerBlock = require('./minerblock');
var Address = require('../primitives/address');
var Workers = require('../workers/workers');
var time = require('../net/timedata');
/**
@ -58,7 +57,6 @@ function Miner(options) {
this.running = false;
this.timeout = null;
this.attempt = null;
this.workerPool = null;
this._init();
}
@ -113,21 +111,6 @@ Miner.prototype._init = function _init() {
stat.height,
stat.best);
});
if (Workers.enabled) {
this.workerPool = new Workers({
size: 1,
timeout: -1
});
this.workerPool.on('error', function(err) {
self.emit('error', err);
});
this.workerPool.on('status', function(stat) {
self.emit('status', stat);
});
}
};
/**
@ -227,9 +210,6 @@ Miner.prototype.stop = function stop() {
this.attempt.destroy();
this.attempt = null;
}
if (this.workerPool)
this.workerPool.destroy();
};
/**
@ -262,7 +242,6 @@ Miner.prototype.createBlock = co(function* createBlock(tip) {
}
attempt = new MinerBlock({
workerPool: this.workerPool,
tip: tip,
version: version,
target: target,

View File

@ -1,5 +1,5 @@
/*!
* miner.js - inefficient miner for bcoin (because we can)
* minerblock.js - miner block object 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
@ -15,15 +15,13 @@ 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');
var mine = require('./mine');
var workers = require('../workers/workers');
/**
* MinerBlock
@ -62,10 +60,7 @@ function MinerBlock(options) {
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.destroyed = false;
this.coinbase = new TX();
this.coinbase.mutable = true;
@ -78,6 +73,22 @@ function MinerBlock(options) {
utils.inherits(MinerBlock, EventEmitter);
/**
* Nonce range interval.
* @const {Number}
* @default
*/
MinerBlock.INTERVAL = 0xffffffff / 1500 | 0;
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;
});
/**
* Initialize the block.
* @private
@ -265,43 +276,137 @@ MinerBlock.prototype.addTX = function addTX(tx) {
};
/**
* Hash until the nonce overflows, increment extraNonce, rebuild merkletree.
* Hash until the nonce overflows.
* @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;
var interval = MinerBlock.INTERVAL;
var min = 0;
var max = interval;
var nonce;
while (max <= 0xffffffff) {
nonce = mine(data, target, min, max);
if (nonce !== -1)
break;
block.nonce = max;
min += interval;
max += interval;
this.sendStatus();
}
return nonce;
};
/**
* Hash until the nonce overflows.
* @returns {Boolean} Whether the nonce was found.
*/
MinerBlock.prototype.findNonceAsync = co(function* findNonceAsync() {
var block = this.block;
var target = this.target;
var data = block.abbr();
var interval = MinerBlock.INTERVAL;
var min = 0;
var max = interval;
var nonce;
while (max <= 0xffffffff) {
nonce = yield workers.pool.mine(data, target, min, max);
if (this.destroyed)
throw new Error('Miner was interrupted.');
if (nonce !== -1)
break;
block.nonce = max;
min += interval;
max += interval;
this.sendStatus();
}
return nonce;
});
/**
* Mine synchronously until the block is found.
* @returns {Block}
*/
MinerBlock.prototype.mine = function mine() {
var nonce;
// Track how long we've been at it.
this.begin = utils.now();
assert(block.ts > tip.ts);
assert(this.block.ts > this.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;
}
for (;;) {
nonce = this.findNonce();
// Increment the nonce to get a different hash
block.nonce++;
if (nonce !== -1)
break;
// 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();
this.iterate();
}
this.block.nonce = nonce;
this.block.mutable = false;
this.coinbase.mutable = false;
return this.block;
};
/**
* Mine asynchronously until the block is found.
* @returns {Promise} - Returns {@link Block}.
*/
MinerBlock.prototype.mineAsync = co(function* mineAsync() {
var nonce;
// Track how long we've been at it.
this.begin = utils.now();
assert(this.block.ts > this.tip.ts);
for (;;) {
nonce = yield this.findNonceAsync();
if (nonce !== -1)
break;
this.iterate();
}
this.block.nonce = nonce;
this.block.mutable = false;
this.coinbase.mutable = false;
return this.block;
});
/**
* Increment extraNonce, rebuild merkletree.
*/
MinerBlock.prototype.iterate = function iterate() {
var block = this.block;
var tip = this.tip;
var now = time.now();
// Keep track of our iterations.
this.iterations++;
@ -313,28 +418,17 @@ MinerBlock.prototype.findNonce = function findNonce() {
// 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;
return;
}
// 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`).
*/
@ -351,165 +445,13 @@ MinerBlock.prototype.sendStatus = function sendStatus() {
};
/**
* 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.
* Destroy the minerblock. Stop mining.
*/
MinerBlock.prototype.destroy = function destroy() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.block = null;
this.destroyed = true;
};
/**
* 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
*/

View File

@ -12,13 +12,11 @@ var bn = require('bn.js');
var utils = require('../utils/utils');
var assert = require('assert');
var BufferWriter = require('../utils/writer');
var Block = require('../primitives/block');
var MTX = require('../primitives/mtx');
var TX = require('../primitives/tx');
var KeyRing = require('../primitives/keyring');
var Script = require('../script/script');
var Witness = require('../script/witness');
var MinerBlock = require('../miner/minerblock');
/**
* Framer
@ -66,11 +64,16 @@ Framer.item = function _item(item, writer) {
p.writeVarString(item, 'utf8');
break;
case 'number':
p.writeU8(2);
p.write32(item);
if (item > 0x7fffffff) {
p.writeU8(3);
p.writeU32(item);
} else {
p.writeU8(2);
p.write32(item);
}
break;
case 'boolean':
p.writeU8(3);
p.writeU8(4);
p.writeU8(item ? 1 : 0);
break;
case 'object':
@ -91,29 +94,23 @@ Framer.item = function _item(item, writer) {
} else if (item instanceof TX) {
p.writeU8(43);
item.toExtended(true, p);
} else if (item instanceof Block) {
p.writeU8(44);
item.toRaw(p);
} else if (item instanceof MinerBlock) {
p.writeU8(45);
item.toRaw(p);
} else if (item instanceof KeyRing) {
p.writeU8(46);
p.writeU8(44);
item.toRaw(p);
} else if (bn.isBN(item)) {
p.writeU8(10);
p.writeVarBytes(item.toArrayLike(Buffer));
} else if (Buffer.isBuffer(item)) {
p.writeU8(4);
p.writeU8(5);
p.writeVarBytes(item);
} else if (Array.isArray(item)) {
p.writeU8(5);
p.writeU8(6);
p.writeVarint(item.length);
for (i = 0; i < item.length; i++)
Framer.item(item[i], p);
} else {
keys = Object.keys(item);
p.writeU8(6);
p.writeU8(7);
p.writeVarint(keys.length);
for (i = 0; i < keys.length; i++) {
p.writeVarString(keys[i], 'utf8');

View File

@ -8,6 +8,7 @@
var ec = require('../crypto/ec');
var scrypt = require('../crypto/scrypt');
var mine = require('../miner/mine');
/**
* Jobs to execute within the worker.
@ -116,17 +117,15 @@ jobs.ecSign = function ecSign(msg, key) {
/**
* Mine a block on worker.
* @param {Object} attempt - Naked {@link MinerBlock}.
* @returns {Block}
* @param {Buffer} data
* @param {Buffer} target
* @param {Number} min
* @param {Number} max
* @returns {Number}
*/
jobs.mine = function mine(attempt) {
if (jobs.master) {
attempt.on('status', function(status) {
jobs.master.sendEvent('status', status);
});
}
return attempt.mineSync();
jobs.mine = function _mine(data, target, min, max) {
return mine(data, target, min, max);
};
/**
@ -142,5 +141,5 @@ jobs.mine = function mine(attempt) {
*/
jobs.scrypt = function _scrypt(passwd, salt, N, r, p, len) {
return scrypt(passwd, salt, N >>> 0, r >>> 0, p >>> 0, len);
return scrypt(passwd, salt, N, r, p, len);
};

View File

@ -12,13 +12,11 @@ var bn = require('bn.js');
var utils = require('../utils/utils');
var assert = require('assert');
var BufferReader = require('../utils/reader');
var Block = require('../primitives/block');
var MTX = require('../primitives/mtx');
var TX = require('../primitives/tx');
var KeyRing = require('../primitives/keyring');
var Script = require('../script/script');
var Witness = require('../script/witness');
var MinerBlock = require('../miner/minerblock');
/**
* Parser
@ -164,16 +162,18 @@ Parser.parseItem = function parseItem(data) {
case 2:
return p.read32();
case 3:
return p.readU8() === 1;
return p.readU32();
case 4:
return p.readVarBytes();
return p.readU8() === 1;
case 5:
return p.readVarBytes();
case 6:
items = [];
count = p.readVarint();
for (i = 0; i < count; i++)
items.push(Parser.parseItem(p));
return items;
case 6:
case 7:
items = {};
count = p.readVarint();
for (i = 0; i < count; i++)
@ -190,10 +190,6 @@ Parser.parseItem = function parseItem(data) {
case 43:
return TX.fromExtended(p, true);
case 44:
return Block.fromRaw(p);
case 45:
return MinerBlock.fromRaw(p);
case 46:
return KeyRing.fromRaw(p);
default:
throw new Error('Bad type.');

View File

@ -44,6 +44,7 @@ function Workers(options) {
this.timeout = options.timeout || 60000;
this.children = [];
this.nonce = 0;
this.enabled = true;
}
utils.inherits(Workers, EventEmitter);
@ -220,7 +221,7 @@ Workers.prototype.destroy = function destroy() {
Workers.prototype.execute = function execute(method, args, timeout) {
var result, child;
if (!Workers.enabled) {
if (!this.enabled || !Workers.support) {
return new Promise(function(resolve, reject) {
utils.nextTick(function() {
try {
@ -338,12 +339,15 @@ Workers.prototype.ecSign = function ecSign(msg, key) {
/**
* Execute the mining job (no timeout).
* @param {MinerBlock} attempt
* @returns {Promise} - Returns {@link MinerBlock}.
* @param {Buffer} data
* @param {Buffer} target
* @param {Number} min
* @param {Number} max
* @returns {Promise} - Returns {Number}.
*/
Workers.prototype.mine = function mine(attempt) {
return this.execute('mine', [attempt], -1);
Workers.prototype.mine = function mine(data, target, min, max) {
return this.execute('mine', [data, target, min, max], -1);
};
/**
@ -861,22 +865,24 @@ function fromError(err) {
*/
Workers.pool = new Workers();
Workers.enabled = false;
Workers.pool.enabled = false;
Workers.support = true;
if (utils.isBrowser) {
Workers.support = typeof global.Worker === 'function'
|| typeof global.postMessage === 'function';
}
Workers.set = function set(options) {
if (typeof options.useWorkers === 'boolean')
this.enabled = options.useWorkers;
this.pool.enabled = options.useWorkers;
if (utils.isNumber(options.maxWorkers))
this.pool.size = options.maxWorkers;
if (utils.isNumber(options.workerTimeout))
this.pool.timeout = options.workerTimeout;
if (utils.isBrowser && this.enabled) {
this.enabled = typeof global.Worker === 'function'
|| typeof global.postMessage === 'function';
}
};
Workers.set({

View File

@ -27,7 +27,7 @@ describe('Chain', function() {
var redeemer;
if (!tx)
return attempt.mineSync();
return yield attempt.mineAsync();
redeemer = bcoin.mtx();
@ -49,7 +49,7 @@ describe('Chain', function() {
attempt.addTX(redeemer.toTX());
return attempt.mineSync();
return yield attempt.mineAsync();
});
function deleteCoins(tx) {