refactor: mining.

This commit is contained in:
Christopher Jeffrey 2017-03-10 03:57:44 -08:00
parent 50d47dd5a6
commit 945fa381c5
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
11 changed files with 1492 additions and 1147 deletions

View File

@ -17,4 +17,5 @@ exports.RPCClient = require('./rpcclient');
exports.Wallet = require('./wallet');
exports.Base = require('./base');
exports.RPC = require('./rpc');
exports.RPCBase = require('./rpcbase');
exports.Server = require('./server');

View File

@ -20,7 +20,6 @@ var Block = require('../primitives/block');
var Headers = require('../primitives/headers');
var Input = require('../primitives/input');
var KeyRing = require('../primitives/keyring');
var Lock = require('../utils/lock');
var MerkleBlock = require('../primitives/merkleblock');
var MTX = require('../primitives/mtx');
var Network = require('../protocol/network');
@ -64,7 +63,9 @@ function RPC(node) {
this.attempt = null;
this.lastActivity = 0;
this.boundChain = false;
this.nonces = {};
this.nonce1 = 0;
this.nonce2 = 0;
this.jobs = {};
this.pollers = [];
this.init();
@ -920,38 +921,43 @@ RPC.prototype.submitWork = co(function* submitWork(data) {
RPC.prototype._submitWork = co(function* _submitWork(data) {
var attempt = this.attempt;
var block, entry, header, nonce, ts, nonces;
var header = Headers.fromAbbr(data);
var nonce = header.nonce;
var ts = header.ts;
var job, n1, n2, hash, block, entry;
if (!attempt)
return false;
block = attempt.block;
header = Headers.fromAbbr(data);
nonce = header.nonce;
ts = header.ts;
if (data.length !== 128)
throw new RPCError('Invalid work size.');
data = data.slice(0, 80);
data = swap32(data);
if (header.prevBlock !== block.prevBlock
|| header.bits !== block.bits) {
if (header.prevBlock !== attempt.prevBlock
|| header.bits !== attempt.bits) {
return false;
}
if (!header.verify())
return false;
nonces = this.nonces[header.merkleRoot];
job = this.jobs[header.merkleRoot];
if (!nonces)
if (!job)
return false;
if (!nonces.verify(attempt, nonce, ts))
n1 = job.nonce1;
n2 = job.nonce2;
hash = attempt.hash(n1, n2, ts, nonce);
if (!consensus.verifyPOW(hash, attempt.bits))
return false;
block = attempt.commit(n1, n2, ts, nonce);
try {
entry = yield this.chain.add(block);
} catch (err) {
@ -982,14 +988,17 @@ RPC.prototype.createWork = co(function* createWork(data) {
});
RPC.prototype._createWork = co(function* _createWork() {
var attempt = yield this.updateAttempt();
var data, abbr;
var attempt = yield this.updateWork();
var n1 = this.nonce1;
var n2 = this.nonce2;
var ts = attempt.ts;
var data, head;
data = new Buffer(128);
data.fill(0);
abbr = attempt.block.abbr();
abbr.copy(data, 0);
head = attempt.getHeader(n1, n2, ts, 0);
head.copy(data, 0);
data[80] = 0x80;
data.writeUInt32BE(80 * 8, data.length - 4, true);
@ -1162,9 +1171,8 @@ RPC.prototype.createTemplate = co(function* createTemplate(version, coinbase, ru
});
RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, rules) {
var attempt = yield this.getAttempt();
var attempt = yield this.getTemplate();
var scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR;
var block = attempt.block;
var mutable = ['time', 'transactions', 'prevblock'];
var txs = [];
var index = {};
@ -1217,11 +1225,11 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase,
case common.thresholdStates.FAILED:
break;
case common.thresholdStates.LOCKED_IN:
block.version |= 1 << deploy.bit;
attempt.version |= 1 << deploy.bit;
case common.thresholdStates.STARTED:
if (!deploy.force) {
if (rules.indexOf(name) === -1)
block.version &= ~(1 << deploy.bit);
attempt.version &= ~(1 << deploy.bit);
name = '!' + name;
}
vbavailable[name] = deploy.bit;
@ -1240,24 +1248,24 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase,
}
}
block.version >>>= 0;
attempt.version >>>= 0;
json = {
capabilities: ['proposal'],
mutable: mutable,
version: block.version,
version: attempt.version,
rules: vbrules,
vbavailable: vbavailable,
vbrequired: 0,
height: attempt.height,
previousblockhash: util.revHex(block.prevBlock),
previousblockhash: util.revHex(attempt.prevBlock),
target: util.revHex(attempt.target.toString('hex')),
bits: util.hex32(block.bits),
bits: util.hex32(attempt.bits),
noncerange: '00000000ffffffff',
curtime: block.ts,
mintime: block.ts,
maxtime: block.ts + 7200,
expires: block.ts + 7200,
curtime: attempt.ts,
mintime: attempt.ts,
maxtime: attempt.ts + 7200,
expires: attempt.ts + 7200,
sigoplimit: consensus.MAX_BLOCK_SIGOPS_COST / scale | 0,
sizelimit: consensus.MAX_BLOCK_SIZE,
weightlimit: undefined,
@ -1266,7 +1274,7 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase,
coinbaseaux: {
flags: attempt.coinbaseFlags.toString('hex')
},
coinbasevalue: attempt.coinbase.getOutputValue(),
coinbasevalue: attempt.getReward(),
coinbasetxn: undefined,
default_witness_commitment: undefined,
transactions: txs
@ -1280,13 +1288,14 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase,
}
if (coinbase) {
tx = attempt.coinbase;
tx = attempt.toCoinbase();
// We don't include the commitment
// output (see bip145).
if (attempt.witness) {
output = tx.outputs.pop();
assert(output.script.isCommitment());
tx.refresh();
}
json.coinbasetxn = {
@ -1298,13 +1307,10 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase,
sigops: tx.getSigopsCost() / scale | 0,
weight: tx.getWeight()
};
if (attempt.witness)
tx.outputs.push(output);
}
if (attempt.witness) {
tx = attempt.coinbase;
tx = attempt.toCoinbase();
output = tx.outputs[tx.outputs.length - 1];
assert(output.script.isCommitment());
json.default_witness_commitment = output.script.toJSON();
@ -1315,21 +1321,19 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase,
RPC.prototype.getMiningInfo = co(function* getMiningInfo(args, help) {
var attempt = this.attempt;
var hashps;
var scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR;
if (help || args.length !== 0)
throw new RPCError('getmininginfo');
hashps = yield this.getHashRate(120);
return {
blocks: this.chain.height,
currentblocksize: attempt ? attempt.block.getBaseSize() : 0,
currentblocktx: attempt ? attempt.block.txs.length : 0,
currentblocksize: attempt ? attempt.weight / scale | 0 : 0,
currentblocktx: attempt ? attempt.items.length + 1 : 0,
difficulty: this.difficulty(),
errors: '',
genproclimit: this.procLimit,
networkhashps: hashps,
networkhashps: yield this.getHashRate(120),
pooledtx: this.totalTX(),
testnet: this.network !== Network.main,
chain: 'main',
@ -2113,7 +2117,9 @@ RPC.prototype.refreshBlock = function refreshBlock() {
this.attempt = null;
this.lastActivity = 0;
this.coinbase = {};
this.jobs = {};
this.nonce1 = 0;
this.nonce2 = 0;
this.pollers = [];
for (i = 0; i < pollers.length; i++) {
@ -2149,7 +2155,7 @@ RPC.prototype.bindChain = function bindChain() {
});
};
RPC.prototype.getAttempt = co(function* getAttempt() {
RPC.prototype.getTemplate = co(function* getTemplate() {
var attempt = this.attempt;
this.bindChain();
@ -2163,22 +2169,31 @@ RPC.prototype.getAttempt = co(function* getAttempt() {
return attempt;
});
RPC.prototype.updateAttempt = co(function* updateAttempt() {
RPC.prototype.updateWork = co(function* updateWork() {
var attempt = this.attempt;
var root;
this.bindChain();
if (attempt) {
attempt.updateNonce();
this.nonces[block.merkleRoot] = attempt.snapshot();
if (++this.nonce1 === 0xffffffff) {
this.nonce1 = 0;
this.nonce2++;
}
root = attempt.getRoot(this.nonce1, this.nonce2);
root = root.toString('hex');
this.jobs[root] = new Nonces(this);
return attempt;
}
attempt = yield this.miner.createBlock();
root = attempt.getRoot(this.nonce1, this.nonce2);
root = root.toString('hex');
this.attempt = attempt;
this.lastActivity = util.now();
this.nonces[block.merkleRoot] = attempt.snapshot();
this.jobs[root] = new Nonces(this);
return attempt;
});
@ -2530,6 +2545,11 @@ function toDeployment(id, version, status) {
};
}
function Nonces(rpc) {
this.nonce1 = rpc.nonce1;
this.nonce2 = rpc.nonce2;
}
/*
* Expose
*/

562
lib/mining/cpuminer.js Normal file
View File

@ -0,0 +1,562 @@
/*!
* cpuminer.js - inefficient cpu miner for bcoin (because we can)
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var util = require('../utils/util');
var co = require('../utils/co');
var AsyncObject = require('../utils/asyncobject');
var workerPool = require('../workers/workerpool').pool;
var mine = require('./mine');
/**
* CPU miner.
* @alias module:mining.CPUMiner
* @constructor
* @param {Miner} miner
* @emits CPUMiner#block
* @emits CPUMiner#status
*/
function CPUMiner(miner) {
if (!(this instanceof CPUMiner))
return new CPUMiner(miner);
AsyncObject.call(this);
this.miner = miner;
this.network = this.miner.network;
this.logger = this.miner.logger;
this.chain = this.miner.chain;
this.running = false;
this.stopping = false;
this.job = null;
this.since = 0;
this._init();
}
util.inherits(CPUMiner, AsyncObject);
/**
* Nonce range interval.
* @const {Number}
* @default
*/
CPUMiner.INTERVAL = 0xffffffff / 1500 | 0;
/**
* Initialize the miner.
* @private
*/
CPUMiner.prototype._init = function _init() {
var self = this;
this.chain.on('tip', function(tip) {
if (!self.job)
return;
if (self.job.attempt.prevBlock === tip.prevBlock)
self.job.destroy();
});
this.on('block', function(block, entry) {
// Emit the block hex as a failsafe (in case we can't send it)
self.logger.info('Found block: %d (%s).', entry.height, entry.rhash());
self.logger.debug('Raw: %s', block.toRaw().toString('hex'));
});
this.on('status', function(stat) {
// Send progress report.
self.logger.info(
'CPUMiner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s',
stat.hashrate / 1000 | 0,
stat.hashes,
stat.target,
stat.height,
stat.best);
});
};
/**
* Open the miner.
* @method
* @alias module:mining.CPUMiner#open
* @returns {Promise}
*/
CPUMiner.prototype._open = co(function* open() {
});
/**
* Close the miner.
* @method
* @alias module:mining.CPUMiner#close
* @returns {Promise}
*/
CPUMiner.prototype._close = co(function* close() {
if (!this.running)
return;
if (this.stopping) {
yield this._onStop();
return;
}
yield this.stop();
});
/**
* Start mining.
* @method
* @returns {Promise}
*/
CPUMiner.prototype.start = co(function* start() {
var block, entry;
assert(!this.running, 'CPUMiner is already running.');
this.running = true;
this.stopping = false;
for (;;) {
this.job = null;
try {
this.job = yield this.createJob();
} catch (e) {
if (this.stopping)
break;
this.emit('error', e);
continue;
}
if (this.stopping)
break;
try {
block = yield this.mineAsync(this.job);
} catch (e) {
if (this.stopping)
break;
this.emit('error', e);
continue;
}
if (this.stopping)
break;
if (!block)
continue;
try {
entry = yield this.chain.add(block);
} catch (e) {
if (this.stopping)
break;
this.emit('error', e);
continue;
}
if (!entry) {
this.logger.warning('Mined a bad-prevblk (race condition?)');
continue;
}
if (this.stopping)
break;
this.emit('block', block, entry);
}
this.emit('done');
});
/**
* Stop mining.
* @method
* @returns {Promise}
*/
CPUMiner.prototype.stop = co(function* stop() {
assert(this.running, 'CPUMiner is not running.');
assert(!this.stopping, 'CPUMiner is already stopping.');
this.stopping = true;
yield this._onDone();
this.running = false;
this.stopping = false;
this.job = null;
this.emit('stop');
});
/**
* Wait for `done` event.
* @private
* @returns {Promise}
*/
CPUMiner.prototype._onDone = function _onDone() {
var self = this;
return new Promise(function(resolve, reject) {
self.once('done', resolve);
});
};
/**
* Wait for `stop` event.
* @private
* @returns {Promise}
*/
CPUMiner.prototype._onStop = function _onStop() {
var self = this;
return new Promise(function(resolve, reject) {
self.once('stop', resolve);
});
};
/**
* Create a mining job.
* @method
* @param {ChainEntry?} tip
* @param {Address?} address
* @returns {Promise} - Returns {@link Job}.
*/
CPUMiner.prototype.createJob = co(function* createJob(tip, address) {
var attempt = yield this.miner.createBlock(tip, address);
return new CPUJob(this, attempt);
});
/**
* Mine a single block.
* @method
* @param {ChainEntry?} tip
* @param {Address?} address
* @returns {Promise} - Returns [{@link Block}].
*/
CPUMiner.prototype.mineBlock = co(function* mineBlock(tip, address) {
var job = yield this.createJob(tip, address);
return yield this.mineAsync(job);
});
/**
* Notify the miner that a new
* tx has entered the mempool.
*/
CPUMiner.prototype.notifyEntry = function notifyEntry() {
if (!this.running)
return;
if (!this.job)
return;
if (++this.since > 20) {
this.since = 0;
this.job.destroy();
}
};
/**
* Hash until the nonce overflows.
* @param {CPUJob} job
* @returns {Number} nonce
*/
CPUMiner.prototype.findNonce = function findNonce(job) {
var data = job.getHeader(0);
var target = job.attempt.target;
var interval = CPUMiner.INTERVAL;
var min = 0;
var max = interval;
var nonce;
while (max <= 0xffffffff) {
nonce = mine(data, target, min, max);
if (nonce !== -1)
break;
this.sendStatus(job, max);
min += interval;
max += interval;
}
return nonce;
};
/**
* Hash until the nonce overflows.
* @method
* @param {CPUJob} job
* @returns {Promise} Returns Number.
*/
CPUMiner.prototype.findNonceAsync = co(function* findNonceAsync(job) {
var data = job.getHeader(0);
var target = job.attempt.target;
var interval = CPUMiner.INTERVAL;
var min = 0;
var max = interval;
var nonce;
while (max <= 0xffffffff) {
nonce = yield workerPool.mine(data, target, min, max);
if (nonce !== -1)
break;
if (job.destroyed)
return nonce;
this.sendStatus(job, max);
min += interval;
max += interval;
}
return nonce;
});
/**
* Mine synchronously until the block is found.
* @param {CPUJob} job
* @returns {Block}
*/
CPUMiner.prototype.mine = function mine(job) {
var nonce;
// Track how long we've been at it.
job.begin = util.now();
for (;;) {
nonce = this.findNonce(job);
if (nonce !== -1)
break;
this.iterate(job);
}
return job.commit(nonce);
};
/**
* Mine asynchronously until the block is found.
* @method
* @param {CPUJob} job
* @returns {Promise} - Returns {@link Block}.
*/
CPUMiner.prototype.mineAsync = co(function* mineAsync(job) {
var nonce;
// Track how long we've been at it.
job.begin = util.now();
for (;;) {
nonce = yield this.findNonceAsync(job);
if (nonce !== -1)
break;
if (job.destroyed)
return;
this.iterate(job);
}
return job.commit(nonce);
});
/**
* Increment extraNonce and send status.
* @param {CPUJob} job
*/
CPUMiner.prototype.iterate = function iterate(job) {
job.iterations++;
job.updateNonce();
this.sendStatus(job, 0);
};
/**
* Send a progress report (emits `status`).
* @param {CPUJob} job
* @param {Number} nonce
*/
CPUMiner.prototype.sendStatus = function sendStatus(job, nonce) {
this.emit('status', {
target: job.attempt.bits,
hashes: job.getHashes(),
hashrate: job.getRate(nonce),
height: job.attempt.height,
best: util.revHex(job.attempt.prevBlock)
});
};
/**
* Mining Job
* @constructor
* @ignore
* @param {CPUMiner} miner
* @param {BlockTemplate} attempt
*/
function CPUJob(miner, attempt) {
this.miner = miner;
this.attempt = attempt;
this.destroyed = false;
this.committed = false;
this.iterations = 0;
this.begin = 0;
this.nonce1 = 0;
this.nonce2 = 0;
this.refresh();
}
/**
* Get the raw block header.
* @param {Number} nonce
* @returns {Buffer}
*/
CPUJob.prototype.getHeader = function getHeader(nonce) {
var attempt = this.attempt;
var n1 = this.nonce1;
var n2 = this.nonce2;
var ts = attempt.ts;
return this.attempt.getHeader(n1, n2, ts, nonce);
};
/**
* Commit job and return a block.
* @param {Number} nonce
* @returns {Block}
*/
CPUJob.prototype.commit = function commit(nonce) {
var attempt = this.attempt;
var n1 = this.nonce1;
var n2 = this.nonce2;
var ts = attempt.ts;
assert(!this.committed, 'Job already committed.');
this.committed = true;
return this.attempt.commit(n1, n2, ts, nonce);
};
/**
* Mine block synchronously.
* @returns {Block}
*/
CPUJob.prototype.mine = function mine() {
return this.miner.mine(this);
};
/**
* Mine block asynchronously.
* @returns {Promise}
*/
CPUJob.prototype.mineAsync = function mineAsync() {
return this.miner.mineAsync(this);
};
/**
* Refresh the block template.
*/
CPUJob.prototype.refresh = function refresh() {
return this.attempt.refresh();
};
/**
* Increment the extraNonce.
*/
CPUJob.prototype.updateNonce = function() {
// Overflow the nonce and increment the extraNonce.
this.nonce1++;
// Wrap at 4 bytes.
if (this.nonce1 === 0xffffffff) {
this.nonce1 = 0;
this.nonce2++;
}
};
/**
* Destroy the job.
*/
CPUJob.prototype.destroy = function() {
assert(!this.destroyed, 'Job already destroyed.');
this.destroyed = true;
};
/**
* Calculate number of hashes.
* @returns {Number}
*/
CPUJob.prototype.getHashes = function() {
return this.iterations * 0xffffffff + this.block.nonce;
};
/**
* Calculate hashrate.
* @returns {Number}
*/
CPUJob.prototype.getRate = function(nonce) {
return (nonce / (util.now() - this.begin)) | 0;
};
/**
* Add a transaction to the block.
* @param {TX} tx
* @param {CoinView} view
*/
CPUJob.prototype.addTX = function(tx, view) {
return this.attempt.addTX(tx, view);
};
/**
* Add a transaction to the block
* (less verification than addTX).
* @param {TX} tx
* @param {CoinView?} view
*/
CPUJob.prototype.pushTX = function(tx, view) {
return this.attempt.pushTX(tx, view);
};
/*
* Expose
*/
module.exports = CPUMiner;

View File

@ -4,6 +4,7 @@
* @module mining
*/
exports.BlockTemplate = require('./template');
exports.CPUMiner = require('./cpuminer');
exports.mine = require('./mine');
exports.Miner = require('./miner');
exports.MinerBlock = require('./minerblock');

View File

@ -1,5 +1,5 @@
/*!
* miner.js - inefficient miner for bcoin (because we can)
* miner.js - block generator for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
@ -14,23 +14,18 @@ var Heap = require('../utils/heap');
var AsyncObject = require('../utils/asyncobject');
var Amount = require('../btc/amount');
var Address = require('../primitives/address');
var MinerBlock = require('./minerblock');
var BlockTemplate = require('./template');
var Network = require('../protocol/network');
var consensus = require('../protocol/consensus');
var policy = require('../protocol/policy');
var BlockEntry = MinerBlock.BlockEntry;
var CPUMiner = require('./cpuminer');
var BlockEntry = BlockTemplate.BlockEntry;
/**
* A bitcoin miner (supports mining witness blocks).
* A bitcoin miner and block generator.
* @alias module:mining.Miner
* @constructor
* @param {Object} options
* @param {Address} options.address - Payout address.
* @param {String} [options.coinbaseFlags="mined by bcoin"]
* @property {Boolean} running
* @property {MinerBlock} attempt
* @emits Miner#block
* @emits Miner#status
*/
function Miner(options) {
@ -40,58 +35,17 @@ function Miner(options) {
AsyncObject.call(this);
this.options = new MinerOptions(options);
this.network = this.options.network;
this.logger = this.options.logger;
this.chain = this.options.chain;
this.mempool = this.options.mempool;
this.addresses = this.options.addresses;
this.locker = this.chain.locker;
this.running = false;
this.stopping = false;
this.attempt = null;
this.since = 0;
this._init();
this.cpu = new CPUMiner(this);
}
util.inherits(Miner, AsyncObject);
/**
* Initialize the miner.
* @private
*/
Miner.prototype._init = function _init() {
var self = this;
this.chain.on('tip', function(tip) {
if (!self.attempt)
return;
if (self.attempt.block.prevBlock === tip.prevBlock)
self.attempt.destroy();
});
this.on('block', function(block, entry) {
// Emit the block hex as a failsafe (in case we can't send it)
self.logger.info('Found block: %d (%s).', entry.height, entry.rhash());
self.logger.debug('Raw: %s', block.toRaw().toString('hex'));
});
this.on('status', function(stat) {
self.logger.info(
'Miner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s',
stat.hashrate / 1000 | 0,
stat.hashes,
stat.target,
stat.height,
stat.best);
});
};
/**
* Open the miner, wait for the chain and mempool to load.
* @method
@ -105,6 +59,8 @@ Miner.prototype._open = co(function* open() {
if (this.mempool)
yield this.mempool.open();
yield this.cpu.open();
this.logger.info('Miner loaded (flags=%s).',
this.options.coinbaseFlags.toString('utf8'));
@ -120,145 +76,15 @@ Miner.prototype._open = co(function* open() {
*/
Miner.prototype._close = co(function* close() {
if (!this.running)
return;
if (this.stopping) {
yield this._onStop();
return;
}
yield this.stop();
yield this.cpu.close();
});
/**
* Start mining.
* Create a block template.
* @method
* @param {Number?} version - Custom block version.
* @returns {Promise}
*/
Miner.prototype.start = co(function* start() {
var self = this;
var block, entry;
assert(!this.running, 'Miner is already running.');
this.running = true;
this.stopping = false;
for (;;) {
this.attempt = null;
try {
this.attempt = yield this.createBlock();
} catch (e) {
if (this.stopping)
break;
this.emit('error', e);
continue;
}
if (this.stopping)
break;
this.attempt.on('status', function(status) {
self.emit('status', status);
});
try {
block = yield this.attempt.mineAsync();
} catch (e) {
if (this.stopping)
break;
this.emit('error', e);
continue;
}
if (this.stopping)
break;
if (!block)
continue;
try {
entry = yield this.chain.add(block);
} catch (e) {
if (this.stopping)
break;
this.emit('error', e);
continue;
}
if (!entry) {
this.logger.warning('Mined a bad-prevblk (race condition?)');
continue;
}
if (this.stopping)
break;
this.emit('block', block, entry);
}
this.emit('done');
});
/**
* Stop mining.
* @method
* @returns {Promise}
*/
Miner.prototype.stop = co(function* stop() {
assert(this.running, 'Miner is not running.');
assert(!this.stopping, 'Miner is already stopping.');
this.stopping = true;
if (this.attempt)
this.attempt.destroy();
yield this._onDone();
this.running = false;
this.stopping = false;
this.attempt = null;
this.emit('stop');
});
/**
* Wait for `done` event.
* @private
* @returns {Promise}
*/
Miner.prototype._onDone = function _onDone() {
var self = this;
return new Promise(function(resolve, reject) {
self.once('done', resolve);
});
};
/**
* Wait for `stop` event.
* @private
* @returns {Promise}
*/
Miner.prototype._onStop = function _onStop() {
var self = this;
return new Promise(function(resolve, reject) {
self.once('stop', resolve);
});
};
/**
* Create a block "attempt".
* @method
* @param {ChainEntry} tip
* @returns {Promise} - Returns {@link MinerBlock}.
* @param {ChainEntry?} tip
* @param {Address?} address
* @returns {Promise} - Returns {@link BlockTemplate}.
*/
Miner.prototype.createBlock = co(function* createBlock(tip, address) {
@ -271,16 +97,17 @@ Miner.prototype.createBlock = co(function* createBlock(tip, address) {
});
/**
* Create a block "attempt" (without a lock).
* Create a block template (without a lock).
* @method
* @private
* @param {ChainEntry} tip
* @returns {Promise} - Returns {@link MinerBlock}.
* @param {ChainEntry?} tip
* @param {Address?} address
* @returns {Promise} - Returns {@link BlockTemplate}.
*/
Miner.prototype._createBlock = co(function* createBlock(tip, address) {
var version = this.options.version;
var ts, locktime, target, attempt;
var ts, mtp, locktime, target, attempt, block;
if (!tip)
tip = this.chain.tip;
@ -291,19 +118,18 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) {
if (version === -1)
version = yield this.chain.computeBlockVersion(tip);
if (this.chain.state.hasMTP()) {
locktime = yield tip.getMedianTime();
ts = Math.max(this.network.now(), locktime + 1);
} else {
ts = Math.max(this.network.now(), tip.ts + 1);
locktime = ts;
}
mtp = yield tip.getMedianTime();
ts = Math.max(this.network.now(), mtp + 1);
locktime = ts;
if (this.chain.state.hasMTP())
locktime = mtp;
target = yield this.chain.getTarget(ts, tip);
attempt = new MinerBlock({
network: this.network,
tip: tip,
attempt = new BlockTemplate({
prevBlock: tip.hash,
height: tip.height + 1,
version: version,
ts: ts,
bits: target,
@ -312,11 +138,12 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) {
address: address,
coinbaseFlags: this.options.coinbaseFlags,
witness: this.chain.state.hasWitness(),
halvingInterval: this.network.halvingInterval,
weight: this.options.reservedWeight,
sigops: this.options.reservedSigops
});
this.build(attempt);
this.assemble(attempt);
this.logger.debug(
'Created miner block (height=%d, weight=%d, fees=%d, txs=%s).',
@ -326,8 +153,10 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) {
attempt.items.length + 1);
if (this.options.preverify) {
block = attempt.toBlock();
try {
yield this.chain._verifyBlock(attempt.block);
yield this.chain._verifyBlock(block);
} catch (e) {
if (e.type === 'VerifyError') {
this.logger.warning('Miner created invalid block!');
@ -346,34 +175,28 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) {
});
/**
* Mine a single block.
* Create a cpu miner job.
* @method
* @param {ChainEntry} tip
* @returns {Promise} - Returns [{@link Block}].
* @param {ChainEntry?} tip
* @param {Address?} address
* @returns {Promise} Returns {@link CPUJob}.
*/
Miner.prototype.mineBlock = co(function* mineBlock(tip, address) {
var attempt = yield this.createBlock(tip, address);
return yield attempt.mineAsync();
Miner.prototype.createJob = co(function* createJob(tip, address) {
return yield this.cpu.createJob(tip, address);
});
/**
* Notify the miner that a new tx has entered the mempool.
* @param {MempoolEntry} entry
* Mine a single block.
* @method
* @param {ChainEntry?} tip
* @param {Address?} address
* @returns {Promise} Returns {@link Block}.
*/
Miner.prototype.notifyEntry = function notifyEntry() {
if (!this.running)
return;
if (!this.attempt)
return;
if (++this.since > 20) {
this.since = 0;
this.attempt.destroy();
}
};
Miner.prototype.mineBlock = co(function* mineBlock(tip, address) {
return yield this.cpu.mineBlock(tip, address);
});
/**
* Add an address to the address list.
@ -391,23 +214,23 @@ Miner.prototype.addAddress = function addAddress(address) {
Miner.prototype.getAddress = function getAddress() {
if (this.addresses.length === 0)
return;
return new Address();
return this.addresses[Math.random() * this.addresses.length | 0];
};
/**
* Get mempool entries, sort by dependency order.
* Prioritize by priority and fee rates.
* @param {BlockTemplate} attempt
* @returns {MempoolEntry[]}
*/
Miner.prototype.build = function build(attempt) {
Miner.prototype.assemble = function assemble(attempt) {
var depMap = {};
var block = attempt.block;
var queue = new Heap(cmpRate);
var priority = this.options.priorityWeight > 0;
var i, j, entry, item, tx, hash, input;
var prev, deps, hashes, weight, sigops;
var prev, deps, hashes, weight, sigops, block;
if (priority)
queue.set(cmpPriority);
@ -492,8 +315,6 @@ Miner.prototype.build = function build(attempt) {
attempt.fees += item.fee;
attempt.items.push(item);
block.txs.push(tx);
deps = depMap[hash];
if (!deps)
@ -508,10 +329,18 @@ Miner.prototype.build = function build(attempt) {
attempt.refresh();
assert(block.getWeight() <= attempt.weight,
assert(attempt.weight <= consensus.MAX_BLOCK_WEIGHT,
'Block exceeds reserved weight!');
assert(block.getBaseSize() <= consensus.MAX_BLOCK_SIZE,
'Block exceeds max block size.');
if (this.options.preverify) {
block = attempt.toBlock();
assert(block.getWeight() <= attempt.weight,
'Block exceeds reserved weight!');
assert(block.getBaseSize() <= consensus.MAX_BLOCK_SIZE,
'Block exceeds max block size.');
}
};
/**

View File

@ -1,719 +0,0 @@
/*!
* minerblock.js - miner block object for bcoin (because we can)
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var BN = require('bn.js');
var util = require('../utils/util');
var co = require('../utils/co');
var StaticWriter = require('../utils/staticwriter');
var Network = require('../protocol/network');
var Address = require('../primitives/address');
var TX = require('../primitives/tx');
var Block = require('../primitives/block');
var Input = require('../primitives/input');
var Output = require('../primitives/output');
var mine = require('./mine');
var workerPool = require('../workers/workerpool').pool;
var consensus = require('../protocol/consensus');
var policy = require('../protocol/policy');
var encoding = require('../utils/encoding');
/**
* MinerBlock (block attempt)
* @alias module:mining.MinerBlock
* @constructor
* @param {Object} options
* @param {ChainEntry} options.tip
* @param {Number} options.height
* @param {Number} options.target - Compact form.
* @param {Base58Address} options.address - Payout address.
* @param {Boolean} options.witness - Allow witness
* transactions, mine a witness block.
* @param {String} options.coinbaseFlags
* @property {Block} block
* @property {TX} coinbase
* @property {BN} hashes - Number of hashes attempted.
* @property {Number} rate - Hash rate.
* @emits MinerBlock#status
*/
function MinerBlock(options) {
if (!(this instanceof MinerBlock))
return new MinerBlock(options);
EventEmitter.call(this);
this.network = Network.get(options.network);
this.tip = options.tip;
this.version = options.version;
this.height = options.tip.height + 1;
this.ts = options.ts;
this.bits = options.bits;
this.target = consensus.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32);
this.locktime = options.locktime;
this.flags = options.flags;
this.nonce1 = 0;
this.nonce2 = 0;
this.iterations = 0;
this.coinbaseFlags = options.coinbaseFlags;
this.witness = options.witness;
this.address = options.address;
this.reward = consensus.getReward(this.height, this.network.halvingInterval);
this.destroyed = false;
this.committed = false;
this.sigops = options.sigops;
this.weight = options.weight;
this.fees = 0;
this.items = [];
this.coinbase = new TX();
this.coinbase.mutable = true;
this.block = new Block();
this.block.mutable = true;
this._init();
}
util.inherits(MinerBlock, EventEmitter);
/**
* Nonce range interval.
* @const {Number}
* @default
*/
MinerBlock.INTERVAL = 0xffffffff / 1500 | 0;
/**
* Calculate number of hashes.
* @returns {Number}
*/
MinerBlock.prototype.getHashes = function() {
return this.iterations * 0xffffffff + this.block.nonce;
};
/**
* Calculate hashrate.
* @returns {Number}
*/
MinerBlock.prototype.getRate = function() {
return (this.block.nonce / (util.now() - this.begin)) | 0;
};
/**
* Initialize the block.
* @private
*/
MinerBlock.prototype._init = function _init() {
var scale = consensus.WITNESS_SCALE_FACTOR;
var hash = encoding.ZERO_HASH;
var block = this.block;
var cb = this.coinbase;
var weight = 0;
var sigops = 0;
var input, output, commit, padding;
assert(this.coinbaseFlags.length <= 20);
// Setup our block.
block.version = this.version;
block.prevBlock = this.tip.hash;
block.merkleRoot = encoding.NULL_HASH;
block.ts = this.ts;
block.bits = this.bits;
block.nonce = 0;
// Coinbase input.
input = new Input();
// Height (required in v2+ blocks)
input.script.set(0, new BN(this.height));
// Let the world know this little
// miner succeeded.
input.script.set(1, encoding.ZERO_HASH160);
// Smaller nonce for good measure.
input.script.set(2, util.nonce().slice(0, 4));
// extraNonce - incremented when
// the nonce overflows.
input.script.set(3, this.extraNonce());
input.script.compile();
// Set up the witness nonce.
if (this.witness) {
input.witness.set(0, block.createWitnessNonce());
input.witness.compile();
}
cb.inputs.push(input);
// Reward output.
output = new Output();
output.script.fromPubkeyhash(encoding.ZERO_HASH160);
cb.outputs.push(output);
// If we're using segwit, we
// need to set up the commitment.
if (this.witness) {
// Commitment output.
commit = new Output();
commit.script.fromCommitment(hash);
cb.outputs.push(commit);
}
block.txs.push(cb);
// Initialize weight.
weight = block.getWeight();
// 4 extra bytes for varint tx count.
weight += 4 * scale;
// Padding for the CB height (constant size).
padding = 5 - input.script.code[0].getSize();
assert(padding >= 0);
weight += padding * scale;
// Reserved size.
// Without segwit:
// Block weight = 840
// Block stripped size = 210
// Block size = 210
// CB weight = 500
// CB stripped size = 125
// CB size = 125
// Sigops cost = 4
// With segwit:
// Block weight = 1064
// Block stripped size = 257
// Block size = 293
// CB weight = 724
// CB stripped size = 172
// CB size = 208
// Sigops cost = 4
if (!this.witness) {
assert.equal(weight, 840);
assert.equal(block.getBaseSize() + 4 + padding, 210);
assert.equal(block.getSize() + 4 + padding, 210);
assert.equal(cb.getWeight() + padding * scale, 500);
assert.equal(cb.getBaseSize() + padding, 125);
assert.equal(cb.getSize() + padding, 125);
} else {
assert.equal(weight, 1064);
assert.equal(block.getBaseSize() + 4 + padding, 257);
assert.equal(block.getSize() + 4 + padding, 293);
assert.equal(cb.getWeight() + padding * scale, 724);
assert.equal(cb.getBaseSize() + padding, 172);
assert.equal(cb.getSize() + padding, 208);
}
// Initialize sigops weight.
sigops = 4;
// Setup coinbase flags (variable size).
input.script.set(1, this.coinbaseFlags);
input.script.compile();
// Setup output script (variable size).
if (this.address) {
output.script.clear();
output.script.fromAddress(this.address);
}
// Update commitments.
this.refresh();
// Ensure the variable size
// stuff didn't break anything.
assert(block.getWeight() <= weight,
'Coinbase exceeds reserved size!');
assert(cb.getSigopsCost(null, this.flags) <= sigops,
'Coinbase exceeds reserved sigops!');
assert(this.weight >= weight,
'Coinbase exceeds reserved size!');
assert(this.sigops >= sigops,
'Coinbase exceeds reserved sigops!');
};
/**
* Update coinbase, witness
* commitment, and merkle root.
*/
MinerBlock.prototype.refresh = function refresh() {
// Update coinbase.
this.updateCoinbase();
// Witness commitment.
if (this.witness)
this.updateCommitment();
// Create our merkle root.
this.updateMerkle();
};
/**
* Update the commitment output for segwit.
*/
MinerBlock.prototype.updateCommitment = function updateCommitment() {
var output = this.coinbase.outputs[1];
var hash;
// Recalculate witness merkle root.
hash = this.block.createCommitmentHash();
// Update commitment.
output.script.clear();
output.script.fromCommitment(hash);
};
/**
* Update the extra nonce and coinbase reward.
*/
MinerBlock.prototype.updateCoinbase = function updateCoinbase() {
var input = this.coinbase.inputs[0];
var output = this.coinbase.outputs[0];
// Update extra nonce.
input.script.set(3, this.extraNonce());
input.script.compile();
// Update reward.
output.value = this.reward + this.fees;
};
/**
* Increment the extraNonce.
*/
MinerBlock.prototype.updateNonce = function updateNonce() {
// Overflow the nonce and increment the extraNonce.
this.block.nonce = 0;
this.nonce1++;
// Wrap at 4 bytes.
if (this.nonce1 === 0xffffffff) {
this.nonce1 = 0;
this.nonce2++;
}
// We incremented the extraNonce, need to update coinbase.
this.updateCoinbase();
// We changed the coinbase, need to update merkleRoot.
this.updateMerkle();
};
/**
* Rebuild the merkle tree and update merkle root.
*/
MinerBlock.prototype.updateMerkle = function updateMerkle() {
// Recalculate merkle root.
this.block.merkleRoot = this.block.createMerkleRoot('hex');
};
/**
* Render extraNonce.
* @returns {Buffer}
*/
MinerBlock.prototype.extraNonce = function extraNonce() {
var bw = new StaticWriter(8);
bw.writeU32BE(this.nonce1);
bw.writeU32BE(this.nonce2);
return bw.render();
};
/**
* Set the reward output address.
* @param {Address} address
*/
MinerBlock.prototype.setAddress = function setAddress(address) {
var output = this.coinbase.outputs[0];
this.address = Address(address);
output.script.clear();
output.script.fromAddress(this.address);
// Update commitments.
this.refresh();
};
/**
* Add a transaction to the block. Rebuilds the merkle tree,
* updates coinbase and commitment.
* @param {TX} tx
* @returns {Boolean} Whether the transaction was successfully added.
*/
MinerBlock.prototype.addTX = function addTX(tx, view) {
var hash = tx.hash('hex');
var item, weight, sigops;
assert(!tx.mutable, 'Cannot add mutable TX to block.');
if (this.block.hasTX(hash))
return false;
item = BlockEntry.fromTX(tx, view, this);
weight = item.tx.getWeight();
sigops = item.sigops;
if (!tx.isFinal(this.height, this.locktime))
return false;
if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT)
return false;
if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST)
return false;
if (!this.witness && tx.hasWitness())
return false;
this.weight += weight;
this.sigops += sigops;
this.fees += item.fee;
// Add the tx to our block
this.block.txs.push(tx);
this.items.push(item);
// Update commitments.
this.refresh();
return true;
};
/**
* Hash until the nonce overflows.
* @returns {Boolean} Whether the nonce was found.
*/
MinerBlock.prototype.findNonce = function findNonce() {
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 = 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.
* @method
* @returns {Promise} - Returns Boolean.
*/
MinerBlock.prototype.findNonceAsync = co(function* findNonceAsync() {
var block = this.block;
var target = this.target;
var interval = MinerBlock.INTERVAL;
var min = 0;
var max = interval;
var data, nonce;
while (max <= 0xffffffff) {
data = block.abbr();
nonce = yield workerPool.mine(data, target, min, max);
if (nonce !== -1)
break;
if (this.destroyed)
return nonce;
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 = util.now();
for (;;) {
nonce = this.findNonce();
if (nonce !== -1)
break;
this.iterate();
}
this.commit(nonce);
return this.block;
};
/**
* Mine asynchronously until the block is found.
* @method
* @returns {Promise} - Returns {@link Block}.
*/
MinerBlock.prototype.mineAsync = co(function* mineAsync() {
var nonce;
// Track how long we've been at it.
this.begin = util.now();
for (;;) {
nonce = yield this.findNonceAsync();
if (nonce !== -1)
break;
if (this.destroyed)
return;
this.iterate();
}
this.commit(nonce);
return this.block;
});
/**
* Increment extraNonce, rebuild merkletree.
*/
MinerBlock.prototype.iterate = function iterate() {
// Keep track of our iterations.
this.iterations++;
// Send progress report.
this.sendStatus();
// Overflow the nonce and increment the extraNonce.
this.updateNonce();
};
/**
* Commit and finalize mined block.
* @returns {Block}
*/
MinerBlock.prototype.commit = function commit(nonce) {
assert(!this.committed, 'Block is already committed.');
this.committed = true;
this.block.nonce = nonce;
this.block.mutable = false;
this.coinbase.mutable = false;
return this.block;
};
/**
* Snapshot the nonces.
* @returns {Nonces}
*/
MinerBlock.prototype.snapshot = function snapshot() {
return new Nonces(this.nonce1, this.nonce2);
};
/**
* Send a progress report (emits `status`).
*/
MinerBlock.prototype.sendStatus = function sendStatus() {
this.emit('status', {
block: this.block,
target: this.block.bits,
hashes: this.getHashes(),
hashrate: this.getRate(),
height: this.height,
best: util.revHex(this.tip.hash)
});
};
/**
* Destroy the minerblock. Stop mining.
*/
MinerBlock.prototype.destroy = function destroy() {
this.destroyed = true;
};
/**
* BlockEntry
* @alias module:mining.BlockEntry
* @constructor
* @param {TX} tx
* @property {TX} tx
* @property {Hash} hash
* @property {Amount} fee
* @property {Rate} rate
* @property {Number} priority
* @property {Boolean} free
* @property {Sigops} sigops
* @property {Number} depCount
*/
function BlockEntry(tx) {
this.tx = tx;
this.hash = tx.hash('hex');
this.fee = 0;
this.rate = 0;
this.priority = 0;
this.free = false;
this.sigops = 0;
this.descRate = 0;
this.depCount = 0;
}
/**
* Instantiate block entry from transaction.
* @param {TX} tx
* @param {CoinView} view
* @param {MinerBlock} attempt
* @returns {BlockEntry}
*/
BlockEntry.fromTX = function fromTX(tx, view, attempt) {
var item = new BlockEntry(tx);
item.fee = tx.getFee(view);
item.rate = tx.getRate(view);
item.priority = tx.getPriority(view, attempt.height);
item.free = false;
item.sigops = tx.getSigopsCost(view, attempt.flags);
item.descRate = item.rate;
return item;
};
/**
* Instantiate block entry from mempool entry.
* @param {MempoolEntry} entry
* @param {MinerBlock} attempt
* @returns {BlockEntry}
*/
BlockEntry.fromEntry = function fromEntry(entry, attempt) {
var item = new BlockEntry(entry.tx);
item.fee = entry.getFee();
item.rate = entry.getRate();
item.priority = entry.getPriority(attempt.height);
item.free = item.fee < policy.getMinFee(entry.size);
item.sigops = entry.sigops;
item.descRate = entry.getDescRate();
return item;
};
/**
* Nonces
* @constructor
* @ignore
*/
function Nonces(nonce1, nonce2) {
this.nonce1 = nonce1;
this.nonce2 = nonce2;
}
/**
* Inject nonces into miner block.
* @param {MinerBlock} attempt
* @param {Number} nonce
* @param {Number} ts
*/
Nonces.prototype.inject = function inject(attempt, nonce, ts) {
attempt.block.ts = ts;
attempt.block.nonce = nonce;
attempt.nonce1 = this.nonce1;
attempt.nonce2 = this.nonce2;
attempt.updateCoinbase();
attempt.updateMerkle();
};
/**
* Attempt to verify POW.
* @param {MinerBlock} attempt
* @param {Number} nonce
* @param {Number} ts
*/
Nonces.prototype.verify = function verify(attempt, nonce, ts) {
var block = attempt.block;
var bts = block.ts;
var bnonce = block.nonce;
var nonce1 = attempt.nonce1;
var nonce2 = attempt.nonce2;
var result;
this.inject(attempt, nonce, ts);
if (consensus.verifyPOW(block.hash(), block.bits))
return true;
this.inject(attempt, bnonce, bts);
return false;
};
/*
* Expose
*/
exports = MinerBlock;
exports.MinerBlock = MinerBlock;
exports.BlockEntry = BlockEntry;
module.exports = exports;

621
lib/mining/template.js Normal file
View File

@ -0,0 +1,621 @@
/*!
* template.js - block template object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var BN = require('bn.js');
var util = require('../utils/util');
var crypto = require('../crypto/crypto');
var StaticWriter = require('../utils/staticwriter');
var Address = require('../primitives/address');
var TX = require('../primitives/tx');
var Block = require('../primitives/block');
var Input = require('../primitives/input');
var Output = require('../primitives/output');
var consensus = require('../protocol/consensus');
var policy = require('../protocol/policy');
var encoding = require('../utils/encoding');
var CoinView = require('../coins/coinview');
var DUMMY = new Buffer(0);
/**
* Block Template
* @alias module:mining.BlockTemplate
* @constructor
* @param {Object} options
*/
function BlockTemplate(options) {
if (!(this instanceof BlockTemplate))
return new BlockTemplate(options);
this.prevBlock = options.prevBlock;
this.version = options.version;
this.height = options.height;
this.ts = options.ts;
this.bits = options.bits;
this.target = consensus.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32);
this.locktime = options.locktime;
this.flags = options.flags;
this.coinbaseFlags = options.coinbaseFlags;
this.witness = options.witness;
this.address = options.address;
this.sigops = options.sigops;
this.weight = options.weight;
this.reward = consensus.getReward(this.height, options.halvingInterval);
this.tree = new MerkleTree();
this.left = DUMMY;
this.right = DUMMY;
this.fees = 0;
this.items = [];
}
/**
* Create witness commitment hash.
* @returns {Buffer}
*/
BlockTemplate.prototype.commitmentHash = function commitmentHash() {
var nonce = encoding.ZERO_HASH;
var leaves = [];
var i, item, root, data;
leaves.push(encoding.ZERO_HASH);
for (i = 0; i < this.items.length; i++) {
item = this.items[i];
leaves.push(item.tx.witnessHash());
}
root = crypto.createMerkleRoot(leaves);
assert(!root.malleated);
data = util.concat(root.hash, nonce);
return crypto.hash256(data);
};
/**
* Calculate the block reward.
* @returns {Amount}
*/
BlockTemplate.prototype.getReward = function getReward() {
return this.reward + this.fees;
};
/**
* Initialize the default coinbase.
* @returns {TX}
*/
BlockTemplate.prototype.createCoinbase = function createCoinbase() {
var scale = consensus.WITNESS_SCALE_FACTOR;
var cb = new TX();
var padding = 0;
var input, output, commit, hash;
// Coinbase input.
input = new Input();
// Height (required in v2+ blocks)
input.script.set(0, new BN(this.height));
// Let the world know this little
// miner succeeded.
input.script.set(1, encoding.ZERO_HASH160);
// Smaller nonce for good measure.
input.script.set(2, util.nonce().slice(0, 4));
// extraNonce - incremented when
// the nonce overflows.
input.script.set(3, extraNonce(0, 0));
input.script.compile();
// Set up the witness nonce.
if (this.witness) {
input.witness.set(0, encoding.ZERO_HASH);
input.witness.compile();
}
cb.inputs.push(input);
// Reward output.
output = new Output();
output.script.fromPubkeyhash(encoding.ZERO_HASH160);
output.value = this.getReward();
cb.outputs.push(output);
// If we're using segwit, we
// need to set up the commitment.
if (this.witness) {
// Commitment output.
commit = new Output();
hash = this.commitmentHash();
commit.script.fromCommitment(hash);
cb.outputs.push(commit);
}
// Padding for the CB height (constant size).
padding = 5 - input.script.code[0].getSize();
assert(padding >= 0);
// Reserved size.
// Without segwit:
// CB weight = 500
// CB stripped size = 125
// CB size = 125
// Sigops cost = 4
// With segwit:
// CB weight = 724
// CB stripped size = 172
// CB size = 208
// Sigops cost = 4
if (!this.witness) {
assert.equal(cb.getWeight() + padding * scale, 500);
assert.equal(cb.getBaseSize() + padding, 125);
assert.equal(cb.getSize() + padding, 125);
} else {
assert.equal(cb.getWeight() + padding * scale, 724);
assert.equal(cb.getBaseSize() + padding, 172);
assert.equal(cb.getSize() + padding, 208);
}
// Setup coinbase flags (variable size).
input.script.set(1, this.coinbaseFlags);
input.script.compile();
// Setup output script (variable size).
output.script.clear();
output.script.fromAddress(this.address);
cb.refresh();
return cb;
};
/**
* Refresh the coinbase and merkle tree.
*/
BlockTemplate.prototype.refresh = function refresh() {
var cb = this.createCoinbase();
var raw = cb.toNormal();
var size = 0;
var left, right;
size += 4; // version
size += 1; // varint inputs length
size += cb.inputs[0].getSize(); // input size
size -= 4 + 4 + 4; // -(nonce1 + nonce2 + sequence)
// Cut off right after the nonce
// push and before the sequence.
left = raw.slice(0, size);
// Include the sequence.
size += 4 + 4; // nonce1 + nonce2
right = raw.slice(size);
this.left = left;
this.right = right;
this.tree = MerkleTree.fromItems(this.items);
};
/**
* Get raw coinbase with desired nonces.
* @param {Number} nonce1
* @param {Number} nonce2
* @returns {Buffer}
*/
BlockTemplate.prototype.getCoinbase = function getCoinbase(nonce1, nonce2) {
var size = 0;
var bw;
size += this.left.length;
size += 4 + 4;
size += this.right.length;
bw = new StaticWriter(size);
bw.writeBytes(this.left);
bw.writeU32BE(nonce1);
bw.writeU32BE(nonce2);
bw.writeBytes(this.right);
return bw.render();
};
/**
* Calculate the merkle root with given nonces.
* @param {Number} nonce1
* @param {Number} nonce2
* @returns {Buffer}
*/
BlockTemplate.prototype.getRoot = function getRoot(nonce1, nonce2) {
var raw = this.getCoinbase(nonce1, nonce2);
var hash = crypto.hash256(raw);
return this.tree.withFirst(hash);
};
/**
* Create raw block header with given parameters.
* @param {Number} nonce1
* @param {Number} nonce2
* @param {Number} ts
* @param {Number} nonce
* @returns {Buffer}
*/
BlockTemplate.prototype.getHeader = function getHeader(nonce1, nonce2, ts, nonce) {
var bw = new StaticWriter(80);
var root = this.getRoot(nonce1, nonce2);
bw.writeU32(this.version);
bw.writeHash(this.prevBlock);
bw.writeHash(root);
bw.writeU32(ts);
bw.writeU32(this.bits);
bw.writeU32(nonce);
return bw.render();
};
/**
* Calculate block hash with given parameters.
* @param {Number} nonce1
* @param {Number} nonce2
* @param {Number} ts
* @param {Number} nonce
* @returns {Buffer}
*/
BlockTemplate.prototype.hash = function hash(nonce1, nonce2, ts, nonce) {
var data = this.getHeader(nonce1, nonce2, ts, nonce);
return crypto.hash256(data);
};
/**
* Create coinbase from given parameters.
* @param {Number} nonce1
* @param {Number} nonce2
* @returns {TX}
*/
BlockTemplate.prototype.coinbase = function coinbase(nonce1, nonce2) {
var raw = this.getCoinbase(nonce1, nonce2);
var tx = TX.fromRaw(raw);
var input;
if (this.witness) {
input = tx.inputs[0];
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
tx.refresh();
}
return tx;
};
/**
* Create block from given parameters.
* @param {Number} nonce1
* @param {Number} nonce2
* @param {Number} ts
* @param {Number} nonce
* @returns {Block}
*/
BlockTemplate.prototype.commit = function commit(nonce1, nonce2, ts, nonce) {
var tx = this.coinbase(nonce1, nonce2);
var root = this.tree.withFirst(tx.hash());
var block = new Block();
var i, item;
block.version = this.version;
block.prevBlock = this.prevBlock;
block.merkleRoot = root.toString('hex');
block.ts = ts;
block.bits = this.bits;
block.nonce = nonce;
block.txs.push(tx);
for (i = 0; i < this.items.length; i++) {
item = this.items[i];
block.txs.push(item.tx);
}
return block;
};
/**
* Quick and dirty way to
* get a coinbase tx object.
* @returns {TX}
*/
BlockTemplate.prototype.toCoinbase = function toCoinbase() {
return this.coinbase(0, 0);
};
/**
* Quick and dirty way to get a block
* object (most likely to be an invalid one).
* @returns {Block}
*/
BlockTemplate.prototype.toBlock = function toBlock() {
return this.commit(0, 0, this.ts, 0);
};
/**
* Set the reward output
* address and refresh.
* @param {Address} address
*/
BlockTemplate.prototype.setAddress = function setAddress(address) {
this.address = Address(address);
this.refresh();
};
/**
* Add a transaction to the template.
* @param {TX} tx
* @param {CoinView} view
*/
BlockTemplate.prototype.addTX = function addTX(tx, view) {
var item, weight, sigops;
assert(!tx.mutable, 'Cannot add mutable TX to block.');
item = BlockEntry.fromTX(tx, view, this);
weight = item.tx.getWeight();
sigops = item.sigops;
if (!tx.isFinal(this.height, this.locktime))
return false;
if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT)
return false;
if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST)
return false;
if (!this.witness && tx.hasWitness())
return false;
this.weight += weight;
this.sigops += sigops;
this.fees += item.fee;
// Add the tx to our block
this.items.push(item);
return true;
};
/**
* Add a transaction to the template
* (less verification than addTX).
* @param {TX} tx
* @param {CoinView?} view
*/
BlockTemplate.prototype.pushTX = function pushTX(tx, view) {
var item, weight, sigops;
assert(!tx.mutable, 'Cannot add mutable TX to block.');
if (!view)
view = new CoinView();
item = BlockEntry.fromTX(tx, view, this);
weight = item.tx.getWeight();
sigops = item.sigops;
this.weight += weight;
this.sigops += sigops;
this.fees += item.fee;
// Add the tx to our block
this.items.push(item);
return true;
};
/**
* BlockEntry
* @alias module:mining.BlockEntry
* @constructor
* @param {TX} tx
* @property {TX} tx
* @property {Hash} hash
* @property {Amount} fee
* @property {Rate} rate
* @property {Number} priority
* @property {Boolean} free
* @property {Sigops} sigops
* @property {Number} depCount
*/
function BlockEntry(tx) {
this.tx = tx;
this.hash = tx.hash('hex');
this.fee = 0;
this.rate = 0;
this.priority = 0;
this.free = false;
this.sigops = 0;
this.descRate = 0;
this.depCount = 0;
}
/**
* Instantiate block entry from transaction.
* @param {TX} tx
* @param {CoinView} view
* @param {BlockTemplate} attempt
* @returns {BlockEntry}
*/
BlockEntry.fromTX = function fromTX(tx, view, attempt) {
var item = new BlockEntry(tx);
item.fee = tx.getFee(view);
item.rate = tx.getRate(view);
item.priority = tx.getPriority(view, attempt.height);
item.free = false;
item.sigops = tx.getSigopsCost(view, attempt.flags);
item.descRate = item.rate;
return item;
};
/**
* Instantiate block entry from mempool entry.
* @param {MempoolEntry} entry
* @param {BlockTemplate} attempt
* @returns {BlockEntry}
*/
BlockEntry.fromEntry = function fromEntry(entry, attempt) {
var item = new BlockEntry(entry.tx);
item.fee = entry.getFee();
item.rate = entry.getRate();
item.priority = entry.getPriority(attempt.height);
item.free = item.fee < policy.getMinFee(entry.size);
item.sigops = entry.sigops;
item.descRate = entry.getDescRate();
return item;
};
/*
* MerkleTree
* @constructor
* @property {Hash[]} steps
*/
function MerkleTree() {
this.steps = [];
}
MerkleTree.prototype.withFirst = function withFirst(hash) {
var i, step, data;
for (i = 0; i < this.steps.length; i++) {
step = this.steps[i];
data = util.concat(hash, step);
hash = crypto.hash256(data);
}
return hash;
};
MerkleTree.prototype.toJSON = function toJSON() {
var steps = [];
var i, step;
for (i = 0; i < this.steps.length; i++) {
step = this.steps[i];
steps.push(step.toString('hex'));
}
return steps;
};
MerkleTree.prototype.fromItems = function fromItems(items) {
var leaves = [];
var i, item;
leaves.push(encoding.ZERO_HASH);
for (i = 0; i < items.length; i++) {
item = items[i];
leaves.push(item.tx.hash());
}
return this.fromLeaves(leaves);
};
MerkleTree.fromItems = function fromItems(items) {
return new MerkleTree().fromItems(items);
};
MerkleTree.prototype.fromBlock = function fromBlock(txs) {
var leaves = [];
var i, tx;
leaves.push(encoding.ZERO_HASH);
for (i = 1; i < txs.length; i++) {
tx = txs[i];
leaves.push(tx.hash());
}
return this.fromLeaves(leaves);
};
MerkleTree.fromBlock = function fromBlock(txs) {
return new MerkleTree().fromBlock(txs);
};
MerkleTree.prototype.fromLeaves = function fromLeaves(leaves) {
var len = leaves.length;
var i, hashes, data, hash;
while (len > 1) {
this.steps.push(leaves[1]);
if (len % 2)
leaves.push(leaves[len - 1]);
hashes = [null];
for (i = 2; i < len; i += 2) {
data = util.concat(leaves[i], leaves[i + 1]);
hash = crypto.hash256(data);
hashes.push(hash);
}
leaves = hashes;
len = leaves.length;
}
return this;
};
MerkleTree.fromLeaves = function fromLeaves(leaves) {
return new MerkleTree().fromLeaves(leaves);
};
/*
* Helpers
*/
function extraNonce(nonce1, nonce2) {
var bw = new StaticWriter(8);
bw.writeU32BE(nonce1);
bw.writeU32BE(nonce2);
return bw.render();
};
/*
* Expose
*/
exports = BlockTemplate;
exports.BlockTemplate = BlockTemplate;
exports.BlockEntry = BlockEntry;
module.exports = exports;

View File

@ -12,6 +12,7 @@ exports.Bloom = require('./bloom');
exports.RollingFilter = exports.Bloom.Rolling;
exports.co = require('./co');
exports.encoding = require('./encoding');
exports.fs = require('./fs');
exports.Heap = require('./heap');
exports.IP = require('./ip');
exports.lazy = require('./lazy');

View File

@ -15,7 +15,6 @@ var Amount = require('../btc/amount');
var Script = require('../script/script');
var Address = require('../primitives/address');
var KeyRing = require('../primitives/keyring');
var Lock = require('../utils/lock');
var MerkleBlock = require('../primitives/merkleblock');
var MTX = require('../primitives/mtx');
var Outpoint = require('../primitives/outpoint');

View File

@ -14,6 +14,7 @@ var MemWallet = require('./util/memwallet');
var Network = require('../lib/protocol/network');
var Output = require('../lib/primitives/output');
var util = require('../lib/utils/util');
var common = require('../lib/blockchain/common');
var opcodes = Script.opcodes;
describe('Chain', function() {
@ -22,16 +23,16 @@ describe('Chain', function() {
var miner = new Miner({ chain: chain, version: 4 });
var wallet = new MemWallet({ network: network });
var wwallet = new MemWallet({ network: network, witness: true });
var tip1, tip2, addBlock, mineCSV;
var cpu = miner.cpu;
var tip1, tip2, addBlock, mineBlock, mineCSV;
this.timeout(45000);
addBlock = co(function* addBlock(attempt) {
var block = yield attempt.mineAsync();
addBlock = co(function* addBlock(block, flags) {
var entry;
try {
entry = yield chain.add(block);
entry = yield chain.add(block, flags);
} catch (e) {
assert(e.type === 'VerifyError');
return e.reason;
@ -43,8 +44,13 @@ describe('Chain', function() {
return 'OK';
});
mineBlock = co(function* mineBlock(job, flags) {
var block = yield job.mineAsync();
return yield addBlock(block, flags);
});
mineCSV = co(function* mineCSV(tx) {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var rtx;
rtx = new MTX();
@ -63,9 +69,10 @@ describe('Chain', function() {
wallet.sign(rtx);
attempt.addTX(rtx.toTX(), rtx.view);
job.addTX(rtx.toTX(), rtx.view);
job.refresh();
return yield attempt.mineAsync();
return yield job.mineAsync();
});
chain.on('connect', function(entry, block) {
@ -90,7 +97,7 @@ describe('Chain', function() {
var i, block;
for (i = 0; i < 200; i++) {
block = yield miner.mineBlock();
block = yield cpu.mineBlock();
assert(block);
assert(yield chain.add(block));
}
@ -99,11 +106,11 @@ describe('Chain', function() {
}));
it('should mine competing chains', co(function* () {
var i, mtx, at1, at2, blk1, blk2, hash1, hash2;
var i, mtx, job1, job2, blk1, blk2, hash1, hash2;
for (i = 0; i < 10; i++) {
at1 = yield miner.createBlock(tip1);
at2 = yield miner.createBlock(tip2);
job1 = yield cpu.createJob(tip1);
job2 = yield cpu.createJob(tip2);
mtx = yield wallet.create({
outputs: [{
@ -112,11 +119,14 @@ describe('Chain', function() {
}]
});
at1.addTX(mtx.toTX(), mtx.view);
at2.addTX(mtx.toTX(), mtx.view);
job1.addTX(mtx.toTX(), mtx.view);
job2.addTX(mtx.toTX(), mtx.view);
blk1 = yield at1.mineAsync();
blk2 = yield at2.mineAsync();
job1.refresh();
job2.refresh();
blk1 = yield job1.mineAsync();
blk2 = yield job2.mineAsync();
hash1 = blk1.hash('hex');
hash2 = blk2.hash('hex');
@ -156,7 +166,7 @@ describe('Chain', function() {
assert(entry);
assert(chain.height === entry.height);
block = yield miner.mineBlock(entry);
block = yield cpu.mineBlock(entry);
assert(block);
chain.once('reorganize', function() {
@ -186,7 +196,7 @@ describe('Chain', function() {
}));
it('should mine a block after a reorg', co(function* () {
var block = yield miner.mineBlock();
var block = yield cpu.mineBlock();
var hash, entry, result;
assert(yield chain.add(block));
@ -202,7 +212,7 @@ describe('Chain', function() {
}));
it('should prevent double spend on new chain', co(function* () {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var mtx, block;
mtx = yield wallet.create({
@ -212,37 +222,40 @@ describe('Chain', function() {
}]
});
attempt.addTX(mtx.toTX(), mtx.view);
job.addTX(mtx.toTX(), mtx.view);
job.refresh();
block = yield attempt.mineAsync();
block = yield job.mineAsync();
assert(yield chain.add(block));
attempt = yield miner.createBlock();
job = yield cpu.createJob();
assert(mtx.outputs.length > 1);
mtx.outputs.pop();
attempt.addTX(mtx.toTX(), mtx.view);
job.addTX(mtx.toTX(), mtx.view);
job.refresh();
assert.equal(yield addBlock(attempt), 'bad-txns-inputs-missingorspent');
assert.equal(yield mineBlock(job), 'bad-txns-inputs-missingorspent');
}));
it('should fail to connect coins on an alternate chain', co(function* () {
var block = yield chain.db.getBlock(tip1.hash);
var cb = block.txs[0];
var mtx = new MTX();
var attempt;
var job;
mtx.addTX(cb, 0);
mtx.addOutput(wallet.getAddress(), 10 * 1e8);
wallet.sign(mtx);
attempt = yield miner.createBlock();
attempt.addTX(mtx.toTX(), mtx.view);
job = yield cpu.createJob();
job.addTX(mtx.toTX(), mtx.view);
job.refresh();
assert.equal(yield addBlock(attempt), 'bad-txns-inputs-missingorspent');
assert.equal(yield mineBlock(job), 'bad-txns-inputs-missingorspent');
}));
it('should have correct chain value', function() {
@ -252,7 +265,7 @@ describe('Chain', function() {
});
it('should get coin', co(function* () {
var mtx, attempt, block, tx, output, coin;
var mtx, job, block, tx, output, coin;
mtx = yield wallet.send({
outputs: [
@ -271,10 +284,11 @@ describe('Chain', function() {
]
});
attempt = yield miner.createBlock();
attempt.addTX(mtx.toTX(), mtx.view);
job = yield cpu.createJob();
job.addTX(mtx.toTX(), mtx.view);
job.refresh();
block = yield attempt.mineAsync();
block = yield job.mineAsync();
assert(yield chain.add(block));
tx = block.txs[1];
@ -330,7 +344,7 @@ describe('Chain', function() {
assert.equal(state, 1);
for (i = 0; i < 417; i++) {
block = yield miner.mineBlock();
block = yield cpu.mineBlock();
assert(yield chain.add(block));
switch (chain.height) {
case 288:
@ -371,7 +385,7 @@ describe('Chain', function() {
it('should test csv', co(function* () {
var tx = (yield chain.db.getBlock(chain.height - 100)).txs[0];
var block = yield mineCSV(tx);
var csv, attempt, rtx;
var csv, job, rtx;
assert(yield chain.add(block));
@ -390,11 +404,12 @@ describe('Chain', function() {
rtx.addTX(csv, 0);
rtx.setSequence(0, 1, false);
attempt = yield miner.createBlock();
job = yield cpu.createJob();
attempt.addTX(rtx.toTX(), rtx.view);
job.addTX(rtx.toTX(), rtx.view);
job.refresh();
block = yield attempt.mineAsync();
block = yield job.mineAsync();
assert(yield chain.add(block));
}));
@ -402,7 +417,7 @@ describe('Chain', function() {
it('should fail csv with bad sequence', co(function* () {
var csv = (yield chain.db.getBlock(chain.height - 100)).txs[0];
var rtx = new MTX();
var attempt;
var job;
rtx.addOutput({
script: [
@ -415,14 +430,15 @@ describe('Chain', function() {
rtx.addTX(csv, 0);
rtx.setSequence(0, 1, false);
attempt = yield miner.createBlock();
attempt.addTX(rtx.toTX(), rtx.view);
job = yield cpu.createJob();
job.addTX(rtx.toTX(), rtx.view);
job.refresh();
assert.equal(yield addBlock(attempt), 'mandatory-script-verify-flag-failed');
assert.equal(yield mineBlock(job), 'mandatory-script-verify-flag-failed');
}));
it('should mine a block', co(function* () {
var block = yield miner.mineBlock();
var block = yield cpu.mineBlock();
assert(block);
assert(yield chain.add(block));
}));
@ -430,7 +446,7 @@ describe('Chain', function() {
it('should fail csv lock checks', co(function* () {
var tx = (yield chain.db.getBlock(chain.height - 100)).txs[0];
var block = yield mineCSV(tx);
var csv, attempt, rtx;
var csv, job, rtx;
assert(yield chain.add(block));
@ -449,10 +465,11 @@ describe('Chain', function() {
rtx.addTX(csv, 0);
rtx.setSequence(0, 2, false);
attempt = yield miner.createBlock();
attempt.addTX(rtx.toTX(), rtx.view);
job = yield cpu.createJob();
job.addTX(rtx.toTX(), rtx.view);
job.refresh();
assert.equal(yield addBlock(attempt), 'bad-txns-nonfinal');
assert.equal(yield mineBlock(job), 'bad-txns-nonfinal');
}));
it('should have correct wallet balance', co(function* () {
@ -460,72 +477,72 @@ describe('Chain', function() {
}));
it('should fail to connect bad bits', co(function* () {
var attempt = yield miner.createBlock();
attempt.block.bits = 553713663;
assert.equal(yield addBlock(attempt), 'bad-diffbits');
var job = yield cpu.createJob();
job.attempt.bits = 553713663;
assert.equal(yield mineBlock(job), 'bad-diffbits');
}));
it('should fail to connect bad MTP', co(function* () {
var mtp = yield chain.tip.getMedianTime();
var attempt = yield miner.createBlock();
attempt.block.ts = mtp - 1;
assert.equal(yield addBlock(attempt), 'time-too-old');
var job = yield cpu.createJob();
job.attempt.ts = mtp - 1;
assert.equal(yield mineBlock(job), 'time-too-old');
}));
it('should fail to connect bad time', co(function* () {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var now = network.now() + 3 * 60 * 60;
attempt.block.ts = now;
assert.equal(yield addBlock(attempt), 'time-too-new');
job.attempt.ts = now;
assert.equal(yield mineBlock(job), 'time-too-new');
}));
it('should fail to connect bad locktime', co(function* () {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var tx = yield wallet.send({ locktime: 100000 });
attempt.block.txs.push(tx.toTX());
attempt.refresh();
assert.equal(yield addBlock(attempt), 'bad-txns-nonfinal');
job.pushTX(tx.toTX());
job.refresh();
assert.equal(yield mineBlock(job), 'bad-txns-nonfinal');
}));
it('should fail to connect bad cb height', co(function* () {
var bip34height = network.block.bip34height;
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var input = tx.inputs[0];
var job = yield cpu.createJob();
input.script.set(0, new BN(10));
input.script.compile();
attempt.refresh();
job.attempt.height = 10;
job.attempt.refresh();
try {
network.block.bip34height = 0;
assert.equal(yield addBlock(attempt), 'bad-cb-height');
assert.equal(yield mineBlock(job), 'bad-cb-height');
} finally {
network.block.bip34height = bip34height;
}
}));
it('should fail to connect bad witness nonce size', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var block = yield cpu.mineBlock();
var tx = block.txs[0];
var input = tx.inputs[0];
input.witness.set(0, new Buffer(33));
input.witness.compile();
assert.equal(yield addBlock(attempt), 'bad-witness-nonce-size');
block.refresh(true);
assert.equal(yield addBlock(block), 'bad-witness-nonce-size');
}));
it('should fail to connect bad witness nonce', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var block = yield cpu.mineBlock();
var tx = block.txs[0];
var input = tx.inputs[0];
input.witness.set(0, encoding.ONE_HASH);
input.witness.compile();
assert.equal(yield addBlock(attempt), 'bad-witness-merkle-match');
block.refresh(true);
assert.equal(yield addBlock(block), 'bad-witness-merkle-match');
}));
it('should fail to connect bad witness commitment', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW;
var block = yield cpu.mineBlock();
var tx = block.txs[0];
var output = tx.outputs[1];
var commit;
@ -536,18 +553,26 @@ describe('Chain', function() {
output.script.set(1, commit);
output.script.compile();
attempt.updateMerkle();
assert.equal(yield addBlock(attempt), 'bad-witness-merkle-match');
block.refresh(true);
block.merkleRoot = block.createMerkleRoot('hex');
assert.equal(yield addBlock(block, flags), 'bad-witness-merkle-match');
}));
it('should fail to connect unexpected witness', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW;
var block = yield cpu.mineBlock();
var tx = block.txs[0];
var output = tx.outputs[1];
assert(output.script.isCommitment());
tx.outputs.pop();
attempt.updateMerkle();
assert.equal(yield addBlock(attempt), 'unexpected-witness');
block.refresh(true);
block.merkleRoot = block.createMerkleRoot('hex');
assert.equal(yield addBlock(block, flags), 'unexpected-witness');
}));
it('should add wit addrs to miner', co(function* () {
@ -560,7 +585,7 @@ describe('Chain', function() {
var i, block;
for (i = 0; i < 2001; i++) {
block = yield miner.mineBlock();
block = yield cpu.mineBlock();
assert(block);
assert(yield chain.add(block));
}
@ -572,17 +597,18 @@ describe('Chain', function() {
var block = yield chain.db.getBlock(chain.height - 2000);
var cb = block.txs[0];
var mtx = new MTX();
var attempt;
var job;
mtx.addTX(cb, 0);
mtx.addOutput(wwallet.getAddress(), 1000);
wwallet.sign(mtx);
attempt = yield miner.createBlock();
attempt.addTX(mtx.toTX(), mtx.view);
job = yield cpu.createJob();
job.addTX(mtx.toTX(), mtx.view);
job.refresh();
block = yield attempt.mineAsync();
block = yield job.mineAsync();
assert(yield chain.add(block));
}));
@ -590,7 +616,7 @@ describe('Chain', function() {
it('should mine fail to connect too much weight', co(function* () {
var start = chain.height - 2000;
var end = chain.height - 200;
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var mtx = new MTX();
var i, j, block, cb;
@ -606,18 +632,18 @@ describe('Chain', function() {
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
job.pushTX(mtx.toTX());
}
attempt.refresh();
job.refresh();
assert.equal(yield addBlock(attempt), 'bad-blk-weight');
assert.equal(yield mineBlock(job), 'bad-blk-weight');
}));
it('should mine fail to connect too much size', co(function* () {
var start = chain.height - 2000;
var end = chain.height - 200;
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var mtx = new MTX();
var i, j, block, cb;
@ -633,18 +659,18 @@ describe('Chain', function() {
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
job.pushTX(mtx.toTX());
}
attempt.refresh();
job.refresh();
assert.equal(yield addBlock(attempt), 'bad-blk-length');
assert.equal(yield mineBlock(job), 'bad-blk-length');
}));
it('should mine a big block', co(function* () {
var start = chain.height - 2000;
var end = chain.height - 200;
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var mtx = new MTX();
var i, j, block, cb;
@ -660,34 +686,34 @@ describe('Chain', function() {
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
job.pushTX(mtx.toTX());
}
attempt.refresh();
job.refresh();
assert.equal(yield addBlock(attempt), 'OK');
assert.equal(yield mineBlock(job), 'OK');
}));
it('should fail to connect bad versions', co(function* () {
var i, attempt;
var i, job;
for (i = 0; i <= 3; i++) {
attempt = yield miner.createBlock();
attempt.block.version = i;
assert.equal(yield addBlock(attempt), 'bad-version');
job = yield cpu.createJob();
job.attempt.version = i;
assert.equal(yield mineBlock(job), 'bad-version');
}
}));
it('should fail to connect bad amount', co(function* () {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
attempt.block.txs[0].outputs[0].value += 1;
attempt.updateMerkle();
assert.equal(yield addBlock(attempt), 'bad-cb-amount');
job.attempt.reward += 1;
job.refresh();
assert.equal(yield mineBlock(job), 'bad-cb-amount');
}));
it('should fail to connect premature cb spend', co(function* () {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var block = yield chain.db.getBlock(chain.height - 98);
var cb = block.txs[0];
var mtx = new MTX();
@ -697,14 +723,15 @@ describe('Chain', function() {
wwallet.sign(mtx);
attempt.addTX(mtx.toTX(), mtx.view);
job.addTX(mtx.toTX(), mtx.view);
job.refresh();
assert.equal(yield addBlock(attempt),
assert.equal(yield mineBlock(job),
'bad-txns-premature-spend-of-coinbase');
}));
it('should fail to connect vout belowout', co(function* () {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var block = yield chain.db.getBlock(chain.height - 99);
var cb = block.txs[0];
var mtx = new MTX();
@ -714,15 +741,15 @@ describe('Chain', function() {
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
attempt.refresh();
job.pushTX(mtx.toTX());
job.refresh();
assert.equal(yield addBlock(attempt),
assert.equal(yield mineBlock(job),
'bad-txns-in-belowout');
}));
it('should fail to connect outtotal toolarge', co(function* () {
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var block = yield chain.db.getBlock(chain.height - 99);
var cb = block.txs[0];
var mtx = new MTX();
@ -734,15 +761,16 @@ describe('Chain', function() {
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
attempt.refresh();
job.pushTX(mtx.toTX());
job.refresh();
assert.equal(yield addBlock(attempt),
assert.equal(yield mineBlock(job),
'bad-txns-txouttotal-toolarge');
}));
it('should mine 111 multisig blocks', co(function* () {
var i, j, script, attempt, cb, output, val, block;
var flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW;
var i, j, script, job, cb, output, val, block;
script = new Script();
script.push(new BN(20));
@ -757,8 +785,8 @@ describe('Chain', function() {
script = Script.fromScripthash(script.hash160());
for (i = 0; i < 111; i++) {
attempt = yield miner.createBlock();
cb = attempt.block.txs[0];
block = yield cpu.mineBlock();
cb = block.txs[0];
val = cb.outputs[0].value;
cb.outputs[0].value = 0;
@ -771,10 +799,10 @@ describe('Chain', function() {
cb.outputs.push(output);
}
attempt.updateMerkle();
block.refresh(true);
block.merkleRoot = block.createMerkleRoot('hex');
block = yield attempt.mineAsync();
assert(yield chain.add(block));
assert(yield chain.add(block, flags));
}
assert.equal(chain.height, 2749);
@ -783,7 +811,7 @@ describe('Chain', function() {
it('should fail to connect too many sigops', co(function* () {
var start = chain.height - 110;
var end = chain.height - 100;
var attempt = yield miner.createBlock();
var job = yield cpu.createJob();
var i, j, mtx, script, block, cb;
script = new Script();
@ -812,12 +840,12 @@ describe('Chain', function() {
mtx.addOutput(wwallet.getAddress(), 1);
attempt.block.txs.push(mtx.toTX());
job.pushTX(mtx.toTX());
}
attempt.refresh();
job.refresh();
assert.equal(yield addBlock(attempt), 'bad-blk-sigops');
assert.equal(yield mineBlock(job), 'bad-blk-sigops');
}));
it('should cleanup', co(function* () {

View File

@ -8,7 +8,6 @@ var Coin = require('../lib/primitives/coin');
var Script = require('../lib/script/script');
var FullNode = require('../lib/node/fullnode');
var MTX = require('../lib/primitives/mtx');
// var Client = require('../lib/wallet/client');
describe('Node', function() {
var node = new FullNode({
@ -23,18 +22,16 @@ describe('Node', function() {
var miner = node.miner;
var wallet, tip1, tip2, cb1, cb2, mineBlock;
// walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' });
node.on('error', function() {});
this.timeout(5000);
mineBlock = co(function* mineBlock(tip, tx) {
var attempt = yield miner.createBlock(tip);
var job = yield miner.createJob(tip);
var rtx;
if (!tx)
return yield attempt.mineAsync();
return yield job.mineAsync();
rtx = new MTX();
@ -47,9 +44,10 @@ describe('Node', function() {
yield wallet.sign(rtx);
attempt.addTX(rtx.toTX(), rtx.view);
job.addTX(rtx.toTX(), rtx.view);
job.refresh();
return yield attempt.mineAsync();
return yield job.mineAsync();
});
it('should open chain and miner', co(function* () {
@ -312,7 +310,7 @@ describe('Node', function() {
}));
var mineCSV = co(function* mineCSV(tx) {
var attempt = yield miner.createBlock();
var job = yield miner.createJob();
var redeemer;
redeemer = new MTX();
@ -331,15 +329,16 @@ describe('Node', function() {
yield wallet.sign(redeemer);
attempt.addTX(redeemer.toTX(), redeemer.view);
job.addTX(redeemer.toTX(), redeemer.view);
job.refresh();
return yield attempt.mineAsync();
return yield job.mineAsync();
});
it('should test csv', co(function* () {
var tx = (yield chain.db.getBlock(chain.height)).txs[0];
var block = yield mineCSV(tx);
var csv, attempt, redeemer;
var csv, job, redeemer;
yield chain.add(block);
@ -358,18 +357,19 @@ describe('Node', function() {
redeemer.addTX(csv, 0);
redeemer.setSequence(0, 1, false);
attempt = yield miner.createBlock();
job = yield miner.createJob();
attempt.addTX(redeemer.toTX(), redeemer.view);
job.addTX(redeemer.toTX(), redeemer.view);
job.refresh();
block = yield attempt.mineAsync();
block = yield job.mineAsync();
yield chain.add(block);
}));
it('should fail csv with bad sequence', co(function* () {
var csv = (yield chain.db.getBlock(chain.height)).txs[1];
var block, attempt, redeemer, err;
var block, job, redeemer, err;
redeemer = new MTX();
@ -384,11 +384,12 @@ describe('Node', function() {
redeemer.addTX(csv, 0);
redeemer.setSequence(0, 1, false);
attempt = yield miner.createBlock();
job = yield miner.createJob();
attempt.addTX(redeemer.toTX(), redeemer.view);
job.addTX(redeemer.toTX(), redeemer.view);
job.refresh();
block = yield attempt.mineAsync();
block = yield job.mineAsync();
try {
yield chain.add(block);
@ -409,7 +410,7 @@ describe('Node', function() {
it('should fail csv lock checks', co(function* () {
var tx = (yield chain.db.getBlock(chain.height)).txs[0];
var block = yield mineCSV(tx);
var csv, attempt, redeemer, err;
var csv, job, redeemer, err;
yield chain.add(block);
@ -428,11 +429,12 @@ describe('Node', function() {
redeemer.addTX(csv, 0);
redeemer.setSequence(0, 2, false);
attempt = yield miner.createBlock();
job = yield miner.createJob();
attempt.addTX(redeemer.toTX(), redeemer.view);
job.addTX(redeemer.toTX(), redeemer.view);
job.refresh();
block = yield attempt.mineAsync();
block = yield job.mineAsync();
try {
yield chain.add(block);