diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index b17b9d65..775e72a5 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -299,194 +299,6 @@ Block.prototype._verify = function _verify() { return true; }; -Block.prototype.verifyContext = function verifyContext() { - var flags = {}; - var sigops = 0; - var prev, height, ts, i, j, tx, cb, input; - - if (this.isGenesis()) - return true; - - if (!this.chain) - return true; - - prev = this.chain.getBlock(this.prevBlock); - - // Ensure it's not an orphan - if (!prev) { - utils.debug('Block has no previous entry: %s', this.rhash); - return false; - } - - height = prev.height + 1; - - // Ensure the timestamp is correct - if (this.ts <= prev.getMedianTime()) { - utils.debug('Block time is lower than median: %s', this.rhash); - return false; - } - - // Ensure the miner's target is equal to what we expect - if (this.bits !== this.chain.getTarget(prev, this)) { - utils.debug('Block is using wrong target: %s', this.rhash); - return false; - } - - // Only allow version 2 blocks (coinbase height) - // once the majority of blocks are using it. - if (this.version < 2 && prev.isOutdated(2)) { - utils.debug('Block is outdated (v2): %s', this.rhash); - return false; - } - - // Only allow version 3 blocks (sig validation) - // once the majority of blocks are using it. - if (this.version < 3 && prev.isOutdated(3)) { - utils.debug('Block is outdated (v3): %s', this.rhash); - return false; - } - - // Only allow version 4 blocks (checklocktimeverify) - // once the majority of blocks are using it. - if (this.version < 4 && prev.isOutdated(4)) { - utils.debug('Block is outdated (v4): %s', this.rhash); - return false; - } - - // Only allow version 8 blocks (locktime median past) - // once the majority of blocks are using it. - // if (this.version < 8 && prev.isOutdated(8)) { - // utils.debug('Block is outdated (v8): %s', this.rhash); - // return false; - // } - - // Can't verify any further when merkleblock or headers. - if (this.subtype !== 'block') - return true; - - // Make sure the height contained in the coinbase is correct. - if (this.version >= 2 && prev.isUpgraded(2)) { - cb = bcoin.script.getCoinbaseData(this.txs[0].inputs[0].script); - - // Make sure the coinbase is parseable. - if (!cb) { - utils.debug('Block has malformed coinbase: %s', this.rhash); - return false; - } - - // Make sure coinbase height is equal to the actual height. - if (cb.height !== height) { - utils.debug('Block has bad coinbase height: %s', this.rhash); - return false; - } - } - - // Signature validation is now enforced (bip66) - if (!(this.version >= 3 && prev.isUpgraded(3))) - flags.dersig = false; - - // CHECKLOCKTIMEVERIFY is now usable (bip65) - if (!(this.version >= 4 && prev.isUpgraded(4))) - flags.checklocktimeverify = false; - - // Use nLockTime median past (bip113) - // https://github.com/btcdrak/bips/blob/d4c9a236ecb947866c61aefb868b284498489c2b/bip-0113.mediawiki - // Support version bits: - // https://gist.github.com/sipa/bf69659f43e763540550 - // http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-August/010396.html - // if (this.version >= 8 && prev.isUpgraded(8)) - // flags.locktimeMedian = true; - - // If we are an ancestor of a checkpoint, we can - // skip the input verification. - if (height < network.checkpoints.lastHeight && !network.checkpoints[height]) - flags.scriptChecks = false; - - // Get timestamp for tx.isFinal(). - ts = flags.locktimeMedian - ? prev.getMedianTime() - : this.ts; - - // Check all transactions - for (i = 0; i < this.txs.length; i++) { - tx = this.txs[i]; - - // Transactions must be finalized with - // regards to nSequence and nLockTime. - if (!tx.isFinal(height, ts)) { - utils.debug('TX is not final: %s (%s)', this.rhash, i); - return false; - } - - // Check for tx sigops limits - // Bitcoind does not check for this when accepting - // a block even though it probably should. - // if (tx.getSigops(true) > constants.script.maxTxSigops) { - // // Block 71036 abused checksig to - // // include a huge number of sigops. - // utils.debug('Block TX has too many sigops: %s', this.rhash); - // if (!(network.type === 'main' && height === 71036)) - // return false; - // } - - // Check for block sigops limits - // Start counting P2SH sigops once block - // timestamps reach March 31st, 2012. - if (this.ts >= constants.block.bip16time) - sigops += tx.getSigops(true); - else - sigops += tx.getSigops(); - - if (sigops > constants.script.maxBlockSigops) { - utils.debug('Block has too many sigops: %s', this.rhash); - return false; - } - - // BIP30 - Ensure there are no duplicate txids - // this.node.hasTX(tx.hash('hex'), function(err, has) { - // if (has) - if (this.chain[tx.hash('hex')]) { - // Blocks 91842 and 91880 created duplicate - // txids by using the same exact output script - // and extraNonce. - utils.debug('Block is overwriting txids: %s', this.rhash); - if (!(network.type === 'main' && (height === 91842 || height === 91880))) - return false; - } - - // Verify the inputs of every tx (CheckInputs) - if (flags.scriptChecks !== false) { - if (tx.isCoinbase()) - continue; - - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - - // We need the previous output in order - // to verify the script. - if (!input.output) - continue; - - // Ensure tx is not double spending an output - if (!input.output) { - utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)', - this.rhash, tx.hash('hex'), - input.prevout.hash + '/' + input.prevout.index); - return false; - } - - // Verify the script - if (!tx.verify(j, true, flags)) { - utils.debug('Block has invalid inputs: %s', this.rhash); - return false; - } - } - } - } - - return true; -}; - Block.prototype.isGenesis = function isGenesis() { return this.hash('hex') === network.genesis.hash; }; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index c28878d4..d2f143b9 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -39,6 +39,8 @@ function Chain(options) { this.request = new utils.RequestCache(); this.loading = false; this.tip = null; + this.mempool = options.mempool; + this.blockdb = options.blockdb; this.orphan = { map: {}, @@ -261,16 +263,16 @@ Chain.prototype._preload = function _preload(callback) { Chain.prototype._saveBlock = function _saveBlock(block, callback) { var self = this; - var node = bcoin.node.global; - if (!node) + if (!this.blockdb) return callback(); - node.block.saveBlock(block, function(err) { + this.blockdb.block.saveBlock(block, function(err) { if (err) return callback(err); - node.mempool.addBlock(block); + if (self.mempool) + self.mempool.addBlock(block); return callback(); }); @@ -278,19 +280,19 @@ Chain.prototype._saveBlock = function _saveBlock(block, callback) { Chain.prototype._removeBlock = function _removeBlock(tip, callback) { var self = this; - var node = bcoin.node.global; - if (!node) + if (!this.blockdb) return callback(); - node.block.removeBlock(tip, function(err, block) { + this.blockdb.removeBlock(tip, function(err, block) { if (err) return callback(err); if (!block) return callback(); - node.mempool.removeBlock(block); + if (self.mempool) + self.mempool.removeBlock(block); self.emit('reorg block', block.hash('hex')); @@ -331,10 +333,9 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) }; Chain.prototype._verify = function _verify(block, prev) { - // var flags = constants.flags.MANDATORY_VERIFY_FLAGS; - var flags = {}; + var flags = constants.flags.MANDATORY_VERIFY_FLAGS; var sigops = 0; - var height, ts, i, tx, cb, coinbaseHeight; + var height, ts, i, tx, cb, coinbaseHeight, medianTime, locktimeMedian; // Skip the genesis block if (block.isGenesis()) @@ -347,9 +348,10 @@ Chain.prototype._verify = function _verify(block, prev) { } height = prev.height + 1; + medianTime = prev.getMedianTime(); // Ensure the timestamp is correct - if (block.ts <= prev.getMedianTime()) { + if (block.ts <= medianTime) { utils.debug('Block time is lower than median: %s', block.rhash); return false; } @@ -393,12 +395,12 @@ Chain.prototype._verify = function _verify(block, prev) { coinbaseHeight = true; // Signature validation is now enforced (bip66) - if (!(block.version >= 3 && prev.isUpgraded(3))) - flags.dersig = false; + if (block.version >= 3 && prev.isUpgraded(3)) + flags |= constants.flags.VERIFY_DERSIG; // CHECKLOCKTIMEVERIFY is now usable (bip65) - if (!(block.version >= 4 && prev.isUpgraded(4))) - flags.checklocktimeverify = false; + if (block.version >= 4 && prev.isUpgraded(4)) + flags |= constants.flags.VERIFY_CHECKLOCKTIMEVERIFY; // Use nLockTime median past (bip113) // https://github.com/btcdrak/bips/blob/d4c9a236ecb947866c61aefb868b284498489c2b/bip-0113.mediawiki @@ -406,7 +408,7 @@ Chain.prototype._verify = function _verify(block, prev) { // https://gist.github.com/sipa/bf69659f43e763540550 // http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-August/010396.html // if (block.version >= 8 && prev.isUpgraded(8)) - // flags.locktimeMedian = true; + // locktimeMedian = true; // Can't verify any further when merkleblock or headers. if (block.subtype !== 'block') @@ -430,9 +432,7 @@ Chain.prototype._verify = function _verify(block, prev) { } // Get timestamp for tx.isFinal(). - ts = flags.locktimeMedian - ? prev.getMedianTime() - : block.ts; + ts = locktimeMedian ? medianTime : block.ts; // Check all transactions for (i = 0; i < block.txs.length; i++) { @@ -474,12 +474,11 @@ Chain.prototype._verify = function _verify(block, prev) { }; Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callback) { - var node = bcoin.node.global; var height = prev.height + 1; var pending = block.txs.length; var called; - if (!node || block.subtype !== 'block') + if (!this.blockdb || block.subtype !== 'block') return callback(null, true); if (block.isGenesis()) @@ -499,7 +498,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba var hash = tx.hash('hex'); // BIP30 - Ensure there are no duplicate txids - node.block.hasTX(hash, function(err, result) { + self.blockdb.hasTX(hash, function(err, result) { if (called) return; @@ -522,10 +521,9 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba }; Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) { - var node = bcoin.node.global; var height = prev.height + 1; - if (!node || block.subtype !== 'block') + if (!this.blockdb || block.subtype !== 'block') return callback(null, true); if (block.isGenesis()) @@ -536,7 +534,7 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac if (height < network.checkpoints.lastHeight && !network.checkpoints[height]) return callback(null, true); - node.block.fillCoins(block.txs, function(err) { + this.blockdb.fillCoins(block.txs, function(err) { var i, j, input, hash; if (err) diff --git a/lib/bcoin/chainblock.js b/lib/bcoin/chainblock.js index a56ed23a..a6c67935 100644 --- a/lib/bcoin/chainblock.js +++ b/lib/bcoin/chainblock.js @@ -52,12 +52,16 @@ ChainBlock.prototype.getProof = function getProof() { return new bn(1).ushln(256).div(target.addn(1)); }; -ChainBlock.prototype.getChainwork = function() { +ChainBlock.prototype.getChainwork = function getChainwork() { var prev = this.prev; return (prev ? prev.chainwork : new bn(0)).add(this.getProof()); }; -ChainBlock.prototype.getMedianTime = function() { +ChainBlock.prototype.isGenesis = function isGenesis(version) { + return this.hash === network.genesis.hash; +}; + +ChainBlock.prototype.getMedianTime = function getMedianTime() { var entry = this; var median = []; var timeSpan = constants.block.medianTimespan; @@ -71,15 +75,15 @@ ChainBlock.prototype.getMedianTime = function() { return median[median.length / 2 | 0]; }; -ChainBlock.prototype.isOutdated = function(version) { +ChainBlock.prototype.isOutdated = function isOutdated(version) { return this.isSuperMajority(version, network.block.majorityRejectOutdated); }; -ChainBlock.prototype.isUpgraded = function(version) { +ChainBlock.prototype.isUpgraded = function isUpgraded(version) { return this.isSuperMajority(version, network.block.majorityEnforceUpgrade); }; -ChainBlock.prototype.isSuperMajority = function(version, required) { +ChainBlock.prototype.isSuperMajority = function isSuperMajority(version, required) { var entry = this; var found = 0; var majorityWindow = network.block.majorityWindow; @@ -94,7 +98,7 @@ ChainBlock.prototype.isSuperMajority = function(version, required) { return found >= required; }; -ChainBlock.prototype.toJSON = function() { +ChainBlock.prototype.toJSON = function toJSON() { return { hash: this.hash, version: this.version, @@ -107,7 +111,7 @@ ChainBlock.prototype.toJSON = function() { }; }; -ChainBlock.fromJSON = function(chain, json) { +ChainBlock.fromJSON = function fromJSON(chain, json) { return new ChainBlock(chain, json); }; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 6bf3a1fe..c6f07851 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -1213,9 +1213,13 @@ function pbkdf2(key, salt, iterations, dkLen) { if (typeof key === 'string') key = new Buffer(key, 'ascii'); + else if (Array.isArray(key)) + key = new Buffer(key); if (typeof salt === 'string') salt = new Buffer(salt, 'ascii'); + else if (Array.isArray(key)) + salt = new Buffer(salt); var DK = new Buffer(dkLen); var U = new Buffer(hLen); diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index b76a7031..672adb24 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -19,7 +19,7 @@ var fs = bcoin.fs; * Mempool */ -function Mempool(node, options) { +function Mempool(pool, options) { if (!(this instanceof Mempool)) return new Mempool(pool, options); @@ -27,9 +27,8 @@ function Mempool(node, options) { options = {}; this.options = options; - this.node = node; - this.pool = node.pool; - this.block = node.block; + this.pool = pool; + this.blockdb = pool.blockdb; this.txs = {}; this.spent = {}; @@ -38,6 +37,8 @@ function Mempool(node, options) { this.count = 0; this.locked = false; + Mempool.global = this; + this._init(); } @@ -178,6 +179,7 @@ Mempool.prototype.hasTX = function hasTX(hash) { Mempool.prototype.add = Mempool.prototype.addTX = function addTX(tx, peer, callback) { var self = this; + var flags = constants.flags.STANDARD_VERIFY_FLAGS; var hash = tx.hash('hex'); assert(tx.ts === 0); @@ -201,7 +203,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { this._lockTX(tx); - this.block.fillCoin(tx, function(err) { + this.blockdb.fillCoin(tx, function(err) { var i, input, dup, height, ts, priority; self._unlockTX(tx); @@ -294,7 +296,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { } } - if (!tx.verify(null, true)) { + if (!tx.verify(null, true, flags)) { return callback(new Error('TX did not verify.')); peer.reject({ data: tx.hash(), @@ -340,6 +342,8 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) { self.txs[hash] = tx; self.count++; self.size += tx.getSize(); + + self.emit('tx', tx); }); }; @@ -425,6 +429,7 @@ Mempool.prototype.removeTX = function removeTX(hash, callback) { delete this.txs[hash]; this.count--; this.size -= tx.getSize(); + this.emit('remove tx', tx); }; // Need to lock the mempool when diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index 24db9ae2..4b97b01e 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -137,46 +137,47 @@ Miner.prototype.addBlock = function addBlock(block) { }; Miner.prototype.addTX = function addTX(tx) { - var full, ts; + var flags, height, ts; - full = this.index.inputs.every(function(input) { - return !!input.output; - }); + flags = constants.flags.MANDATORY_VERIFY_FLAGS + | constants.flags.VERIFY_DERSIG + | constants.flags.VERIFY_CHECKLOCKTIMEVERIFY; // Cannot calculate fee if we don't have the prev_out. // Could possibly just burn some coins. if (this.options.burn === false) { - if (!full) - return; + if (!tx.hasPrevout()) + return false; } // Ignore if it's already in a block if (tx.height !== -1) - return; + return false; - if (!tx.verify(null, true)) - return; + if (!tx.verify(null, true, flags)) + return false; if (tx.isCoinbase()) - return; + return false; // Get timestamp for tx.isFinal() - bip113 - ts = this.block.version === 8 + height = this.last.height + 1; + ts = this.block.version >= 8 ? this.last.getMedianTime() : this.block.ts; - if (!tx.isFinal(this.last.height + 1, ts)) - return; + if (!tx.isFinal(height, ts)) + return false; // Deliver me from the block size debate, please if (this.block.getSize() + tx.getSize() > constants.blocks.maxSize) - return; + return false; // Add the tx to our block this.block.txs.push(tx); // Calculate our new reward fee - if (full) + if (tx.hasPrevout()) this.fee.iadd(tx.getFee()); // Update coinbase value diff --git a/lib/bcoin/node.js b/lib/bcoin/node.js index dc52df8e..3b7fe3bb 100644 --- a/lib/bcoin/node.js +++ b/lib/bcoin/node.js @@ -35,7 +35,7 @@ function Node(options) { if (this.options.network) network.set(this.options.network); - this.block = null; + this.blockdb = null; this.mempool = null; this.pool = null; this.chain = null; @@ -50,80 +50,27 @@ inherits(Node, EventEmitter); Node.prototype._init = function _init() { var self = this; + this.blockdb = new bcoin.blockdb(this.options.blockdb); + this.mempool = new bcoin.mempool(this, this.options.mempool); + if (!this.options.pool) this.options.pool = {}; - this.options.pool.type = 'full'; + this.options.pool.spv = false; + this.options.pool.blockdb = this.blockdb; + this.options.pool.mempool = this.mempool; - this.block = new bcoin.blockdb(this.options.block); - this.mempool = new bcoin.mempool(this, this.options.mempool); this.pool = new bcoin.pool(this.options.pool); this.chain = this.pool.chain; - if (0) - this.pool.on('block', function(block, peer) { - self.mempool.addBlock(block); - }); - - if (0) - this.pool.on('block', function(block, peer) { - self.block.saveBlock(block, function(err) { - if (err) - throw err; - - self.mempool.addBlock(block); - if (0) - var hash = block.txs[0].hash('hex'); - if (0) - self.block.getTX(hash, function(err, tx) { - if (err) throw err; - utils.print(tx); - }); - if (0) - self.block.getCoin(hash, 0, function(err, tx) { - if (err) throw err; - utils.print(tx); - }); - }); - }); - this.mempool.on('error', function(err) { self.emit('error', err); }); - this.chain.on('error', function(err) { - self.emit('error', err); - }); - this.pool.on('error', function(err) { self.emit('error', err); }); - this.pool.on('fork', function(data) { - if (data.block) - self.mempool.removeBlock(block); - }); - - if (0) - this.pool.on('fork', function(a, b) { - [a, b].forEach(function(hash) { - self.block.removeBlock(hash, function(err, block) { - if (err) - throw err; - - if (!block) - return; - - self.mempool.removeBlock(block); - }); - }); - }); - - this.pool.on('tx', function(tx, peer) { - assert(tx.ts === 0); - self.mempool.addTX(tx, peer); - }); - this.pool.startSync(); }; @@ -140,12 +87,12 @@ Node.prototype.getCoin = function getCoin(hash, index, callback) { if (this.mempool.isSpent(hash, index)) return callback(null, null); - this.block.getCoin(hash, index, function(err, coin) { + this.blockdb.getCoin(hash, index, function(err, coin) { if (err) return callback(err); if (!coin) - return; + return callback(); return callback(null, coin); }); @@ -159,7 +106,7 @@ Node.prototype.getCoinByAddress = function getCoinsByAddress(addresses, callback mempool = this.mempool.getCoinsByAddress(addresses); - this.block.getCoinsByAddress(addresses, function(err, coins) { + this.blockdb.getCoinsByAddress(addresses, function(err, coins) { if (err) return callback(err); @@ -181,10 +128,13 @@ Node.prototype.getTX = function getTX(hash, callback) { if (tx) return callback(null, tx); - this.block.getTX(hash, function(err, tx) { + this.blockdb.getTX(hash, function(err, tx) { if (err) return callback(err); + if (!tx) + return callback(); + return callback(null, tx); }); }; @@ -197,7 +147,7 @@ Node.prototype.isSpent = function isSpent(hash, index, callback) { if (this.mempool.isSpent(hash, index)) return callback(null, true); - this.block.isSpent(hash, index, callback); + this.blockdb.isSpent(hash, index, callback); }; Node.prototype.getTXByAddress = function getTXByAddress(addresses, callback) { @@ -208,7 +158,7 @@ Node.prototype.getTXByAddress = function getTXByAddress(addresses, callback) { mempool = this.mempool.getTXByAddress(addresses); - this.block.getTXByAddress(addresses, function(err, txs) { + this.blockdb.getTXByAddress(addresses, function(err, txs) { if (err) return callback(err); @@ -218,16 +168,20 @@ Node.prototype.getTXByAddress = function getTXByAddress(addresses, callback) { Node.prototype.fillCoin = function fillCoin(tx, callback) { callback = utils.asyncify(callback); + if (this.mempool.fillCoin(tx)) return callback(); - this.block.fillCoin(tx, callback); + + this.blockdb.fillCoin(tx, callback); }; Node.prototype.fillTX = function fillTX(tx, callback) { callback = utils.asyncify(callback); + if (this.mempool.fillTX(tx)) return callback(); - this.block.fillTX(tx, callback); + + this.blockdb.fillTX(tx, callback); }; /** diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index a619d344..d8fcc842 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -144,13 +144,11 @@ Peer.prototype._init = function init() { self.pool.setMisbehavior(self, 100); }); - if (this.pool.options.fullNode) { - this.once('version', function() { - utils.debug( - 'Sent version (%s): height=%s', - self.host, this.pool.chain.height()); - }); - } + this.once('version', function() { + utils.debug( + 'Sent version (%s): height=%s', + self.host, this.pool.chain.height()); + }); this._ping.timer = setInterval(function() { self.challenge = utils.nonce(); @@ -269,7 +267,7 @@ Peer.prototype.broadcast = function broadcast(items) { }; Peer.prototype.updateWatch = function updateWatch() { - if (this.pool.options.fullNode) + if (!this.pool.options.spv) return; if (this.ack) diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 26bc0df1..2e2a37d9 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -32,24 +32,24 @@ function Pool(options) { this.options = options; - if (this.options.debug) + if (options.debug) bcoin.debug = this.options.debug; - if (this.options.network) - network.set(this.options.network); + if (options.network) + network.set(options.network); - this.options.fullNode = !!this.options.fullNode; + options.spv = options.spv !== false; if (options.type === 'spv') - this.options.fullNode = false; + options.spv = true; else if (options.type === 'full') - this.options.fullNode = true; + options.spv = false; - this.options.headers = this.options.headers; - this.options.multiplePeers = this.options.multiplePeers; - this.options.relay = this.options.relay == null - ? (this.options.fullNode ? true : false) - : this.options.relay; + options.headers = options.headers; + options.multiplePeers = options.multiplePeers; + options.relay = options.relay == null + ? (!options.spv ? true : false) + : options.relay; this.originalSeeds = (options.seeds || network.seeds).map(utils.parseHost); this.setSeeds([]); @@ -58,20 +58,23 @@ function Pool(options) { this.destroyed = false; this.size = options.size || 32; - if (!this.options.fullNode) { - if (this.options.headers == null) - this.options.headers = true; - if (this.options.multiplePeers == null) - this.options.multiplePeers = true; + this.blockdb = options.blockdb; + this.mempool = options.mempool; + + if (options.spv) { + if (options.headers == null) + options.headers = true; + if (options.multiplePeers == null) + options.multiplePeers = true; } else { - if (this.options.headers == null) - this.options.headers = false; - if (this.options.multiplePeers == null) - this.options.multiplePeers = false; + if (options.headers == null) + options.headers = false; + if (options.multiplePeers == null) + options.multiplePeers = false; } - if (!this.options.headers) - this.options.multiplePeers = false; + if (!options.headers) + options.multiplePeers = false; this.syncing = false; this.synced = false; @@ -84,9 +87,11 @@ function Pool(options) { this.requestTimeout = options.requestTimeout || 600000; this.chain = new bcoin.chain({ - fullNode: this.options.fullNode, - multiplePeers: this.options.multiplePeers, - preload: this.options.preload + spv: options.spv, + multiplePeers: options.multiplePeers, + preload: options.preload, + blockdb: options.blockdb, + mempool: options.mempool }); this.watchMap = {}; @@ -115,7 +120,7 @@ function Pool(options) { this.block = { bestHeight: 0, bestHash: null, - type: this.options.fullNode ? 'block' : 'filtered', + type: !options.spv ? 'block' : 'filtered', invalid: {} }; @@ -133,10 +138,6 @@ function Pool(options) { this.validate = { // 5 days scan delta for obtaining TXs delta: 5 * 24 * 3600, - - // Minimum verification depth - minDepth: options.minValidateDepth || 0, - // getTX map map: {} }; @@ -148,7 +149,7 @@ function Pool(options) { }; // Added and watched wallets - this.options.wallets = this.options.wallets || []; + options.wallets = options.wallets || []; this.wallets = []; Pool.global = this; @@ -615,7 +616,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) { requested = self._response(block); // Emulate BIP37: emit all the filtered transactions. - if (self.options.fullNode && self.listeners('watched').length > 0) { + if (!self.options.spv && self.listeners('watched').length > 0) { block.txs.forEach(function(tx) { if (self.isWatched(tx)) self.emit('watched', tx, peer); @@ -857,22 +858,35 @@ Pool.prototype._handleTX = function _handleTX(tx, peer, callback) { callback = utils.asyncify(callback); - this._prehandleTX(tx, peer, function(err) { - var requested, added; + function addMempool(tx, peer, callback) { + if (!self.mempool) + return callback(); + if (tx.ts !== 0) + return callback(); + self.mempool.addTX(tx, peer, callback); + } + this._prehandleTX(tx, peer, function(err) { if (err) return callback(err); - requested = self._response(tx); - added = self._addTX(tx, 1); + addMempool(tx, peer, function(err) { + var requested, added; - if (added || tx.block) - self.emit('tx', tx, peer); + if (err) + utils.debug('Mempool error: %s', err.message); - if (!self.options.fullNode && tx.block) - self.emit('watched', tx, peer); + requested = self._response(tx); + added = self._addTX(tx, 1); - return callback(); + if (added || tx.block) + self.emit('tx', tx, peer); + + if (self.options.spv && tx.block) + self.emit('watched', tx, peer); + + return callback(); + }); }); }; @@ -1232,7 +1246,7 @@ Pool.prototype.addWallet = function addWallet(w) { self.sendTX(tx); }); - if (self.options.fullNode) + if (!self.options.spv) return; if (self._pendingSearch) @@ -1323,7 +1337,7 @@ Pool.prototype.searchWallet = function(w, h) { assert(!this.loading); - if (this.options.fullNode) + if (!this.options.spv) return; if (w == null) { @@ -1393,7 +1407,7 @@ Pool.prototype.search = function search(id, range, e) { assert(!this.loading); - if (this.options.fullNode) + if (!this.options.spv) return; if (typeof e === 'function') { @@ -1570,7 +1584,7 @@ Pool.prototype.getTX = function getTX(hash, range, cb) { if (!this.peers.load) return setTimeout(this.getBlock.bind(this, hash, cb), 1000); - if (this.options.fullNode) + if (!this.options.spv) return cb(new Error('Cannot get tx with full node')); hash = utils.toHex(hash); diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index edf98fbd..c2348e86 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -272,3 +272,33 @@ exports.userAgent = '/bcoin:' + exports.userVersion + '/'; exports.banTime = 24 * 60 * 60; exports.banScore = 100; + +// Script and locktime flags +exports.flags = { + VERIFY_NONE: 0, + VERIFY_P2SH: (1 << 0), + VERIFY_STRICTENC: (1 << 1), + VERIFY_DERSIG: (1 << 2), + VERIFY_LOW_S: (1 << 3), + VERIFY_NULLDUMMY: (1 << 4), + VERIFY_SIGPUSHONLY: (1 << 5), + VERIFY_MINIMALDATA: (1 << 6), + VERIFY_DISCOURAGE_UPGRADABLE_NOPS: (1 << 7), + VERIFY_CLEANSTACK: (1 << 8), + VERIFY_CHECKLOCKTIMEVERIFY: (1 << 9) +}; + +// Block validation +exports.flags.MANDATORY_VERIFY_FLAGS = exports.flags.VERIFY_P2SH; + +// Mempool validation +exports.flags.STANDARD_VERIFY_FLAGS = + exports.flags.MANDATORY_VERIFY_FLAGS + | exports.flags.VERIFY_DERSIG + | exports.flags.VERIFY_STRICTENC + | exports.flags.VERIFY_MINIMALDATA + | exports.flags.VERIFY_NULLDUMMY + | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS + | exports.flags.VERIFY_CLEANSTACK + | exports.flags.VERIFY_CHECKLOCKTIMEVERIFY + | exports.flags.VERIFY_LOW_S; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index ce87e377..4e78eede 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -198,10 +198,10 @@ script.verify = function verify(input, output, tx, i, flags) { var copy, res, redeem; var stack = []; - if (!flags) - flags = {}; + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (flags.sigpushonly !== false) { + if (flags & constants.flags.VERIFY_SIGPUSHONLY) { if (!script.isPushOnly(input)) return false; } @@ -210,7 +210,7 @@ script.verify = function verify(input, output, tx, i, flags) { script.execute(input, stack, tx, i, flags); // Copy the stack for P2SH - if (flags.verifyp2sh !== false) + if (flags & constants.flags.VERIFY_P2SH) copy = stack.slice(); // Execute the previous output script @@ -221,7 +221,7 @@ script.verify = function verify(input, output, tx, i, flags) { return false; // If the script is P2SH, execute the real output script - if (flags.verifyp2sh !== false && script.isScripthash(output)) { + if ((flags & constants.flags.VERIFY_P2SH) && script.isScripthash(output)) { // P2SH can only have push ops in the scriptSig if (!script.isPushOnly(input)) return false; @@ -250,7 +250,7 @@ script.verify = function verify(input, output, tx, i, flags) { } // Ensure there is nothing left on the stack - if (flags.cleanstack !== false) { + if (flags & constants.flags.VERIFY_CLEANSTACK) { if (stack.length !== 0) return false; } @@ -370,8 +370,8 @@ script._next = function _next(to, s, pc) { script.execute = function execute(data, stack, tx, index, flags, recurse) { var s = data.slice(); - if (!flags) - flags = {}; + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; if (s.length > constants.script.maxOps) return false; @@ -418,7 +418,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { case 'nop8': case 'nop9': case 'nop10': { - if (flags.discourage_nops !== false) + if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) return false; break; } @@ -908,7 +908,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { val = stack.pop(); - if (flags.verifynulldummy !== false) { + if (flags & constants.flags.VERIFY_NULLDUMMY) { if (!script.isDummy(val)) return false; } @@ -925,8 +925,8 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { } case 'checklocktimeverify': { // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 - if (flags.checklocktimeverify === false) { - if (flags.discourage_nops !== false) + if (!(flags & constants.flags.VERIFY_CHECKLOCKTIMEVERIFY)) { + if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) return false; break; } @@ -965,8 +965,8 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { } case 'nop1': { // OP_EVAL = OP_NOP1 - if (flags['eval'] !== true) { - if (flags.discourage_nops !== false) + if (!script.allowEval) { + if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) return false; break; } @@ -1126,10 +1126,13 @@ script.removeData = function removeData(s, data) { }; script.checkPush = function checkPush(op, value, flags) { - if (!flags) - flags = {}; + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (flags.minimaldata === false) + // Disabled for now. + return true; + + if (!(flags & constants.flags.VERIFY_MINIMALDATA)) return true; if (!op.pushdata) @@ -2038,13 +2041,13 @@ script.isData = function isData(data) { }; script.isValidKey = function isValidKey(key, flags) { - if (!flags) - flags = {}; + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; if (!utils.isBuffer(key)) return false; - if (flags.strictenc !== false) { + if (flags & constants.flags.VERIFY_STRICTENC) { if (!script.isKeyEncoding(key)) return false; } @@ -2073,8 +2076,8 @@ script.isKeyEncoding = function isKeyEncoding(key) { }; script.isValidSignature = function isValidSignature(sig, flags) { - if (!flags) - flags = {}; + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; if (!utils.isBuffer(sig)) return false; @@ -2083,19 +2086,19 @@ script.isValidSignature = function isValidSignature(sig, flags) { if (sig.length === 0) return true; - if (flags.dersig !== false - || flags.low_s !== false - || flags.strictenc !== false) { + if ((flags & constants.flags.VERIFY_DERSIG) + || (flags & constants.flags.VERIFY_LOW_S) + || (flags & constants.flags.VERIFY_STRICTENC)) { if (!script.isSignatureEncoding(sig)) return false; } - if (flags.low_s !== false) { + if (flags & constants.flags.VERIFY_LOW_S) { if (!script.isLowDER(sig)) return false; } - if (flags.strictenc !== false) { + if (flags & constants.flags.VERIFY_STRICTENC) { if (!script.isHashType(sig)) return false; } diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 7f9fb6f0..d575614f 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -809,13 +809,6 @@ TX.prototype.signatureHash = function signatureHash(index, s, type) { if (typeof index !== 'number') index = this.inputs.indexOf(index); - if (!Array.isArray(s)) { - type = s; - s = this.inputs[index].output.script; - if (bcoin.script.isScripthash(s)) - s = bcoin.script.getRedeem(this.inputs[index].script); - } - if (typeof type === 'string') type = constants.hashType[type];