rpc: more mining nonsense.

This commit is contained in:
Christopher Jeffrey 2016-09-17 15:27:31 -07:00
parent 6598245486
commit 061548f2ac
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 311 additions and 215 deletions

View File

@ -12,6 +12,7 @@ var crypto = require('../crypto/crypto');
var assert = utils.assert; var assert = utils.assert;
var constants = bcoin.constants; var constants = bcoin.constants;
var NetworkAddress = require('../primitives/netaddress'); var NetworkAddress = require('../primitives/netaddress');
var EventEmitter = require('events').EventEmitter;
var fs; var fs;
try { try {
@ -24,6 +25,8 @@ function RPC(node) {
if (!(this instanceof RPC)) if (!(this instanceof RPC))
return new RPC(node); return new RPC(node);
EventEmitter.call(this);
this.node = node; this.node = node;
this.network = node.network; this.network = node.network;
this.chain = node.chain; this.chain = node.chain;
@ -39,12 +42,13 @@ function RPC(node) {
this.mining = false; this.mining = false;
this.proclimit = 0; this.proclimit = 0;
this.prevBlock = null;
this.currentBlock = null; this.currentBlock = null;
this.lastTX = 0; this.blockStart = 0;
this.start = 0; this._boundChain = false;
} }
utils.inherits(RPC, EventEmitter);
RPC.prototype.execute = function execute(json, callback) { RPC.prototype.execute = function execute(json, callback) {
switch (json.method) { switch (json.method) {
case 'stop': case 'stop':
@ -1385,51 +1389,46 @@ RPC.prototype.verifychain = function verifychain(args, callback) {
* Mining * Mining
*/ */
RPC.prototype.getwork = function getwork(args, callback, force) { RPC.prototype._submitwork = function getwork(data, callback) {
var self = this; var data, block, header;
var i, data, abbr, field, block, header;
callback = this.locker.lock(getwork, [args, callback], force); if (data.length !== 128)
return callback(new RPCError('Invalid parameter.'));
if (!callback) if (!this.currentBlock)
return; return this._getwork(callback);
if (args.length > 1) data = data.slice(0, 80);
return callback(new RPCError('getwork ( "data" )'));
if (args.length === 1 && this.currentBlock) { reverseEndian(data);
if (!utils.isHex(args[0]))
return callback(new RPCError('Invalid parameter.'));
if (args[0].length !== 256) header = bcoin.headers.fromAbbr(data);
return callback(new RPCError('Invalid parameter.')); block = this.currentBlock.block;
data = new Buffer(args[0], 'hex').slice(0, 80); if (header.prevBlock !== block.prevBlock
reverseEndian(data); || header.merkleRoot !== block.merkleRoot
|| header.bits !== block.bits) {
header = bcoin.headers.fromAbbr(data); return callback(null, false);
block = this.currentBlock.block;
this._clearBlock();
if (header.prevBlock !== block.prevBlock)
return self.getwork([], callback, true);
block.nonce = header.nonce;
block.ts = header.ts;
block.mutable = false;
block.txs[0].mutable = false;
return this.chain.add(block, function(err) {
if (err) {
if (err.reason)
return callback(new RPCError(err.reason));
return callback(err);
}
return self.getwork([], callback, true);
});
} }
block.nonce = header.nonce;
block.ts = header.ts;
block.mutable = false;
block.txs[0].mutable = false;
return this.chain.add(block, function(err) {
if (err) {
if (err.type === 'VerifyError')
return callback(null, false);
return callback(err);
}
return callback(null, true);
});
};
RPC.prototype._getwork = function getwork(callback) {
var i, data, abbr;
this._createBlock(function(err, attempt) { this._createBlock(function(err, attempt) {
if (err) if (err)
return callback(err); return callback(err);
@ -1453,20 +1452,70 @@ RPC.prototype.getwork = function getwork(args, callback, force) {
}); });
}; };
RPC.prototype.getblocktemplate = function getblocktemplate(args, callback) { RPC.prototype.getwork = function getwork(args, callback) {
var self = this; var self = this;
var txs = []; var data;
var txIndex = {};
var mode = 'template';
var maxVersion = -1;
var i, j, tx, deps, input, dep, block;
var opt, lpid, keys, vbavailable, vbrules, mutable, clientRules;
callback = this.locker.lock(getblocktemplate, [args, callback]); callback = this.locker.lock(getwork, [args, callback]);
if (!callback) if (!callback)
return; return;
if (args.length > 1)
return callback(new RPCError('getwork ( "data" )'));
if (args.length === 1) {
if (!utils.isHex(args[0]))
return callback(new RPCError('Invalid parameter.'));
data = new Buffer(args[0], 'hex');
return this._submitwork(data, callback);
}
return this._getwork(callback);
};
RPC.prototype.submitblock = function submitblock(args, callback) {
var block;
callback = this.locker.lock(submitblock, [args, callback]);
if (!callback)
return;
if (args.help || args.length < 1 || args.length > 2) {
return callback(new RPCError('submitblock "hexdata"'
+ ' ( "jsonparametersobject" )'));
}
block = bcoin.block.fromRaw(toString(args[0]), 'hex');
this._submitblock(block, callback);
};
RPC.prototype._submitblock = function submitblock(block, callback) {
if (block.prevBlock !== this.chain.tip.hash)
return callback(null, 'rejected: inconclusive-not-best-prevblk');
this.chain.add(block, function(err, total) {
if (err) {
if (err.type === 'VerifyError')
return callback(null, 'rejected: ' + err.reason);
return callback(err);
}
callback(null, null);
});
};
RPC.prototype.getblocktemplate = function getblocktemplate(args, callback) {
var self = this;
var mode = 'template';
var version = -1;
var coinbase = true;
var i, opt, lpid, keys, rules;
var cap, coinbasevalue, coinbasetxn;
if (args.help || args.length > 1) if (args.help || args.length > 1)
return callback(new RPCError('getblocktemplate ( "jsonrequestobject" )')); return callback(new RPCError('getblocktemplate ( "jsonrequestobject" )'));
@ -1487,25 +1536,32 @@ RPC.prototype.getblocktemplate = function getblocktemplate(args, callback) {
block = bcoin.block.fromRaw(opt.data, 'hex'); block = bcoin.block.fromRaw(opt.data, 'hex');
if (block.prevBlock !== self.chain.tip.hash) return this._submitblock(block, callback);
return callback(new RPCError('inconclusive-not-best-prevblk'));
return this.chain.add(block, function(err) {
if (err) {
if (err.reason)
return callback(null, err.reason);
return callback(null, 'rejected');
}
callback(null, null);
});
} }
if (Array.isArray(opt.rules)) { if (Array.isArray(opt.rules)) {
clientRules = []; rules = [];
for (i = 0; i < opt.rules.length; i++) for (i = 0; i < opt.rules.length; i++)
clientRules.push(String(opt.rules[i])); rules.push(toString(opt.rules[i]));
} else if (utils.isNumber(opt.maxversion)) { } else if (utils.isNumber(opt.maxversion)) {
maxVersion = opt.maxversion; version = opt.maxversion;
}
if (Array.isArray(opt.capabilities)) {
for (i = 0; i < opt.capabilities.length; i++) {
cap = toString(opt.capabilities[i]);
switch (cap) {
case 'coinbasetxn':
coinbasetxn = true;
break;
case 'coinbasevalue':
coinbasevalue = true;
break;
}
}
if (coinbasetxn && !coinbasevalue)
coinbase = false;
} }
} }
@ -1520,110 +1576,143 @@ RPC.prototype.getblocktemplate = function getblocktemplate(args, callback) {
this._poll(lpid, function(err) { this._poll(lpid, function(err) {
if (err) if (err)
return callback(err); return callback(err);
self._tmpl(version, coinbase, rules, callback);
});
};
self._createBlock(function(err, attempt) { RPC.prototype._tmpl = function _tmpl(version, coinbase, rules, callback) {
var self = this;
var txs = [];
var txIndex = {};
var i, j, tx, deps, input, dep, block;
var keys, vbavailable, vbrules, mutable, template;
callback = this.locker.lock(_tmpl, [version, coinbase, rules, callback]);
if (!callback)
return;
this._createBlock(function(err, attempt) {
if (err)
return callback(err);
block = attempt.block;
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
txIndex[tx.hash('hex')] = i;
deps = [];
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
dep = txIndex[input.prevout.hash];
if (dep != null)
deps.push(dep);
}
txs.push({
data: tx.toRaw().toString('hex'),
txid: tx.rhash,
hash: tx.rwhash,
depends: deps,
fee: tx.getFee(),
sigops: tx.getSigops(),
weight: tx.getWeight()
});
}
keys = Object.keys(self.network.deployments);
vbavailable = {};
vbrules = [];
mutable = ['time', 'transactions', 'prevblock'];
if (version >= 2)
mutable.push('version/force');
utils.forEachSerial(keys, function(id, next) {
var deployment = self.network.deployments[id];
self.chain.getState(self.chain.tip, id, function(err, state) {
if (err)
return next(err);
switch (state) {
case constants.thresholdStates.DEFINED:
case constants.thresholdStates.FAILED:
break;
case constants.thresholdStates.LOCKED_IN:
block.version |= 1 << deployment.bit;
case constants.thresholdStates.STARTED:
vbavailable[id] = deployment.bit;
if (rules) {
if (rules.indexOf(id) === -1 && !deployment.force)
block.version &= ~(1 << deployment.bit);
}
break;
case constants.thresholdStates.ACTIVE:
vbrules.push(id);
if (rules) {
if (rules.indexOf(id) === -1 && !deployment.force)
return next(new RPCError('Client must support ' + id + '.'));
}
break;
}
next();
});
}, function(err) {
if (err) if (err)
return callback(err); return callback(err);
block = attempt.block; block.version >>>= 0;
for (i = 1; i < block.txs.length; i++) { template = {
tx = block.txs[i]; capabilities: ['proposal'],
txIndex[tx.hash('hex')] = i; version: block.version,
deps = []; rules: vbrules,
vbavailable: vbavailable,
vbrequired: 0,
previousblockhash: utils.revHex(block.prevBlock),
transactions: txs,
coinbaseaux: {
flags: attempt.coinbaseFlags.toString('hex')
},
coinbasevalue: attempt.coinbase.outputs[0].value,
longpollid: self.chain.tip.rhash + utils.pad32(self.mempool.totalTX),
target: utils.revHex(attempt.target.toString('hex')),
submitold: false,
mintime: block.ts,
maxtime: bcoin.now(),
mutable: mutable,
noncerange: '00000000ffffffff',
sigoplimit: attempt.witness
? constants.block.MAX_SIGOPS_WEIGHT
: constants.block.MAX_SIGOPS,
sizelimit: constants.block.MAX_SIZE,
weightlimit: constants.block.MAX_WEIGHT,
curtime: bcoin.now(),
bits: utils.hex32(block.bits),
height: attempt.height,
default_witness_commitment: attempt.witness
? attempt.coinbase.outputs[1].script.toJSON()
: undefined
};
for (j = 0; j < tx.inputs.length; j++) { if (!coinbase) {
input = tx.inputs[j]; template.coinbaseaux = undefined;
dep = txIndex[input.prevout.hash]; template.coinbasevalue = undefined;
if (dep != null) tx = block.txs[0];
deps.push(dep); template.coinbasetxn = {
}
txs.push({
data: tx.toRaw().toString('hex'), data: tx.toRaw().toString('hex'),
txid: tx.rhash, txid: tx.rhash,
hash: tx.rwhash, hash: tx.rwhash,
depends: deps, depends: [],
fee: tx.getFee(), fee: 0,
sigops: tx.getSigops(), sigops: tx.getSigops(),
weight: tx.getWeight() weight: tx.getWeight()
}); };
} }
keys = Object.keys(self.network.deployments); callback(null, template);
vbavailable = {};
vbrules = [];
mutable = ['time', 'transactions', 'prevblock'];
if (maxVersion >= 2)
mutable.push('version/force');
utils.forEachSerial(keys, function(id, next) {
var deployment = self.network.deployments[id];
self.chain.getState(self.chain.tip, id, function(err, state) {
if (err)
return next(err);
switch (state) {
case constants.thresholdStates.DEFINED:
case constants.thresholdStates.FAILED:
break;
case constants.thresholdStates.LOCKED_IN:
block.version |= 1 << deployment.bit;
case constants.thresholdStates.STARTED:
vbavailable[id] = deployment.bit;
if (clientRules) {
if (clientRules.indexOf(id) === -1 && !deployment.force)
block.version &= ~(1 << deployment.bit);
}
break;
case constants.thresholdStates.ACTIVE:
vbrules.push(id);
if (clientRules) {
if (clientRules.indexOf(id) === -1 && !deployment.force)
return next(new RPCError('Client must support ' + id + '.'));
}
break;
}
next();
});
}, function(err) {
if (err)
return callback(err);
block.version >>>= 0;
callback(null, {
capabilities: ['proposal'],
version: block.version,
rules: vbrules,
vbavailable: vbavailable,
vbrequired: 0,
previousblockhash: utils.revHex(block.prevBlock),
transactions: txs,
coinbaseaux: {
flags: new Buffer(attempt.coinbaseFlags, 'utf8').toString('hex')
},
coinbasevalue: attempt.coinbase.outputs[0].value,
longpollid: self.chain.tip.rhash + utils.pad32(self.mempool.totalTX),
target: utils.revHex(attempt.target.toString('hex')),
mintime: attempt.block.ts,
mutable: mutable,
noncerange: '00000000ffffffff',
sigoplimit: attempt.witness
? constants.block.MAX_SIGOPS_WEIGHT
: constants.block.MAX_SIGOPS,
sizelimit: constants.block.MAX_SIZE,
weightlimit: constants.block.MAX_WEIGHT,
curtime: block.ts,
bits: utils.hex32(block.bits),
height: attempt.height,
default_witness_commitment: attempt.witness
? attempt.coinbase.outputs[1].script.toJSON()
: undefined
});
});
}); });
}); });
}; };
@ -1632,60 +1721,77 @@ RPC.prototype._poll = function _poll(lpid, callback) {
var self = this; var self = this;
var watched, lastTX; var watched, lastTX;
if (lpid == null) if (typeof lpid !== 'string')
return callback(); return callback();
if (typeof lpid === 'string') { if (lpid.length !== 74)
if (lpid.length < 65) return callback(new RPCError('Invalid parameter.'));
return callback(new RPCError('Invalid parameter.'));
watched = lpid.slice(0, 64);
lastTX = +lpid.slice(64);
if (!utils.isHex(watched) || !utils.isNumber(lastTX))
return callback(new RPCError('Invalid parameter.'));
watched = utils.revHex(watched);
} else {
watched = this.chain.tip.hash;
lastTX = this.lastTX;
}
function listener() { watched = lpid.slice(0, 64);
if (self.chain.tip.hash !== watched || self.mempool.totalTX !== lastTX) { lastTX = +lpid.slice(64, 74);
self.chain.removeListener('block', listener);
self.mempool.removeListener('tx', listener);
return callback();
}
}
this.chain.on('block', listener); if (!utils.isHex(watched) || !utils.isNumber(lastTX))
this.mempool.on('tx', listener); return callback(new RPCError('Invalid parameter.'));
watched = utils.revHex(watched);
if (self.chain.tip.hash !== watched)
return callback();
this.once('clear block', callback);
}; };
RPC.prototype._clearBlock = function _clearBlock() { RPC.prototype._clearBlock = function _clearBlock() {
this.prevBlock = null;
this.currentBlock = null; this.currentBlock = null;
this.lastTX = 0; this.blockStart = 0;
this.start = 0; };
RPC.prototype._bindChain = function _bindChain() {
var self = this;
if (this._boundChain)
return;
this._boundChain = true;
this.chain.on('connect', function() {
if (!self.currentBlock)
return;
self._clearBlock();
self.emit('clear block');
});
this.mempool.on('tx', function() {
var diff;
if (!self.currentBlock)
return;
diff = utils.now() - self.blockStart;
if (diff > 5) {
self._clearBlock();
self.emit('clear block');
}
});
}; };
RPC.prototype._createBlock = function _createBlock(callback) { RPC.prototype._createBlock = function _createBlock(callback) {
var self = this; var self = this;
var diff = utils.now() - this.start;
var total = this.mempool.totalTX;
if (this.prevBlock !== this.chain.tip.hash this._bindChain();
|| (total !== this.lastTX && diff > 5)) {
return this.miner.createBlock(function(err, attempt) {
if (err)
return callback(err);
self.prevBlock = attempt.block.prevBlock;
self.currentBlock = attempt;
self.lastTX = self.mempool.totalTX;
self.start = utils.now();
callback(null, attempt);
});
}
callback(null, this.currentBlock); if (this.currentBlock)
return callback(null, this.currentBlock);
this.miner.createBlock(function(err, attempt) {
if (err)
return callback(err);
self.currentBlock = attempt;
self.blockStart = utils.now();
callback(null, attempt);
});
}; };
RPC.prototype.getmininginfo = function getmininginfo(args, callback) { RPC.prototype.getmininginfo = function getmininginfo(args, callback) {
@ -1714,8 +1820,8 @@ RPC.prototype.getmininginfo = function getmininginfo(args, callback) {
genproclimit: self.proclimit, genproclimit: self.proclimit,
networkhashps: hashps, networkhashps: hashps,
pooledtx: self.mempool.totalTX, pooledtx: self.mempool.totalTX,
testnet: self.network.type !== bcoin.network.main, testnet: self.network !== bcoin.network.main,
chain: self.network.type, chain: 'main',
generate: self.mining generate: self.mining
}); });
}); });
@ -1773,23 +1879,6 @@ RPC.prototype.prioritisetransaction = function prioritisetransaction(args, callb
callback(null, true); callback(null, true);
}; };
RPC.prototype.submitblock = function submitblock(args, callback) {
var block;
if (args.help || args.length < 1 || args.length > 2) {
return callback(new RPCError('submitblock "hexdata"'
+ ' ( "jsonparametersobject" )'));
}
block = bcoin.block.fromRaw(toString(args[0]), 'hex');
this.chain.add(block, function(err, total) {
if (err)
return callback(null, 'rejected');
callback(null, 'valid');
});
};
RPC.prototype._hashps = function _hashps(lookup, height, callback) { RPC.prototype._hashps = function _hashps(lookup, height, callback) {
var self = this; var self = this;
var minTime, maxTime, pb0, time, workDiff, timeDiff, ps; var minTime, maxTime, pb0, time, workDiff, timeDiff, ps;

View File

@ -40,6 +40,9 @@ function Miner(options) {
this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin'; this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin';
this.version = null; this.version = null;
if (typeof this.coinbaseFlags === 'string')
this.coinbaseFlags = new Buffer(this.coinbaseFlags, 'utf8');
this.pool = options.pool; this.pool = options.pool;
this.chain = options.chain; this.chain = options.chain;
this.logger = options.logger || this.chain.logger; this.logger = options.logger || this.chain.logger;
@ -144,7 +147,8 @@ Miner.prototype._open = function open(callback) {
if (err) if (err)
return callback(err); return callback(err);
self.logger.info('Miner loaded (flags=%s).', self.coinbaseFlags); self.logger.info('Miner loaded (flags=%s).',
self.coinbaseFlags.toString('utf8'));
callback(); callback();
}); });

View File

@ -57,6 +57,9 @@ function MinerBlock(options) {
this.timeout = null; this.timeout = null;
this.callback = null; this.callback = null;
if (typeof this.coinbaseFlags === 'string')
this.coinbaseFlags = new Buffer(this.coinbaseFlags, 'utf8');
this.coinbase = new bcoin.tx(); this.coinbase = new bcoin.tx();
this.coinbase.mutable = true; this.coinbase.mutable = true;
@ -417,7 +420,7 @@ MinerBlock.prototype.toRaw = function toRaw(writer) {
p.writeU32(this.block.version); p.writeU32(this.block.version);
p.writeU32(this.block.bits); p.writeU32(this.block.bits);
p.writeVarBytes(this.address.toRaw()); p.writeVarBytes(this.address.toRaw());
p.writeVarString(this.coinbaseFlags, 'utf8'); p.writeVarBytes(this.coinbaseFlags);
p.writeU8(this.witness ? 1 : 0); p.writeU8(this.witness ? 1 : 0);
p.writeVarint(this.block.txs.length - 1); p.writeVarint(this.block.txs.length - 1);
@ -443,7 +446,7 @@ MinerBlock.fromRaw = function fromRaw(data) {
var version = p.readU32(); var version = p.readU32();
var bits = p.readU32(); var bits = p.readU32();
var address = bcoin.address.fromRaw(p.readVarBytes()); var address = bcoin.address.fromRaw(p.readVarBytes());
var coinbaseFlags = p.readVarString('utf8'); var coinbaseFlags = p.readVarBytes();
var witness = p.readU8() === 1; var witness = p.readU8() === 1;
var count = p.readVarint(); var count = p.readVarint();
var txs = []; var txs = [];