diff --git a/lib/bcoin/http/rpc.js b/lib/bcoin/http/rpc.js index 083bbb9d..832426e4 100644 --- a/lib/bcoin/http/rpc.js +++ b/lib/bcoin/http/rpc.js @@ -29,6 +29,11 @@ function RPC(node) { this.feeRate = null; this.mining = false; this.proclimit = 0; + + this.prevBlock = null; + this.currentBlock = null; + this.lastTX = 0; + this.start = 0; } RPC.prototype.execute = function execute(json, callback) { @@ -892,7 +897,7 @@ RPC.prototype.blockToJSON = function blockToJSON(entry, block, txDetails, callba confirmations: self.chain.height - entry.height + 1, strippedsize: block.getBaseSize(), size: block.getSize(), - cost: block.getCost(), + weight: block.getCost(), height: entry.height, version: entry.version, merkleroot: utils.revHex(entry.merkleRoot), @@ -1160,64 +1165,215 @@ RPC.prototype.getblocktemplate = function getblocktemplate(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; if (args.help || args.length > 1) return callback(new RPCError('getblocktemplate ( "jsonrequestobject" )')); - this.miner.createBlock(function(err, attempt) { - if (err) - return callback(err); + if (args.length === 1) { + opt = args[0] || {}; - block = attempt.block; + if (opt.mode != null) { + mode = opt.mode; + if (mode !== 'template' && mode !== 'proposal') + return callback(new RPCError('Invalid mode.')); + } - for (i = 1; i < block.txs.length; i++) { - tx = block.txs[i]; - txIndex[tx.hash('hex')] = i; - deps = []; + lpid = opt.longpollid; - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - dep = txIndex[input.prevout.hash]; - if (dep != null) - deps.push(dep); - } + if (mode === 'proposal') { + if (!utils.isHex(opt.data)) + return callback(new RPCError('Invalid parameter.')); - txs.push({ - data: tx.toRaw().toString('hex'), - txid: tx.rhash, - hash: tx.rwhash, - depends: deps, - fee: tx.getFee(), - sigops: tx.getSigops(), - cost: tx.getCost() + block = bcoin.block.fromRaw(opt.data, 'hex'); + + if (block.prevBlock !== self.chain.tip.hash) + return callback(new RPCError('inconclusive-not-best-prevblk')); + + return self.chain.add(block, function(err) { + if (err) { + if (err.reason) + return callback(null, err.reason); + return callback(null, 'rejected'); + } + return callback(null, null); }); } - callback(null, { - capabilities: ['proposal'], - previousblockhash: utils.revHex(block.prevBlock), - transactions: txs, - coinbaseaux: attempt.coinbaseFlags.toString('hex'), - coinbasevalue: attempt.coinbase.outputs[0].value, - longpollid: self.chain.tip.rhash + self.mempool.total, - target: attempt.target.toString('hex'), - mintime: attempt.ts, - mutable: ['time', 'transactions', 'prevblock', 'version/force'], - noncerange: '00000000ffffffff', - sigoplimit: constants.block.MAX_SIGOPS_COST, - sizelimit: constants.block.MAX_SIZE, - costlimit: constants.block.MAX_COST, - curtime: block.ts, - bits: block.bits, - height: attempt.height, - default_witness_commitment: attempt.witness - ? attempt.coinbase.outputs[1].script.toJSON() - : undefined + if (Array.isArray(opt.rules)) { + clientRules = []; + for (i = 0; i < opt.rules.length; i++) + clientRules.push(String(opt.rules[i])); + } else if (utils.isNumber(opt.maxversion)) { + maxVersion = opt.maxversion; + } + } + + if (this.pool.peers.all.length === 0) + return callback(new RPCError('Bitcoin is not connected!')); + + if (!this.chain.isFull()) + return callback(new RPCError('Bitcoin is downloading blocks...')); + + this._poll(lpid, function(err) { + if (err) + return callback(err); + + self._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.getCost() + }); + } + + 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 + self.mempool.total, + target: utils.revHex(attempt.target.toString('hex')), + mintime: attempt.ts, + mutable: mutable, + noncerange: '00000000ffffffff', + sigoplimit: constants.block.MAX_SIGOPS_COST, + sizelimit: constants.block.MAX_SIZE, + costlimit: constants.block.MAX_COST, + curtime: block.ts, + bits: String(block.bits), + height: attempt.height, + default_witness_commitment: attempt.witness + ? attempt.coinbase.outputs[1].script.toJSON() + : undefined + }); + }); }); }); }; +RPC.prototype._poll = function _poll(lpid, callback) { + var self = this; + var watched, lastTX; + + if (lpid == null) + return callback(); + + if (typeof lpid === 'string') { + 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() { + if (self.chain.tip.hash !== watched || self.mempool.total !== lastTX) { + self.chain.removeListener('block', listener); + self.mempool.removeListener('tx', listener); + return callback(); + } + } + + this.chain.on('block', listener); + this.mempool.on('tx', listener); +}; + +RPC.prototype._createBlock = function _createBlock(callback) { + var self = this; + if (this.prevBlock !== this.chain.tip.hash + || (this.mempool.total !== this.lastTX && utils.now() - this.start > 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.total; + self.start = utils.now(); + callback(null, attempt); + }); + } + return callback(null, this.currentBlock); +}; + RPC.prototype.getmininginfo = function getmininginfo(args, callback) { var self = this; diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index 733fb3ee..0dc07fc6 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -331,27 +331,32 @@ main.deployments = { testdummy: { bit: 28, startTime: 1199145601, // January 1, 2008 - timeout: 1230767999 // December 31, 2008 + timeout: 1230767999, // December 31, 2008 + force: true }, csv: { bit: 0, startTime: 1462060800, // May 1st, 2016 - timeout: 1493596800 // May 1st, 2017 + timeout: 1493596800, // May 1st, 2017 + force: true }, witness: { bit: 1, startTime: 2000000000, // Far in the future - timeout: 2100000000 + timeout: 2100000000, + force: false }, mast: { bit: 2, startTime: 2000000000, // Far in the future - timeout: 2100000000 + timeout: 2100000000, + force: false } // bip109: { // bit: 28, // startTime: 1453939200, // Jan 28th, 2016 - // timeout: 1514764800 // Jan 1st, 2018 + // timeout: 1514764800, // Jan 1st, 2018 + // force: true // } }; @@ -554,22 +559,26 @@ testnet.deployments = { testdummy: { bit: 28, startTime: 1199145601, // January 1, 2008 - timeout: 1230767999 // December 31, 2008 + timeout: 1230767999, // December 31, 2008 + force: true }, csv: { bit: 0, startTime: 1456790400, // March 1st, 2016 - timeout: 1493596800 // May 1st, 2017 + timeout: 1493596800, // May 1st, 2017 + force: true }, witness: { bit: 1, startTime: 1462060800, // May 1st 2016 - timeout: 1493596800 // May 1st 2017 + timeout: 1493596800, // May 1st 2017 + force: false }, mast: { bit: 2, startTime: 2000000000, // Far in the future - timeout: 2100000000 + timeout: 2100000000, + force: false } }; @@ -699,22 +708,26 @@ regtest.deployments = { testdummy: { bit: 28, startTime: 0, - timeout: 999999999999 + timeout: 999999999999, + force: true }, csv: { bit: 0, startTime: 0, - timeout: 999999999999 + timeout: 999999999999, + force: true }, witness: { bit: 1, startTime: 0, - timeout: 999999999999 + timeout: 999999999999, + force: false }, mast: { bit: 2, startTime: 2000000000, // Far in the future - timeout: 2100000000 + timeout: 2100000000, + force: false } }; @@ -966,17 +979,20 @@ segnet4.deployments = { testdummy: { bit: 28, startTime: 1199145601, // January 1, 2008 - timeout: 1230767999 // December 31, 2008 + timeout: 1230767999, // December 31, 2008 + force: true }, csv: { bit: 0, startTime: 1456790400, // March 1st, 2016 - timeout: 1493596800 // May 1st, 2017 + timeout: 1493596800, // May 1st, 2017 + force: true }, witness: { bit: 1, startTime: 0, - timeout: 999999999999 + timeout: 999999999999, + force: false } };