This commit is contained in:
Christopher Jeffrey 2016-04-05 18:06:46 -07:00
parent 9356a40e62
commit 40cabd37fc
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 354 additions and 232 deletions

View File

@ -132,6 +132,37 @@ $ node bin/bcoin-cli wallet primary --passphrase=node
$ node bin/bcoin-cli mempool $ node bin/bcoin-cli mempool
``` ```
### Creating a blockchain and mempool
``` js
var bcoin = require('bcoin');
bcoin.protocol.network.set('regtest');
var chain = new bcoin.chain({ db: 'memory' });
var mempool = new bcoin.mempool({ chain: chain, db: 'memory' });
var miner = new bcoin.miner({ chain: chain, mempool: mempool });
// Create a block "attempt"
miner.createBlock(function(err, attempt) {
if (err)
throw err;
// Mine the block on the worker pool (use mine() for the master process)
attempt.mineAsync(function(err, block) {
if (err)
throw err;
// Add the block to the chain
chain.add(block, function(err) {
if (err)
throw err;
console.log('Added %s to the blockchain.', block.rhash);
console.log(block);
});
});
});
```
### TX creation ### TX creation
TODO TODO

View File

@ -276,8 +276,6 @@ Block.prototype.getReward = function getReward() {
var reward = Block.reward(this.height); var reward = Block.reward(this.height);
var i; var i;
assert(this.height !== -1);
for (i = 1; i < this.txs.length; i++) for (i = 1; i < this.txs.length; i++)
reward.iadd(this.txs[i].getFee()); reward.iadd(this.txs[i].getFee());

View File

@ -44,8 +44,6 @@ function Miner(options) {
this.timeout = null; this.timeout = null;
this.block = null; this.block = null;
this.iterations = 0;
this._begin = utils.now();
this._init(); this._init();
} }
@ -68,13 +66,13 @@ Miner.prototype._init = function _init() {
this.mempool.on('tx', function(tx) { this.mempool.on('tx', function(tx) {
if (!self.running) if (!self.running)
return; return;
self.addTX(tx); self.attempt.addTX(tx);
}); });
} else if (this.pool) { } else if (this.pool) {
this.pool.on('tx', function(tx) { this.pool.on('tx', function(tx) {
if (!self.running) if (!self.running)
return; return;
self.addTX(tx); self.attempt.addTX(tx);
}); });
} }
@ -122,41 +120,40 @@ Miner.prototype.start = function start() {
this.running = true; this.running = true;
// Reset iterations
this.iterations = 0;
// Create a new block and start hashing // Create a new block and start hashing
this.createBlock(function(err, block) { this.createBlock(function(err, attempt) {
if (err) if (err)
return self.emit('error', err); return self.emit('error', err);
self.block = block; if (!self.running)
return;
if (!self.mempool) self.attempt = attempt;
return self.iterate();
self.mempool.getSnapshot(function(err, hashes) { attempt.on('status', function(status) {
if (err) self.emit('status', status);
return self.emit('error', err); });
utils.forEachSerial(hashes, function(hash, next) { attempt.mineAsync(function(err, block) {
self.mempool.getTX(hash, function(err, tx) { if (err) {
if (err) self.emit('error', err);
return next(err); return self.start();
}
self.mempool.fillAllCoins(tx, function(err) { // Add our block to the chain
if (err) self.chain.add(block, function(err) {
return next(err); if (err) {
if (err.type === 'VerifyError')
utils.debug('%s could not be added to chain.', block.rhash);
self.emit('error', err);
return self.start();
}
self.addTX(tx); // Emit our newly found block
next(); self.emit('block', block);
});
});
}, function(err) {
if (err)
return self.emit('error', err);
self.iterate(); // `tip` will now be emitted by chain
// and the whole process starts over.
}); });
}); });
}); });
@ -168,11 +165,185 @@ Miner.prototype.stop = function stop() {
this.running = false; this.running = false;
clearTimeout(this.timeout); if (this.attempt) {
this.timeout = null; this.attempt.destroy();
this.attempt = null;
}
}; };
Miner.prototype.addTX = function addTX(tx) { Miner.prototype.createBlock = function createBlock(callback) {
var self = this;
var ts = Math.max(utils.now(), this.chain.tip.ts + 1);
var attempt;
// Find target
this.chain.getTargetAsync(this.chain.tip, ts, function(err, target) {
if (err)
return callback(err);
// Calculate version with versionbits
self.chain.computeBlockVersion(self.chain.tip, function(err, version) {
if (err)
return callback(err);
attempt = new MinerBlock({
tip: self.chain.tip,
version: version,
target: target,
address: self.address,
coinbaseFlags: self.coinbaseFlags,
segwit: self.chain.segwitActive,
dsha256: self.dsha256
});
if (!self.mempool)
return callback(null, attempt);
self.mempool.getSnapshot(function(err, hashes) {
if (err)
return callback(err);
utils.forEachSerial(hashes, function(hash, next) {
self.mempool.getTX(hash, function(err, tx) {
if (err)
return next(err);
self.mempool.fillAllCoins(tx, function(err) {
if (err)
return next(err);
attempt.addTX(tx);
next();
});
});
}, function(err) {
if (err)
return callback(err);
return callback(null, attempt);
});
});
});
});
};
Miner.prototype.mineBlock = function mineBlock(callback) {
var self = this;
// Create a new block and start hashing
this.createBlock(function(err, attempt) {
if (err)
return callback(err);
attempt.mineAsync(callback);
});
};
/**
* MinerBlock
*/
function MinerBlock(options) {
if (!(this instanceof MinerBlock))
return new MinerBlock(options);
this.options = options;
this.tip = options.tip;
this.height = options.tip.height + 1;
this.target = utils.fromCompact(options.target).toBuffer('le', 32);
this.extraNonce = new bn(0);
this.iterations = 0;
this.dsha256 = options.dsha256;
// Create a coinbase
this.coinbase = new bcoin.mtx();
this.coinbase.addInput({
prevout: {
hash: utils.toHex(constants.zeroHash),
index: 0xffffffff
},
coin: null,
script: new bcoin.script([
// Height (required in v2+ blocks)
bcoin.script.array(this.height),
// extraNonce - incremented when
// the nonce overflows.
bcoin.script.array(0),
// Add a nonce to ensure we don't
// collide with a previous coinbase
// of ours. This isn't really
// necessary nowdays due to bip34
// (used above).
bcoin.script.array(utils.nonce()),
// Let the world know this little
// miner succeeded.
new Buffer(options.coinbaseFlags, 'ascii')
]),
witness: new bcoin.script.witness([]),
sequence: 0xffffffff
});
this.coinbase.addOutput({
address: options.address,
value: new bn(0)
});
// Create our block
this.block = new bcoin.block({
version: options.version,
prevBlock: this.tip.hash,
merkleRoot: constants.zeroHash,
ts: Math.max(utils.now(), this.tip.ts + 1),
bits: options.target,
nonce: 0
});
this.block.txs.push(this.coinbase);
if (options.segwit) {
// Set up the witness nonce and
// commitment output for segwit.
this.witness = true;
this.witnessNonce = utils.dsha256(new Buffer(this.tip.hash, 'hex'));
this.coinbase.inputs[0].witness.items[0] = this.witnessNonce;
this.coinbase.addOutput({
script: new bcoin.script([]),
value: new bn(0)
});
}
// Update coinbase since our coinbase was added.
this.updateCoinbase();
// Create our merkle root.
this.updateMerkle();
}
utils.inherits(MinerBlock, EventEmitter);
MinerBlock.prototype.updateCommitment = function updateCommitment() {
var hash = this.block.getCommitmentHash();
this.coinbase.outputs[1].script = bcoin.script.createCommitment(hash);
};
MinerBlock.prototype.updateCoinbase = function updateCoinbase() {
this.coinbase.inputs[0].script[1] = this.extraNonce.toBuffer();
this.coinbase.outputs[0].value = this.block.getReward();
};
MinerBlock.prototype.updateMerkle = function updateMerkle() {
// Always update commitment before updating merkle root.
// The updated commitment output will change the merkle root.
if (this.witness)
this.updateCommitment();
this.block.ts = Math.max(utils.now(), this.tip.ts + 1);
this.block.merkleRoot = this.block.getMerkleRoot('hex');
};
MinerBlock.prototype.addTX = function addTX(tx) {
var size = this.block.getVirtualSize() + tx.getVirtualSize(); var size = this.block.getVirtualSize() + tx.getVirtualSize();
// Deliver me from the block size debate, please // Deliver me from the block size debate, please
@ -197,206 +368,33 @@ Miner.prototype.addTX = function addTX(tx) {
return true; return true;
}; };
Miner.prototype.createBlock = function createBlock(callback) { MinerBlock.prototype.findNonce = function findNonce() {
var self = this; var tip = this.tip;
var ts = Math.max(utils.now(), this.chain.tip.ts + 1); var block = this.block;
var coinbase, headers, block; var target = this.target;
var data = block.abbr();
// Find target
this.chain.getTargetAsync(this.chain.tip, ts, function(err, target) {
if (err)
return callback(err);
// Calculate version with versionbits
self.chain.computeBlockVersion(self.chain.tip, function(err, version) {
if (err)
return callback(err);
// Create a coinbase
coinbase = bcoin.mtx();
coinbase.addInput({
prevout: {
hash: utils.toHex(constants.zeroHash),
index: 0xffffffff
},
coin: null,
script: new bcoin.script([
// Height (required in v2+ blocks)
bcoin.script.array(self.chain.height + 1),
// extraNonce - incremented when
// the nonce overflows.
bcoin.script.array(0),
// Add a nonce to ensure we don't
// collide with a previous coinbase
// of ours. This isn't really
// necessary nowdays due to bip34
// (used above).
bcoin.script.array(utils.nonce()),
// Let the world know this little
// miner succeeded.
new Buffer(self.coinbaseFlags, 'ascii')
]),
witness: new bcoin.script.witness([]),
sequence: 0xffffffff
});
coinbase.addOutput({
address: self.address,
value: new bn(0)
});
// Create our block
headers = {
version: version,
prevBlock: self.chain.tip.hash,
merkleRoot: constants.zeroHash,
ts: ts,
bits: target,
nonce: 0
};
block = bcoin.block(headers);
block.txs.push(coinbase);
block.height = self.chain.height + 1;
block.target = utils.fromCompact(target).toBuffer('le', 32);
block.extraNonce = new bn(0);
if (self.chain.segwitActive) {
// Set up the witness nonce and
// commitment output for segwit.
block.witness = true;
block.witnessNonce = utils.nonce().toBuffer('le', 8);
coinbase.inputs[0].witness.items[0] = block.witnessNonce;
coinbase.addOutput({
script: new bcoin.script([]),
value: new bn(0)
});
}
// Update coinbase since our coinbase was added.
self.updateCoinbase(block);
// Create our merkle root.
self.updateMerkle(block);
return callback(null, block);
});
});
};
Miner.prototype.updateCommitment = function updateCommitment(block) {
var coinbase = block.txs[0];
var hash;
assert(coinbase);
if (!block)
block = this.block;
hash = block.getCommitmentHash();
coinbase.outputs[1].script = bcoin.script.createCommitment(hash);
};
Miner.prototype.updateCoinbase = function updateCoinbase(block) {
var coinbase = block.txs[0];
assert(coinbase);
if (!block)
block = this.block;
coinbase.inputs[0].script[1] = block.extraNonce.toBuffer();
coinbase.outputs[0].value = block.getReward();
};
Miner.prototype.updateMerkle = function updateMerkle(block) {
if (!block)
block = this.block;
// Always update commitment before updating merkle root.
// The updated commitment output will change the merkle root.
if (block.witness)
this.updateCommitment(block);
block.ts = Math.max(utils.now(), this.chain.tip.ts + 1);
block.merkleRoot = block.getMerkleRoot('hex');
};
Miner.prototype.iterate = function iterate() {
var self = this;
this.timeout = setTimeout(function() {
// Try to find a block: do one iteration of extraNonce
if (!self.findNonce())
return self.iterate();
// Add our block to the chain
self.chain.add(self.block, function(err) {
if (err) {
if (err.type === 'VerifyError')
utils.debug('%s could not be added to chain.', self.block.rhash);
self.emit('error', err);
return self.start();
}
// Emit our newly found block
self.emit('block', self.block);
// `tip` will now be emitted by chain
// and the whole process starts over.
});
}, 100);
};
Miner.prototype.__defineGetter__('hashes', function() {
return new bn(this.iterations).mul(utils.U32).addn(this.block.nonce);
});
Miner.prototype.__defineGetter__('rate', function() {
if (!this.block.nonce)
return 0;
// Calculate our terrible hashrate
return (this.block.nonce / (utils.now() - this._begin)) * 2 | 0;
});
Miner.prototype.sendStatus = function sendStatus() {
this.emit('status', {
block: this.block,
target: this.block.bits,
hashes: this.hashes.toString(10),
hashrate: this.rate,
height: this.chain.height + 1,
best: utils.revHex(this.chain.tip.hash)
});
};
Miner.prototype.findNonce = function findNonce() {
var data = this.block.abbr();
var now; var now;
// 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(this.block.ts > this.chain.tip.ts); assert(block.ts > tip.ts);
// The heart and soul of the miner: match the target. // The heart and soul of the miner: match the target.
while (this.block.nonce <= 0xffffffff) { while (block.nonce <= 0xffffffff) {
// Hash and test against the next target // Hash and test against the next target
if (rcmp(this.dsha256(data), this.block.target) < 0) if (rcmp(this.dsha256(data), target) < 0)
return true; return true;
// Increment the nonce to get a different hash // Increment the nonce to get a different hash
this.block.nonce++; block.nonce++;
// Update the raw buffer (faster than // Update the raw buffer (faster than
// constantly serializing the block) // constantly serializing the block)
utils.writeU32(data, this.block.nonce, 76); utils.writeU32(data, block.nonce, 76);
// Send progress report every so often // Send progress report every so often
if (this.block.nonce % 100000 === 0) if (block.nonce % 100000 === 0)
this.sendStatus(); this.sendStatus();
} }
@ -412,16 +410,16 @@ Miner.prototype.findNonce = function findNonce() {
// performance because we do not have to // performance because we do not have to
// recalculate the merkle root. // recalculate the merkle root.
now = utils.now(); now = utils.now();
if (now > this.block.ts && now > this.chain.tip.ts) { if (now > block.ts && now > tip.ts) {
this.block.ts = now; block.ts = now;
// Overflow the nonce // Overflow the nonce
this.block.nonce = 0; block.nonce = 0;
return false; return false;
} }
// Overflow the nonce and increment the extraNonce. // Overflow the nonce and increment the extraNonce.
this.block.nonce = 0; block.nonce = 0;
this.block.extraNonce.iaddn(1); block.extraNonce.iaddn(1);
// We incremented the extraNonce, need to update coinbase. // We incremented the extraNonce, need to update coinbase.
this.updateCoinbase(); this.updateCoinbase();
@ -432,6 +430,61 @@ Miner.prototype.findNonce = function findNonce() {
return false; return false;
}; };
MinerBlock.prototype.__defineGetter__('hashes', function() {
return new bn(this.iterations)
.mul(utils.U32)
.addn(this.block.nonce);
});
MinerBlock.prototype.__defineGetter__('rate', function() {
if (!this.block.nonce)
return 0;
// Calculate our terrible hashrate
return (this.block.nonce / (utils.now() - this.begin)) * 2 | 0;
});
MinerBlock.prototype.sendStatus = function sendStatus() {
this.emit('status', {
block: this.block,
target: this.block.bits,
hashes: this.hashes.toString(10),
hashrate: this.rate,
height: this.height,
best: utils.revHex(this.tip.hash)
});
};
MinerBlock.prototype.mine = function mine(callback) {
var self = this;
this.timeout = setTimeout(function() {
// Try to find a block: do one iteration of extraNonce
if (!self.findNonce())
return self.mine(callback);
return callback(null, self.block);
}, 100);
};
MinerBlock.prototype.mineSync = function mineSync() {
while (!this.findNonce());
return this.block;
};
MinerBlock.prototype.mineAsync = function mine(callback) {
return this.mine(callback);
};
MinerBlock.prototype.destroy = function destroy() {
clearTimeout(this.timeout);
this.timeout = null;
this.block = null;
};
/**
* Helpers
*/
function rcmp(a, b) { function rcmp(a, b) {
var i; var i;
@ -451,4 +504,6 @@ function rcmp(a, b) {
* Expose * Expose
*/ */
module.exports = Miner; exports = Miner;
exports.minerblock = MinerBlock;
module.exports = exports;

View File

@ -81,17 +81,21 @@ workers.call = function call(method, args, callback) {
child = workers.alloc(id); child = workers.alloc(id);
function listener(err, result) { function listener(err, result) {
clearTimeout(timeout); if (timeout) {
timeout = null; clearTimeout(timeout);
timeout = null;
}
callback(err, result); callback(err, result);
} }
child.once(event, listener); child.once(event, listener);
timeout = setTimeout(function() { if (method !== 'mine') {
child.removeListener(event, listener); timeout = setTimeout(function() {
return callback(new Error('Worker timed out.')); child.removeListener(event, listener);
}, workers.TIMEOUT); return callback(new Error('Worker timed out.'));
}, workers.TIMEOUT);
}
child.stdin.write(createPacket(id, method, args)); child.stdin.write(createPacket(id, method, args));
}; };
@ -115,13 +119,25 @@ bcoin.tx.prototype.verifyAsync = function verifyAsync(index, force, flags, callb
return workers.call('verify', [this, index, force, flags], callback); return workers.call('verify', [this, index, force, flags], callback);
}; };
bcoin.miner.minerblock.prototype.mineAsync = function mineAsync(callback) {
var attempt = {
tip: this.tip.toRaw(),
version: this.block.version,
target: this.block.bits,
address: this.options.address,
coinbaseFlags: this.options.coinbaseFlags,
segwit: this.options.segwit
};
return workers.call('mine', [attempt], callback);
};
/** /**
* Child * Child
*/ */
workers.listen = function listen() { workers.listen = function listen() {
bcoin.debug = function debug() { bcoin.debug = function debug() {
process.stderr.write('Worker ' + process.env.BCOIN_WORKER_ID + ':'); process.stderr.write('Worker ' + process.env.BCOIN_WORKER_ID + ': ');
return console.error.apply(console.error, arguments); return console.error.apply(console.error, arguments);
}; };
@ -155,6 +171,28 @@ workers.verify = function verify(tx, index, force, flags) {
return tx.verify(index, force, flags); return tx.verify(index, force, flags);
}; };
workers.mine = function mine(attempt) {
attempt = new bcoin.miner.minerblock({
tip: bcoin.chainblock.fromRaw(null, attempt.tip),
version: attempt.version,
target: attempt.target,
address: attempt.address,
coinbaseFlags: attempt.coinbaseFlags,
segwit: attempt.segwit,
dsha256: utils.dsha256
});
attempt.on('status', function(stat) {
utils.debug(
'hashrate=%dkhs hashes=%d target=%d height=%d best=%s',
stat.hashrate / 1000 | 0,
stat.hashes,
stat.target,
stat.height,
stat.best);
});
return attempt.mineSync();
};
/** /**
* Helpers * Helpers
*/ */