From 6d5e2776293f3ef5126df946608635e4c22e9c55 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 23 Mar 2016 15:11:58 -0700 Subject: [PATCH] reject packets. pool refactor. --- lib/bcoin/abstractblock.js | 13 ++- lib/bcoin/block.js | 17 +++- lib/bcoin/chain.js | 53 +++++++++--- lib/bcoin/compactblock.js | 4 +- lib/bcoin/mempool.js | 80 ++---------------- lib/bcoin/merkleblock.js | 9 +- lib/bcoin/pool.js | 164 +++++++++++++++---------------------- 7 files changed, 150 insertions(+), 190 deletions(-) diff --git a/lib/bcoin/abstractblock.js b/lib/bcoin/abstractblock.js index daccc53b..ee6658a5 100644 --- a/lib/bcoin/abstractblock.js +++ b/lib/bcoin/abstractblock.js @@ -65,22 +65,29 @@ AbstractBlock.prototype.getSize = function getSize() { return this._size || this.render().length; }; -AbstractBlock.prototype.verify = function verify() { +AbstractBlock.prototype.verify = function verify(ret) { if (this.valid == null) - this.valid = this._verify(); + this.valid = this._verify(ret); return this.valid; }; -AbstractBlock.prototype.verifyHeaders = function verifyHeaders() { +AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) { + if (!ret) + ret = {}; + // Check proof of work if (!utils.testTarget(this.bits, this.hash())) { utils.debug('Block failed POW test: %s', this.rhash); + ret.reason = 'high-hash'; + ret.score = 50; return false; } // Check timestamp against now + 2 hours if (this.ts > utils.now() + 2 * 60 * 60) { utils.debug('Block timestamp is too high: %s', this.rhash); + ret.reason = 'time-too-new'; + ret.score = 0; return false; } diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 96187772..3339d207 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -168,23 +168,30 @@ Block.prototype.__defineGetter__('commitmentHash', function() { return this._commitmentHash; }); -Block.prototype._verify = function _verify() { +Block.prototype._verify = function _verify(ret) { var uniq = {}; var i, tx, hash; - if (!this.verifyHeaders()) + if (!ret) + ret = {}; + + if (!this.verifyHeaders(ret)) return false; // Size can't be bigger than MAX_BLOCK_SIZE if (this.txs.length > constants.block.maxSize || this.getVirtualSize() > constants.block.maxSize) { utils.debug('Block is too large: %s', this.rhash); + ret.reason = 'bad-blk-length'; + ret.score = 100; return false; } // First TX must be a coinbase if (!this.txs.length || !this.txs[0].isCoinbase()) { utils.debug('Block has no coinbase: %s', this.rhash); + ret.reason = 'bad-cb-missing'; + ret.score = 100; return false; } @@ -195,6 +202,8 @@ Block.prototype._verify = function _verify() { // The rest of the txs must not be coinbases if (i > 0 && tx.isCoinbase()) { utils.debug('Block more than one coinbase: %s', this.rhash); + ret.reason = 'bad-cb-multiple'; + ret.score = 100; return false; } @@ -202,6 +211,8 @@ Block.prototype._verify = function _verify() { hash = tx.hash('hex'); if (uniq[hash]) { utils.debug('Block has duplicate txids: %s', this.rhash); + ret.reason = 'bad-txns-duplicate'; + ret.score = 100; return false; } uniq[hash] = true; @@ -210,6 +221,8 @@ Block.prototype._verify = function _verify() { // Check merkle root if (this.getMerkleRoot() !== this.merkleRoot) { utils.debug('Block failed merkleroot test: %s', this.rhash); + ret.reason = 'bad-txnmrkleroot'; + ret.score = 100; return false; } diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 9071269a..6c5306ab 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -331,24 +331,24 @@ Chain.prototype._preload = function _preload(callback) { }); }; -Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) { +Chain.prototype._verifyContext = function _verifyContext(block, prev, peer, callback) { var self = this; - this._verify(block, prev, function(err, flags) { + this._verify(block, prev, peer, function(err, flags) { if (err) return callback(err); if (flags === false) return callback(null, false); - self._checkDuplicates(block, prev, function(err, result) { + self._checkDuplicates(block, prev, peer, function(err, result) { if (err) return callback(err); if (!result) return callback(null, false); - self._checkInputs(block, prev, flags, function(err, result) { + self._checkInputs(block, prev, flags, peer, function(err, result) { if (err) return callback(err); @@ -361,19 +361,22 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) }); }; -Chain.prototype._verify = function _verify(block, prev, callback) { +Chain.prototype._verify = function _verify(block, prev, peer, callback) { var self = this; var flags = constants.flags.MANDATORY_VERIFY_FLAGS; var height, ts, i, tx, coinbaseHeight; var medianTime, locktimeMedian, segwit; + var ret = {}; function done(err, result) { prev.free(); callback(err, result); } - if (!block.verify()) + if (!block.verify(ret)) { + self.emit('verify-error', block, ret.reason, ret.score, peer); return done(null, false); + } // Skip the genesis block if (block.isGenesis()) @@ -395,11 +398,13 @@ Chain.prototype._verify = function _verify(block, prev, callback) { // Ensure the timestamp is correct if (block.ts <= medianTime) { utils.debug('Block time is lower than median: %s', block.rhash); + self.emit('verify-error', block, 'time-too-old', 0, peer); return done(null, false); } if (block.bits !== self.getTarget(prev, block)) { utils.debug('Block is using wrong target: %s', block.rhash); + self.emit('verify-error', block, 'bad-diffbits', 100, peer); return done(null, false); } @@ -418,6 +423,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { // once the majority of blocks are using it. if (block.version < 2 && prev.isOutdated(2)) { utils.debug('Block is outdated (v2): %s', block.rhash); + self.emit('verify-error', block, 'bad-version', 0, peer); return done(null, false); } @@ -425,6 +431,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { // once the majority of blocks are using it. if (block.version < 3 && prev.isOutdated(3)) { utils.debug('Block is outdated (v3): %s', block.rhash); + self.emit('verify-error', block, 'bad-version', 0, peer); return done(null, false); } @@ -432,6 +439,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { // once the majority of blocks are using it. if (block.version < 4 && prev.isOutdated(4)) { utils.debug('Block is outdated (v4): %s', block.rhash); + self.emit('verify-error', block, 'bad-version', 0, peer); return done(null, false); } @@ -440,6 +448,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { if (network.segwitHeight !== -1 && height >= network.segwitHeight) { if (block.version < 5 && prev.isOutdated(5)) { utils.debug('Block is outdated (v5): %s', block.rhash); + self.emit('verify-error', block, 'bad-version', 0, peer); return done(null, false); } } @@ -481,6 +490,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { if (coinbaseHeight) { if (block.getCoinbaseHeight() !== height) { utils.debug('Block has bad coinbase height: %s', block.rhash); + self.emit('verify-error', block, 'bad-cb-height', 100, peer); return done(null, false); } } @@ -488,11 +498,13 @@ Chain.prototype._verify = function _verify(block, prev, callback) { if (block.version >= 5 && segwit) { if (block.commitmentHash !== block.getCommitmentHash()) { utils.debug('Block failed witnessroot test: %s', block.rhash); + self.emit('verify-error', block, 'bad-blk-wit-length', 100, peer); return done(null, false); } } else { if (block.hasWitness()) { utils.debug('Unexpected witness data found: %s', block.rhash); + self.emit('verify-error', block, 'unexpected-witness', 100, peer); return done(null, false); } } @@ -508,6 +520,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { // regards to nSequence and nLockTime. if (!tx.isFinal(height, ts)) { utils.debug('TX is not final: %s (%s)', block.rhash, i); + self.emit('verify-error', block, 'bad-txns-nonfinal', 10, peer); return done(null, false); } } @@ -516,7 +529,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) { }); }; -Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callback) { +Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, peer, callback) { var self = this; var height = prev.height + 1; @@ -540,8 +553,10 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba // and extraNonce. if (result) { utils.debug('Block is overwriting txids: %s', block.rhash); - if (!(network.type === 'main' && (height === 91842 || height === 91880))) + if (!(network.type === 'main' && (height === 91842 || height === 91880))) { + self.emit('verify-error', block, 'bad-txns-BIP30', 100, peer); return next(null, false); + } } next(null, true); @@ -549,7 +564,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba }, callback); }; -Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) { +Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, peer, callback) { var height = prev.height + 1; var scriptCheck = true; @@ -588,6 +603,7 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac if (sigops > constants.script.maxBlockSigops) { utils.debug('Block has too many sigops: %s', block.rhash); + self.emit('verify-error', block, 'bad-blk-sigops', 100, peer); return callback(null, false); } @@ -610,6 +626,7 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac utils.revHex(input.prevout.hash) + '/' + input.prevout.index); if (height < network.checkpoints.lastHeight) throw new Error('BUG: Spent inputs in historical data!'); + self.emit('verify-error', block, 'bad-txns-inputs-missingorspent', 100, peer); return callback(null, false); } @@ -661,8 +678,10 @@ Chain.prototype._checkReward = function _checkReward(block) { for (i = 1; i < block.txs.length; i++) actual.iadd(block.txs[i].getFee()); - if (claimed.cmp(actual) > 0) + if (claimed.cmp(actual) > 0) { + self.emit('verify-error', block, 'bad-cb-amount', 100, peer); return false; + } return true; }; @@ -910,6 +929,7 @@ Chain.prototype.onFlush = function onFlush(callback) { Chain.prototype.add = function add(initial, peer, callback, force) { var self = this; var total = 0; + var ret = {}; assert(this.loaded); @@ -951,6 +971,7 @@ Chain.prototype.add = function add(initial, peer, callback, force) { chain: !!self.invalid[prevHash] }, peer); self.invalid[hash] = true; + self.emit('verify-error', block, 'duplicate', 0, peer); return done(); } @@ -958,7 +979,7 @@ Chain.prototype.add = function add(initial, peer, callback, force) { // This is only necessary for new // blocks coming in, not the resolving // orphans. - if (block === initial && !block.verify()) { + if (block === initial && !block.verify(ret)) { self.invalid[hash] = true; self.emit('invalid', block, { height: height, @@ -966,6 +987,7 @@ Chain.prototype.add = function add(initial, peer, callback, force) { seen: false, chain: false }, peer); + self.emit('verify-error', block, ret.reason, ret.score, peer); return done(); } @@ -999,6 +1021,9 @@ Chain.prototype.add = function add(initial, peer, callback, force) { seen: true }, peer); + if (self.options.headers) + self.emit('verify-error', block, 'bad-prevblk', 0, peer); + return done(); } @@ -1021,6 +1046,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) { hash: hash, seen: false }, peer); + if (self.options.headers) + self.emit('verify-error', block, 'bad-prevblk', 0, peer); return done(); } @@ -1050,6 +1077,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) { checkpoint: true }, peer); + self.emit('verify-error', block, 'checkpoint mismatch', 100, peer); + return done(); } } @@ -1087,7 +1116,7 @@ Chain.prototype.add = function add(initial, peer, callback, force) { // Do "contextual" verification on our block // now that we're certain its previous // block is in the chain. - self._verifyContext(block, prev, function(err, verified) { + self._verifyContext(block, prev, peer, function(err, verified) { var entry; // Couldn't verify block. diff --git a/lib/bcoin/compactblock.js b/lib/bcoin/compactblock.js index 64616745..0a46857a 100644 --- a/lib/bcoin/compactblock.js +++ b/lib/bcoin/compactblock.js @@ -30,8 +30,8 @@ function CompactBlock(data) { utils.inherits(CompactBlock, bcoin.abstractblock); -CompactBlock.prototype._verify = function _verify() { - return this.verifyHeaders(); +CompactBlock.prototype._verify = function _verify(ret) { + return this.verifyHeaders(ret); }; CompactBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index bfaacbaa..9227bf48 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -54,6 +54,7 @@ function Mempool(node, options) { this.pendingTX = {}; this.pendingSize = 0; this.pendingLimit = 20 << 20; + this.locker = new bcoin.locker(this, this.add, this.pendingLimit); this.freeCount = 0; this.lastTime = 0; @@ -74,79 +75,11 @@ Mempool.flags = constants.flags.STANDARD_VERIFY_FLAGS; Mempool.mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; Mempool.prototype._lock = function _lock(func, args, force) { - var self = this; - var tx, called; - - if (force) { - assert(this.busy); - return function unlock() { - assert(!called); - called = true; - }; - } - - if (this.busy) { - if (func === Mempool.prototype.add) { - tx = args[0]; - this.pending.push(tx); - this.pendingTX[tx.hash('hex')] = true; - this.pendingSize += tx.getSize(); - if (this.pendingSize > this.pendingLimit) { - this.purgePending(); - return; - } - } - this.jobs.push([func, args]); - return; - } - - this.busy = true; - - return function unlock() { - var item, tx; - - assert(!called); - called = true; - - self.busy = false; - - if (func === Mempool.prototype.add) { - if (self.pending.length === 0) - self.emit('flush'); - } - - if (self.jobs.length === 0) - return; - - item = self.jobs.shift(); - - if (item[0] === Mempool.prototype.add) { - tx = item[1][0]; - assert(tx === self.pending.shift()); - delete self.pendingTX[tx.hash('hex')]; - self.pendingSize -= tx.getSize(); - } - - item[0].apply(self, item[1]); - }; + return this.locker.lock(func, args, force); }; Mempool.prototype.purgePending = function purgePending() { - var self = this; - - utils.debug('Warning: %dmb of pending txs. Purging.', - utils.mb(this.pendingSize)); - - this.pending.forEach(function(tx) { - delete self.pendingTX[tx.hash('hex')]; - }); - - this.pending.length = 0; - this.pendingSize = 0; - - this.jobs = this.jobs.filter(function(item) { - return item[0] !== Mempool.prototype.add; - }); + return this.locker.purgePending(); }; Mempool.prototype._init = function _init() { @@ -488,10 +421,9 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force) var hashes = []; var resolved = []; var batch = this.db.batch(); + var p; this.db.get('d/' + hash, function(err, buf) { - var p; - if (err && err.type !== 'NotFoundError') return callback(err); @@ -600,6 +532,10 @@ Mempool.prototype.removeTX = function removeTX(hash, callback, force) { }; Mempool.prototype.checkTX = function checkTX(tx, peer) { + return Mempool.checkTX(tx, peer); +}; + +Mempool.checkTX = function checkTX(tx, peer) { var i, input, output, size; var total = new bn(0); var uniq = {}; diff --git a/lib/bcoin/merkleblock.js b/lib/bcoin/merkleblock.js index 9887a687..ded95e47 100644 --- a/lib/bcoin/merkleblock.js +++ b/lib/bcoin/merkleblock.js @@ -99,13 +99,18 @@ MerkleBlock.prototype._verifyPartial = function _verifyPartial() { return true; }; -MerkleBlock.prototype._verify = function _verify() { - if (!this.verifyHeaders()) +MerkleBlock.prototype._verify = function _verify(ret) { + if (!ret) + ret = {}; + + if (!this.verifyHeaders(ret)) return false; // Verify the partial merkle tree if we are a merkleblock. if (!this._verifyPartial()) { utils.debug('Block failed merkle test: %s', this.rhash); + ret.reason = 'bad-txnmrklroot'; + ret.score = 100; return false; } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 6ce6e667..8e4d9beb 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -186,43 +186,24 @@ Pool.prototype._init = function _init() { } }); + this.chain.on('verify-error', function(block, reason, score, peer) { + peer.sendReject(block, reason, score); + }); + this.chain.on('fork', function(block, data, peer) { self.emit('fork', data, peer); - - if (!peer) - return; - - // If we failed a checkpoint, peer is misbehaving. - if (data.checkpoint) { - self.setMisbehavior(peer, 100); - return; - } }); this.chain.on('invalid', function(block, data, peer) { - if (!peer) - return; - - self.setMisbehavior(peer, 100); + self.emit('invalid', data, peer); }); this.chain.on('exists', function(block, data, peer) { - if (!peer) - return; - - // self.setMisbehavior(peer, 1); + self.emit('exists', data, peer); }); this.chain.on('orphan', function(block, data, peer) { - if (!peer) - return; - - // Increase banscore by 10 if we're using getheaders. - if (self.options.headers) { - // self.setMisbehavior(peer, 10); - return; - } - + self.emit('orphan', data, peer); // Resolve orphan chain self.resolveOrphan(self.peers.load, null, data.hash); }); @@ -520,9 +501,6 @@ Pool.prototype._addLoader = function _addLoader() { }; Pool.prototype.startSync = function startSync() { - if (!this.loaded) - return this.once('open', this.startSync.bind(this)); - this.syncing = true; this._startInterval(); @@ -567,7 +545,7 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer, callback) peer.host); if (headers.length > 2000) { - this.setMisbehavior(peer, 100); + peer.setMisbehavior(100); return callback(); } @@ -627,7 +605,7 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer, callback) { // Normally this is 500, but with older // versions locator.GetDistanceBack() is called. // if (hashes.length > 500) { - // this.setMisbehavior(peer, 100); + // peer.setMisbehavior(100); // return; // } @@ -1140,29 +1118,19 @@ Pool.prototype.watch = function watch(id) { if (id) { hid = utils.toHex(id); - if (this.watchMap[hid]) + + if (this.watchMap[hid]) { this.watchMap[hid]++; - else - this.watchMap[hid] = 1; + return; + } + + this.watchMap[hid] = 1; this.bloom.add(id); } // Send it to peers - if (this._pendingWatch) - return; - - this._pendingWatch = true; - - utils.nextTick(function() { - self._pendingWatch = false; - - if (self.peers.load) - self.peers.load.updateWatch(); - - for (i = 0; i < self.peers.regular.length; i++) - self.peers.regular[i].updateWatch(); - }); + this.updateWatch(); }; Pool.prototype.unwatch = function unwatch(id) { @@ -1171,9 +1139,6 @@ Pool.prototype.unwatch = function unwatch(id) { id = utils.toHex(id); - if (!this.bloom.test(id, 'hex')) - return; - if (!this.watchMap[id] || --this.watchMap[id] !== 0) return; @@ -1182,10 +1147,16 @@ Pool.prototype.unwatch = function unwatch(id) { // Reset bloom filter this.bloom.reset(); Object.keys(this.watchMap).forEach(function(id) { - this.bloom.add(id, 'hex'); + this.bloom.add(id); }, this); // Resend it to peers + this.updateWatch(); +}; + +Pool.prototype.updateWatch = function updateWatch() { + var self = this; + if (this._pendingWatch) return; @@ -1205,13 +1176,13 @@ Pool.prototype.unwatch = function unwatch(id) { // See "Filter matching algorithm": // https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki Pool.prototype.isWatched = function(tx, bloom) { - var i, prev, input, output; + var i, input, output; if (!bloom) bloom = this.bloom; - function testScript(script) { - return script.some(function(chunk) { + function testScript(code) { + return code.some(function(chunk) { if (!Buffer.isBuffer(chunk) || chunk.length === 0) return false; return bloom.test(chunk); @@ -1227,7 +1198,7 @@ Pool.prototype.isWatched = function(tx, bloom) { for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; // Test the output script - if (testScript(output.script)) + if (testScript(output.script.code)) return true; } @@ -1235,23 +1206,23 @@ Pool.prototype.isWatched = function(tx, bloom) { // 4. Test data elements in input scripts for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - prev = input.prevout.hash; - - if (typeof prev === 'string') - prev = new Buffer(prev, 'hex'); // Test the prev_out tx hash - if (bloom.test(prev)) + if (bloom.test(input.prevout.hash, 'hex')) return true; // Test the prev_out script if (input.output) { - if (testScript(input.output.script)) + if (testScript(input.output.script.code)) return true; } // Test the input script - if (testScript(input.script)) + if (testScript(input.script.code)) + return true; + + // Test the witness + if (testScript(input.witness.items)) return true; } @@ -1262,9 +1233,6 @@ Pool.prototype.isWatched = function(tx, bloom) { Pool.prototype.addWallet = function addWallet(wallet, callback) { var self = this; - if (!this.loaded) - return this.once('open', this.addWallet.bind(this, wallet, callback)); - callback = utils.asyncify(callback); if (this.options.spv) @@ -1295,13 +1263,13 @@ Pool.prototype.removeWallet = function removeWallet(wallet) { }; Pool.prototype.watchAddress = function watchAddress(address) { - address = bcoin.address.parse(address); - this.watch(address.hash); + var hash = bcoin.address.parse(address).hash; + this.watch(hash); }; Pool.prototype.unwatchAddress = function unwatchAddress(address) { - address = bcoin.address.parse(address); - this.unwatch(address.hash); + var hash = bcoin.address.parse(address).hash; + this.unwatch(hash); }; Pool.prototype.watchWallet = function watchWallet(wallet) { @@ -1464,13 +1432,9 @@ Pool.prototype.getData = function getData(peer, type, hash, options, callback) { if (exists) return callback(); - if (self.request.map[hash]) { - // if (callback) - // self.request.map[hash].callback.push(callback); + if (self.request.map[hash]) return callback(); - } - // item = new LoadRequest(self, peer, type, hash, callback); item = new LoadRequest(self, peer, type, hash); if (options.noQueue) @@ -1503,7 +1467,9 @@ Pool.prototype.getData = function getData(peer, type, hash, options, callback) { if (!options.force && type !== self.tx.type) return self.chain.has(hash, done); - return done(null, false); + return utils.nextTick(function() { + return done(null, false); + }); }; Pool.prototype.scheduleRequests = function scheduleRequests(peer) { @@ -1587,15 +1553,15 @@ Pool.prototype.getBlock = function getBlock(hash, callback) { if (!this.peers.load) return setTimeout(this.getBlock.bind(this, hash, callback), 1000); - this.getData(this.peers.load, 'block', hash, { force: true }, function(block) { + this.getData(this.peers.load, this.block.type, hash, { force: true }, function(block) { callback(null, block); }); this.scheduleRequests(this.peers.load); }; -Pool.prototype.sendBlock = function sendBlock(block) { - return this.broadcast(block); +Pool.prototype.sendBlock = function sendBlock(block, callback) { + return this.broadcast(block, callback); }; Pool.prototype.getTX = function getTX(hash, range, callback) { @@ -1668,27 +1634,22 @@ Pool.prototype.getTX = function getTX(hash, range, callback) { })(); }; -Pool.prototype.sendTX = function sendTX(tx) { - var flags = constants.flags.STANDARD_VERIFY_FLAGS; - // This is to avoid getting banned by - // bitcoind nodes. Possibly check - // sigops. Call isStandard and/or - // isStandardInputs as well. - if (tx.hasPrevout()) { - if (!tx.verify(null, true, flags)) { - utils.debug( - 'Could not relay TX (%s). It does not verify.', - tx.rhash); - return; - } - } - return this.broadcast(tx); +Pool.prototype.sendTX = function sendTX(tx, callback) { + callback = utils.asyncify(callback); + + // Failsafe to avoid getting banned by bitcoind nodes. + if (!bcoin.mempool.checkTX(tx)) + return callback(new Error('CheckTransaction failed.')); + + return this.broadcast(tx, callback); }; -Pool.prototype.broadcast = function broadcast(msg) { +Pool.prototype.broadcast = function broadcast(msg, callback) { var self = this; var e = new EventEmitter(); + callback = utils.once(callback); + var entry = { msg: msg, e: e, @@ -1696,6 +1657,7 @@ Pool.prototype.broadcast = function broadcast(msg) { var i = self.inv.list.indexOf(entry); if (i !== -1) self.inv.list.splice(i, 1); + callback(new Error('Timed out.')); }, this.inv.timeout) }; @@ -1703,9 +1665,17 @@ Pool.prototype.broadcast = function broadcast(msg) { this.peers.regular.forEach(function(peer) { var result = peer.broadcast(msg); - if (!result) return; + if (!result) + return; + result[0].once('request', function() { e.emit('ack', peer); + callback(); + }); + + result[0].once('reject', function(payload) { + e.emit('reject', payload, peer); + callback(new Error('TX was rejected: ' + payload.reason)); }); }); @@ -1916,7 +1886,7 @@ Pool.prototype.isMisbehaving = function isMisbehaving(host) { Pool.prototype.reject = function reject(peer, obj, reason, dos) { if (dos != null) - this.setMisbehavior(peer, dos); + peer.setMisbehavior(dos); utils.debug('Rejecting %s %s: reason=%s', obj.type, obj.hash('hex'), reason);