From b7929765868a65665dfd1c3c704ba414e585f818 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 21 Mar 2016 18:41:06 -0700 Subject: [PATCH] fixes. refactor. --- lib/bcoin/mempool.js | 101 ++++++++++++++++++------------------------- lib/bcoin/mtx.js | 6 +-- lib/bcoin/peer.js | 12 +++++ lib/bcoin/pool.js | 38 +++++++++++++--- lib/bcoin/tx.js | 13 ++++-- lib/bcoin/txdb.js | 23 +++++++++- 6 files changed, 119 insertions(+), 74 deletions(-) diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index b6f7b8fd..fdf731be 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -169,7 +169,7 @@ Mempool.prototype.addBlock = function addBlock(block) { callback = utils.ensure(callback); // Remove now-mined transactions // XXX should batch this - utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) { + utils.forEachSerial(block.txs, function(tx, next) { self.tx.remove(tx, next); }, callback); }; @@ -178,7 +178,7 @@ Mempool.prototype.removeBlock = function removeBlock(block, callback) { var self = this; callback = utils.ensure(callback); // XXX should batch this - utils.forEachSerial(block.txs, function(tx, next) { + utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) { self.tx.add(tx, next); }, callback); }; @@ -187,7 +187,7 @@ Mempool.prototype.get = Mempool.prototype.getTX = function getTX(hash, callback) { if (hash instanceof bcoin.tx) hash = hash.hash('hex'); - return this.tx.getTX(hash, index, callback); + return this.tx.getTX(hash, callback); }; Mempool.prototype.getCoin = function getCoin(hash, index, callback) { @@ -246,15 +246,15 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) { return callback(new Error('CheckTransaction failed')); if (tx.isCoinbase()) { - this.reject(peer, tx, 'coinbase', 100); + peer.sendReject(tx, 'coinbase', 100); return callback(new Error('coinbase as individual tx')); } ts = utils.now(); height = this.chain.height + 1; - if (self.requireStandard && !tx.isStandard(flags, ts, height, ret)) { - self.reject(peer, tx, ret.reason, 0); + if (this.requireStandard && !tx.isStandard(flags, ts, height, ret)) { + peer.sendReject(tx, ret.reason, 0); return callback(new Error('TX is not standard.')); } @@ -281,50 +281,50 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) { return callback(new Error('TX inputs are not standard.')); if (tx.getSigops(true) > constants.script.maxSigops) { - self.reject(peer, tx, 'bad-txns-too-many-sigops', 0); + peer.sendReject(tx, 'bad-txns-too-many-sigops', 0); return callback(new Error('TX has too many sigops.')); } total = new bn(0); for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - coin = input.coin; + coin = input.output; if (coin.isCoinbase()) { if (self.chain.height - coin.height < constants.tx.coinbaseMaturity) { - self.reject(peer, tx, 'bad-txns-premature-spend-of-coinbase', 0); + peer.sendReject(tx, 'bad-txns-premature-spend-of-coinbase', 0); return callback(new Error('Tried to spend coinbase prematurely.')); } } if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0) - return self.reject(peer, tx, 'bad-txns-inputvalues-outofrange', 100); + return peer.sendReject(tx, 'bad-txns-inputvalues-outofrange', 100); total.iadd(coin.value); } if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) - return self.reject(peer, tx, 'bad-txns-inputvalues-outofrange', 100); + return peer.sendReject(tx, 'bad-txns-inputvalues-outofrange', 100); if (tx.getOutputValue().cmp(total) > 0) { - self.reject(peer, tx, 'bad-txns-in-belowout', 100); + peer.sendReject(tx, 'bad-txns-in-belowout', 100); return callback(new Error('TX is spending coins it does not have.')); } fee = total.subn(tx.getOutputValue()); if (fee.cmpn(0) < 0) { - self.reject(peer, tx, 'bad-txns-fee-negative', 100); + peer.sendReject(tx, 'bad-txns-fee-negative', 100); return callback(new Error('TX has a negative fee.')); } if (fee.cmp(constants.maxMoney) > 0) { - return self.reject(peer, tx, 'bad-txns-fee-outofrange', 100); + peer.sendReject(tx, 'bad-txns-fee-outofrange', 100); return callback(new Error('TX has a fee higher than max money.')); } if (self.limitFree && fee.cmp(tx.getMinFee(true)) < 0) { - self.reject(peer, tx, 'insufficient fee', 0); + peer.sendReject(tx, 'insufficient fee', 0); return callback(new Error('Insufficient fee.')); } @@ -338,7 +338,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) { self.lastTime = now; if (self.freeCount > self.limitFreeRelay * 10 * 1000) { - self.reject(peer, tx, 'insufficient priority', 0); + peer.sendReject(tx, 'insufficient priority', 0); return callback(new Error('Too many free txs at once!')); } @@ -355,14 +355,14 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) { if (!result) { // Just say it's non-mandatory for now. - self.reject(peer, tx, 'non-mandatory-script-verify-flag', 0); + peer.sendReject(tx, 'non-mandatory-script-verify-flag', 0); return callback(new Error('TX did not verify.')); } self.tx.add(tx, function(err) { if (err) { if (err.message === 'Transaction is double-spending.') { - self.reject(peer, tx, 'bad-txns-inputs-spent', 0); + peer.sendReject(tx, 'bad-txns-inputs-spent', 0); } return callback(err); } @@ -389,17 +389,17 @@ Mempool.prototype.removeTX = function removeTX(hash, callback, force) { return; function getTX() { - if (hash.hash) { - hash = hash.hash('hex'); - return self.getTX(hash, function(err, tx) { - if (err) - return callback(err); - if (!tx) - return callback(); - return self.node.fillTX(hash, callback); - }); - } - return callback(null, hash); + if (hash instanceof bcoin.tx) + return callback(null, hash); + + hash = hash.hash('hex'); + return self.getTX(hash, function(err, tx) { + if (err) + return callback(err); + if (!tx) + return callback(); + return self.node.fillTX(hash, callback); + }); } getTX(function(err, tx) { @@ -421,64 +421,47 @@ Mempool.prototype.checkTX = function checkTX(tx, peer) { var uniq = {}; if (tx.inputs.length === 0) - return this.reject(peer, tx, 'bad-txns-vin-empty', 100); + return peer.sendReject(tx, 'bad-txns-vin-empty', 100); if (tx.outputs.length === 0) - return this.reject(peer, tx, 'bad-txns-vout-empty', 100); + return peer.sendReject(tx, 'bad-txns-vout-empty', 100); if (tx.getSize() > constants.block.maxSize) - return this.reject(peer, tx, 'bad-txns-oversize', 100); + return peer.sendReject(tx, 'bad-txns-oversize', 100); for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; if (output.value.cmpn(0) < 0) - return this.reject(peer, tx, 'bad-txns-vout-negative', 100); + return peer.sendReject(tx, 'bad-txns-vout-negative', 100); if (output.value.cmp(constants.maxMoney) > 0) - return this.reject(peer, tx, 'bad-txns-vout-toolarge', 100); + return peer.sendReject(tx, 'bad-txns-vout-toolarge', 100); total.iadd(output.value); - if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney)) - return this.reject(peer, tx, 'bad-txns-txouttotal-toolarge', 100); + if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) + return peer.sendReject(tx, 'bad-txns-txouttotal-toolarge', 100); } for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - if (uniq[input.out.hash]) - return this.reject(peer, tx, 'bad-txns-inputs-duplicate', 100); - uniq[input.out.hash] = true; + if (uniq[input.prevout.hash]) + return peer.sendReject(tx, 'bad-txns-inputs-duplicate', 100); + uniq[input.prevout.hash] = true; } if (tx.isCoinbase()) { size = bcoin.script.getSize(tx.inputs[0].script); if (size < 2 || size > 100) - return this.reject(peer, tx, 'bad-cb-length', 100); + return peer.sendReject(tx, 'bad-cb-length', 100); } else { for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - if (+input.out.hash === 0) - return this.reject(peer, tx, 'bad-txns-prevout-null', 10); + if (+input.prevout.hash === 0) + return peer.sendReject(tx, 'bad-txns-prevout-null', 10); } } return true; }; -Mempool.prototype.reject = function reject(peer, obj, reason, dos) { - utils.debug('Rejecting TX %s. Reason=%s.', obj.hash('hex'), reason); - - if (dos != null) - this.node.pool.setMisbehavior(peer, dos); - - if (!peer) - return false; - - // peer.reject({ - // reason: reason, - // data: obj.hash ? obj.hash() : [] - // }); - - return false; -}; - /** * Expose */ diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index f178f4ff..aefc05fd 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -585,8 +585,8 @@ MTX.prototype.isSigned = function isSigned(m) { return false; } else if (prev.isMultisig()) { // Grab `m` value (number of required sigs). - m = prev[0]; - if (Array.isArray(m)) + m = prev.code[0]; + if (Buffer.isBuffer(m)) m = m[0] || 0; // Ensure all members are signatures. @@ -709,7 +709,7 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) { // here since it will be ignored by // the isMultisig clause. // OP_PUSHDATA2 [redeem] - prev = this.inputs[i].getRedeem(); + prev = this.inputs[i].script.getRedeem(); size += utils.sizeIntv(prev.getSize()) + prev.getSize(); } diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index aa93b1ea..131fd97e 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -681,6 +681,18 @@ Peer.prototype.reject = function reject(details) { this._write(this.framer.reject(details)); }; +Peer.prototype.isMisbehaving = function isMisbehaving() { + return this.pool.isMisbehaving(this.host); +}; + +Peer.prototype.setMisbehavior = function setMisbehavior(dos) { + return this.pool.setMisbehavior(this, dos); +}; + +Peer.prototype.sendReject = function sendReject(obj, reason, dos) { + return this.pool.reject(this, obj, reason, dos); +}; + /** * Expose */ diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 4948033d..a23e3d5b 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -172,12 +172,17 @@ Pool.prototype._init = function _init() { } this.chain.on('block', function(block, entry, peer) { - self.emit('block', block, peer); // Emit merkle txs after the fact if (block.type === 'merkleblock') { - block.txs.forEach(function(tx) { - self._handleTX(tx, peer); + utils.forEachSerial(block.txs, function(tx, next) { + self._handleTX(tx, peer, next); + }, function(err) { + if (err) + return self.emit('error', err); + self.emit('block', block, peer); }); + } else { + self.emit('block', block, peer); } }); @@ -384,6 +389,7 @@ Pool.prototype._startInterval = function _startInterval() { function load() { if (!self.syncing) return; + utils.debug('Stall recovery: loading again.'); // self._load(); } @@ -672,11 +678,11 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer, callback) { Pool.prototype._handleInv = function _handleInv(hashes, peer, callback) { var self = this; + callback = utils.ensure(callback); + // Ignore for now if we're still syncing if (!this.synced) - return; - - callback = utils.ensure(callback); + return callback(); utils.forEachSerial(hashes, function(hash, next) { hash = utils.toHex(hash); @@ -823,7 +829,7 @@ Pool.prototype._createPeer = function _createPeer(options) { }); peer.on('tx', function(tx) { - self._handleTX(tx); + self._handleTX(tx, peer); }); peer.on('addr', function(data) { @@ -1911,6 +1917,24 @@ Pool.prototype.isMisbehaving = function isMisbehaving(host) { return false; }; +Pool.prototype.reject = function reject(peer, obj, reason, dos) { + if (dos != null) + this.setMisbehavior(peer, dos); + + utils.debug('Rejecting %s %s: reason=%s', + obj.type, obj.hash('hex'), reason); + + if (!peer) + return false; + + // peer.reject({ + // reason: reason, + // data: obj.hash() + // }); + + return false; +}; + /** * LoadRequest */ diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 74e69d3f..75a4ae7a 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -647,6 +647,9 @@ TX.prototype._getSigops = function _getSigops(scriptHash, accurate) { prev = input.script.getRedeem(); + if (!prev) + return; + total += prev.getSigops(true); } }, this); @@ -672,7 +675,7 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) { if (prev.isScripthash()) prev = input.script.getRedeem(); - if (prev.isWitnessScripthash()) { + if (prev && prev.isWitnessScripthash()) { prev = input.witness.getRedeem(); cost += prev.getSigops(true); } else { @@ -694,6 +697,7 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) { TX.prototype.isStandard = function isStandard(flags, ts, height, ret) { var i, input, output, type; var nulldata = 0; + var maxVersion = constants.tx.version; if (!ret) ret = { reason: null }; @@ -701,13 +705,16 @@ TX.prototype.isStandard = function isStandard(flags, ts, height, ret) { if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (this.version > constants.tx.version || this.version < 1) { + if (flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY) + maxVersion = Math.max(maxVersion, 2); + + if (this.version < 1 || this.version > maxVersion) { ret.reason = 'version'; return false; } if (ts != null) { - if (!tx.isFinal(ts, height)) { + if (!this.isFinal(ts, height)) { ret.reason = 'non-final'; return false; } diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index 22a54cad..82c871a5 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -1440,7 +1440,17 @@ TXPool.prototype.getTX = function getTX(hash, callback) { return callback(); return callback(err); } - return callback(null, bcoin.tx.fromExtended(tx)); + + if (!tx) + return callback(); + + try { + tx = bcoin.tx.fromExtended(tx); + } catch (e) { + return callback(e); + } + + return callback(null, tx); }); }; @@ -1455,7 +1465,16 @@ TXPool.prototype.getCoin = function getCoin(hash, index, callback) { return callback(err); } - return callback(null, bcoin.coin.fromExtended(coin)); + if (!coin) + return callback(); + + try { + coin = bcoin.coin.fromExtended(coin); + } catch (e) { + return callback(e); + } + + return callback(null, coin); }); };