From 061548f2ac5d14ddf3829a39dc18883afcc074e8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 17 Sep 2016 15:27:31 -0700 Subject: [PATCH] rpc: more mining nonsense. --- lib/http/rpc.js | 513 +++++++++++++++++++++++----------------- lib/miner/miner.js | 6 +- lib/miner/minerblock.js | 7 +- 3 files changed, 311 insertions(+), 215 deletions(-) diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 4425cff0..84be8bb6 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -12,6 +12,7 @@ var crypto = require('../crypto/crypto'); var assert = utils.assert; var constants = bcoin.constants; var NetworkAddress = require('../primitives/netaddress'); +var EventEmitter = require('events').EventEmitter; var fs; try { @@ -24,6 +25,8 @@ function RPC(node) { if (!(this instanceof RPC)) return new RPC(node); + EventEmitter.call(this); + this.node = node; this.network = node.network; this.chain = node.chain; @@ -39,12 +42,13 @@ function RPC(node) { this.mining = false; this.proclimit = 0; - this.prevBlock = null; this.currentBlock = null; - this.lastTX = 0; - this.start = 0; + this.blockStart = 0; + this._boundChain = false; } +utils.inherits(RPC, EventEmitter); + RPC.prototype.execute = function execute(json, callback) { switch (json.method) { case 'stop': @@ -1385,51 +1389,46 @@ RPC.prototype.verifychain = function verifychain(args, callback) { * Mining */ -RPC.prototype.getwork = function getwork(args, callback, force) { - var self = this; - var i, data, abbr, field, block, header; +RPC.prototype._submitwork = function getwork(data, callback) { + var data, block, header; - callback = this.locker.lock(getwork, [args, callback], force); + if (data.length !== 128) + return callback(new RPCError('Invalid parameter.')); - if (!callback) - return; + if (!this.currentBlock) + return this._getwork(callback); - if (args.length > 1) - return callback(new RPCError('getwork ( "data" )')); + data = data.slice(0, 80); - if (args.length === 1 && this.currentBlock) { - if (!utils.isHex(args[0])) - return callback(new RPCError('Invalid parameter.')); + reverseEndian(data); - if (args[0].length !== 256) - return callback(new RPCError('Invalid parameter.')); + header = bcoin.headers.fromAbbr(data); + block = this.currentBlock.block; - data = new Buffer(args[0], 'hex').slice(0, 80); - reverseEndian(data); - - header = bcoin.headers.fromAbbr(data); - 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); - }); + if (header.prevBlock !== block.prevBlock + || header.merkleRoot !== block.merkleRoot + || header.bits !== block.bits) { + return callback(null, false); } + 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) { if (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 txs = []; - var txIndex = {}; - var mode = 'template'; - var maxVersion = -1; - var i, j, tx, deps, input, dep, block; - var opt, lpid, keys, vbavailable, vbrules, mutable, clientRules; + var data; - callback = this.locker.lock(getblocktemplate, [args, callback]); + callback = this.locker.lock(getwork, [args, callback]); if (!callback) 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) return callback(new RPCError('getblocktemplate ( "jsonrequestobject" )')); @@ -1487,25 +1536,32 @@ RPC.prototype.getblocktemplate = function getblocktemplate(args, callback) { block = bcoin.block.fromRaw(opt.data, 'hex'); - if (block.prevBlock !== self.chain.tip.hash) - 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); - }); + return this._submitblock(block, callback); } if (Array.isArray(opt.rules)) { - clientRules = []; + rules = []; 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)) { - 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) { if (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) return callback(err); - block = attempt.block; + block.version >>>= 0; - for (i = 1; i < block.txs.length; i++) { - tx = block.txs[i]; - txIndex[tx.hash('hex')] = i; - deps = []; + template = { + capabilities: ['proposal'], + version: block.version, + 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++) { - input = tx.inputs[j]; - dep = txIndex[input.prevout.hash]; - if (dep != null) - deps.push(dep); - } - - txs.push({ + if (!coinbase) { + template.coinbaseaux = undefined; + template.coinbasevalue = undefined; + tx = block.txs[0]; + template.coinbasetxn = { data: tx.toRaw().toString('hex'), txid: tx.rhash, hash: tx.rwhash, - depends: deps, - fee: tx.getFee(), + depends: [], + fee: 0, sigops: tx.getSigops(), weight: tx.getWeight() - }); + }; } - keys = Object.keys(self.network.deployments); - 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 - }); - }); + callback(null, template); }); }); }; @@ -1632,60 +1721,77 @@ RPC.prototype._poll = function _poll(lpid, callback) { var self = this; var watched, lastTX; - if (lpid == null) + if (typeof lpid !== 'string') return callback(); - if (typeof lpid === 'string') { - if (lpid.length < 65) - 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; - } + if (lpid.length !== 74) + return callback(new RPCError('Invalid parameter.')); - function listener() { - if (self.chain.tip.hash !== watched || self.mempool.totalTX !== lastTX) { - self.chain.removeListener('block', listener); - self.mempool.removeListener('tx', listener); - return callback(); - } - } + watched = lpid.slice(0, 64); + lastTX = +lpid.slice(64, 74); - this.chain.on('block', listener); - this.mempool.on('tx', listener); + if (!utils.isHex(watched) || !utils.isNumber(lastTX)) + 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() { - this.prevBlock = null; this.currentBlock = null; - this.lastTX = 0; - this.start = 0; + this.blockStart = 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) { var self = this; - var diff = utils.now() - this.start; - var total = this.mempool.totalTX; - if (this.prevBlock !== this.chain.tip.hash - || (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); - }); - } + this._bindChain(); - 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) { @@ -1714,8 +1820,8 @@ RPC.prototype.getmininginfo = function getmininginfo(args, callback) { genproclimit: self.proclimit, networkhashps: hashps, pooledtx: self.mempool.totalTX, - testnet: self.network.type !== bcoin.network.main, - chain: self.network.type, + testnet: self.network !== bcoin.network.main, + chain: 'main', generate: self.mining }); }); @@ -1773,23 +1879,6 @@ RPC.prototype.prioritisetransaction = function prioritisetransaction(args, callb 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) { var self = this; var minTime, maxTime, pb0, time, workDiff, timeDiff, ps; diff --git a/lib/miner/miner.js b/lib/miner/miner.js index 92b73af2..58bc83f4 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -40,6 +40,9 @@ function Miner(options) { this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin'; this.version = null; + if (typeof this.coinbaseFlags === 'string') + this.coinbaseFlags = new Buffer(this.coinbaseFlags, 'utf8'); + this.pool = options.pool; this.chain = options.chain; this.logger = options.logger || this.chain.logger; @@ -144,7 +147,8 @@ Miner.prototype._open = function open(callback) { if (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(); }); diff --git a/lib/miner/minerblock.js b/lib/miner/minerblock.js index e807f409..51fb1911 100644 --- a/lib/miner/minerblock.js +++ b/lib/miner/minerblock.js @@ -57,6 +57,9 @@ function MinerBlock(options) { this.timeout = null; this.callback = null; + if (typeof this.coinbaseFlags === 'string') + this.coinbaseFlags = new Buffer(this.coinbaseFlags, 'utf8'); + this.coinbase = new bcoin.tx(); this.coinbase.mutable = true; @@ -417,7 +420,7 @@ MinerBlock.prototype.toRaw = function toRaw(writer) { p.writeU32(this.block.version); p.writeU32(this.block.bits); p.writeVarBytes(this.address.toRaw()); - p.writeVarString(this.coinbaseFlags, 'utf8'); + p.writeVarBytes(this.coinbaseFlags); p.writeU8(this.witness ? 1 : 0); p.writeVarint(this.block.txs.length - 1); @@ -443,7 +446,7 @@ MinerBlock.fromRaw = function fromRaw(data) { var version = p.readU32(); var bits = p.readU32(); var address = bcoin.address.fromRaw(p.readVarBytes()); - var coinbaseFlags = p.readVarString('utf8'); + var coinbaseFlags = p.readVarBytes(); var witness = p.readU8() === 1; var count = p.readVarint(); var txs = [];