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'; 'use strict';
exports.mine = require('./mine');
exports.Miner = require('./miner'); exports.Miner = require('./miner');
exports.MinerBlock = require('./minerblock'); 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 AsyncObject = require('../utils/async');
var MinerBlock = require('./minerblock'); var MinerBlock = require('./minerblock');
var Address = require('../primitives/address'); var Address = require('../primitives/address');
var Workers = require('../workers/workers');
var time = require('../net/timedata'); var time = require('../net/timedata');
/** /**
@ -58,7 +57,6 @@ function Miner(options) {
this.running = false; this.running = false;
this.timeout = null; this.timeout = null;
this.attempt = null; this.attempt = null;
this.workerPool = null;
this._init(); this._init();
} }
@ -113,21 +111,6 @@ Miner.prototype._init = function _init() {
stat.height, stat.height,
stat.best); 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.destroy();
this.attempt = null; this.attempt = null;
} }
if (this.workerPool)
this.workerPool.destroy();
}; };
/** /**
@ -262,7 +242,6 @@ Miner.prototype.createBlock = co(function* createBlock(tip) {
} }
attempt = new MinerBlock({ attempt = new MinerBlock({
workerPool: this.workerPool,
tip: tip, tip: tip,
version: version, version: version,
target: target, 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-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin * https://github.com/bcoin-org/bcoin
@ -15,15 +15,13 @@ var constants = require('../protocol/constants');
var Network = require('../protocol/network'); var Network = require('../protocol/network');
var bn = require('bn.js'); var bn = require('bn.js');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var TX = require('../primitives/tx'); var TX = require('../primitives/tx');
var Address = require('../primitives/address');
var Block = require('../primitives/block'); var Block = require('../primitives/block');
var Input = require('../primitives/input'); var Input = require('../primitives/input');
var Output = require('../primitives/output'); var Output = require('../primitives/output');
var time = require('../net/timedata'); var time = require('../net/timedata');
var ChainEntry = require('../chain/chainentry'); var mine = require('./mine');
var workers = require('../workers/workers');
/** /**
* MinerBlock * MinerBlock
@ -62,10 +60,7 @@ function MinerBlock(options) {
this.witness = options.witness; this.witness = options.witness;
this.address = options.address; this.address = options.address;
this.network = Network.get(options.network); this.network = Network.get(options.network);
this.timeout = null; this.destroyed = false;
if (typeof this.coinbaseFlags === 'string')
this.coinbaseFlags = new Buffer(this.coinbaseFlags, 'utf8');
this.coinbase = new TX(); this.coinbase = new TX();
this.coinbase.mutable = true; this.coinbase.mutable = true;
@ -78,6 +73,22 @@ function MinerBlock(options) {
utils.inherits(MinerBlock, EventEmitter); 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. * Initialize the block.
* @private * @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. * @returns {Boolean} Whether the nonce was found.
*/ */
MinerBlock.prototype.findNonce = function findNonce() { MinerBlock.prototype.findNonce = function findNonce() {
var tip = this.tip;
var block = this.block; var block = this.block;
var target = this.target; var target = this.target;
var data = block.abbr(); 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. // Track how long we've been at it.
this.begin = utils.now(); 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. for (;;) {
while (block.nonce <= 0xffffffff) { nonce = this.findNonce();
// 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 if (nonce !== -1)
block.nonce++; break;
// Update the raw buffer (faster than this.iterate();
// constantly serializing the headers).
data.writeUInt32LE(block.nonce, 76, true);
// Send progress report every so often.
if (block.nonce % 500000 === 0)
this.sendStatus();
} }
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. // Keep track of our iterations.
this.iterations++; this.iterations++;
@ -313,28 +418,17 @@ MinerBlock.prototype.findNonce = function findNonce() {
// update the timestamp. This improves // update the timestamp. This improves
// performance because we do not have to // performance because we do not have to
// recalculate the merkle root. // recalculate the merkle root.
now = time.now();
if (now > block.ts && now > tip.ts) { if (now > block.ts && now > tip.ts) {
block.ts = now; block.ts = now;
// Overflow the nonce // Overflow the nonce
block.nonce = 0; block.nonce = 0;
return false; return;
} }
// Overflow the nonce and increment the extraNonce. // Overflow the nonce and increment the extraNonce.
this.updateNonce(); 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`). * 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 * Destroy the minerblock. Stop mining.
* 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() { MinerBlock.prototype.destroy = function destroy() {
if (this.timeout) { this.destroyed = true;
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 * Expose
*/ */

View File

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

View File

@ -8,6 +8,7 @@
var ec = require('../crypto/ec'); var ec = require('../crypto/ec');
var scrypt = require('../crypto/scrypt'); var scrypt = require('../crypto/scrypt');
var mine = require('../miner/mine');
/** /**
* Jobs to execute within the worker. * Jobs to execute within the worker.
@ -116,17 +117,15 @@ jobs.ecSign = function ecSign(msg, key) {
/** /**
* Mine a block on worker. * Mine a block on worker.
* @param {Object} attempt - Naked {@link MinerBlock}. * @param {Buffer} data
* @returns {Block} * @param {Buffer} target
* @param {Number} min
* @param {Number} max
* @returns {Number}
*/ */
jobs.mine = function mine(attempt) { jobs.mine = function _mine(data, target, min, max) {
if (jobs.master) { return mine(data, target, min, max);
attempt.on('status', function(status) {
jobs.master.sendEvent('status', status);
});
}
return attempt.mineSync();
}; };
/** /**
@ -142,5 +141,5 @@ jobs.mine = function mine(attempt) {
*/ */
jobs.scrypt = function _scrypt(passwd, salt, N, r, p, len) { 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 utils = require('../utils/utils');
var assert = require('assert'); var assert = require('assert');
var BufferReader = require('../utils/reader'); var BufferReader = require('../utils/reader');
var Block = require('../primitives/block');
var MTX = require('../primitives/mtx'); var MTX = require('../primitives/mtx');
var TX = require('../primitives/tx'); var TX = require('../primitives/tx');
var KeyRing = require('../primitives/keyring'); var KeyRing = require('../primitives/keyring');
var Script = require('../script/script'); var Script = require('../script/script');
var Witness = require('../script/witness'); var Witness = require('../script/witness');
var MinerBlock = require('../miner/minerblock');
/** /**
* Parser * Parser
@ -164,16 +162,18 @@ Parser.parseItem = function parseItem(data) {
case 2: case 2:
return p.read32(); return p.read32();
case 3: case 3:
return p.readU8() === 1; return p.readU32();
case 4: case 4:
return p.readVarBytes(); return p.readU8() === 1;
case 5: case 5:
return p.readVarBytes();
case 6:
items = []; items = [];
count = p.readVarint(); count = p.readVarint();
for (i = 0; i < count; i++) for (i = 0; i < count; i++)
items.push(Parser.parseItem(p)); items.push(Parser.parseItem(p));
return items; return items;
case 6: case 7:
items = {}; items = {};
count = p.readVarint(); count = p.readVarint();
for (i = 0; i < count; i++) for (i = 0; i < count; i++)
@ -190,10 +190,6 @@ Parser.parseItem = function parseItem(data) {
case 43: case 43:
return TX.fromExtended(p, true); return TX.fromExtended(p, true);
case 44: case 44:
return Block.fromRaw(p);
case 45:
return MinerBlock.fromRaw(p);
case 46:
return KeyRing.fromRaw(p); return KeyRing.fromRaw(p);
default: default:
throw new Error('Bad type.'); throw new Error('Bad type.');

View File

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

View File

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