diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index b5a2f5a2..e354aaf2 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -281,71 +281,19 @@ TXPool.prototype._add = function add(tx, map, callback, force) { unlock(); if (callback) callback(err, result); - }; + } - this.getTX(hash, function(err, existing) { + // Attempt to confirm tx before adding it. + this._confirm(tx, map, function(err, existing) { if (err) return done(err); + // Ignore if we already have this tx. + if (existing) + return done(null, true); + batch = self.db.batch(); - if (existing) { - // Tricky - update the tx and coin in storage, - // and remove pending flag to mark as confirmed. - if (existing.ts === 0 && tx.ts !== 0) { - assert(tx.height >= 0); - assert(existing.ps > 0); - - batch.put(prefix + 't/t/' + hash, tx.toExtended()); - batch.del(prefix + 't/p/t/' + hash); - batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.del(prefix + 't/s/s/' + pad32(existing.ps) + '/' + hash); - batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); - - map.all.forEach(function(id) { - batch.del(prefix + 't/p/a/' + id + '/' + hash); - batch.put( - prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.del( - prefix + 't/s/a/' + id + '/' + pad32(existing.ps) + '/' + hash); - batch.put( - prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); - }); - - utils.forEachSerial(tx.outputs, function(output, next, i) { - self.getCoin(hash, i, function(err, coin) { - if (err) - return next(err); - - if (!coin) - return next(); - - coin.height = tx.height; - - batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw()); - - next(); - }); - }, function(err) { - if (err) - return done(err); - - batch.write(function(err) { - if (err) - return done(err); - - self.emit('confirmed', tx, map); - self.emit('tx', tx, map); - - return done(null, true); - }); - }); - - return; - } - return done(null, false); - } - batch.put(prefix + 't/t/' + hash, tx.toExtended()); if (tx.ts === 0) { @@ -547,6 +495,85 @@ TXPool.prototype._add = function add(tx, map, callback, force) { }); }; +TXPool.prototype._confirm = function _confirm(tx, map, callback) { + var self = this; + var prefix = this.prefix + '/'; + var hash = tx.hash('hex'); + var batch; + + this.getTX(hash, function(err, existing) { + if (err) + return callback(err); + + // Haven't seen this tx before, add it. + if (!existing) + return callback(null, false); + + // Existing tx is already confirmed. Ignore. + if (existing.ts !== 0) + return callback(null, true); + + // The incoming tx won't confirm the existing one anyway. Ignore. + if (tx.ts === 0) + return callback(null, true); + + batch = self.db.batch(); + + // Tricky - update the tx and coin in storage, + // and remove pending flag to mark as confirmed. + assert(tx.height >= 0); + assert(existing.ps > 0); + + batch.put(prefix + 't/t/' + hash, tx.toExtended()); + batch.del(prefix + 't/p/t/' + hash); + batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.del(prefix + 't/s/s/' + pad32(existing.ps) + '/' + hash); + batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); + + map.all.forEach(function(id) { + batch.del(prefix + 't/p/a/' + id + '/' + hash); + batch.put(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.del(prefix + 't/s/a/' + id + '/' + pad32(existing.ps) + '/' + hash); + batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); + }); + + utils.forEachSerial(tx.outputs, function(output, next, i) { + var address = output.getAddress(); + + // Only update coins if this output is ours. + if (!address || !map[address].length) + return next(); + + self.getCoin(hash, i, function(err, coin) { + if (err) + return next(err); + + if (!coin) + return next(); + + coin.height = tx.height; + + batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw()); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + batch.write(function(err) { + if (err) + return callback(err); + + self.emit('confirmed', tx, map); + self.emit('tx', tx, map); + + return callback(null, true); + }); + }); + }); +}; + TXPool.prototype.remove = function remove(hash, callback) { var self = this; @@ -640,14 +667,15 @@ TXPool.prototype._remove = function remove(tx, map, callback) { if (!input.output) return; - if (address) { - map[address].forEach(function(id) { - batch.put(prefix + 'u/a/' + id - + '/' + input.prevout.hash - + '/' + input.prevout.index, - DUMMY); - }); - } + if (!address || !map[address].length) + return; + + map[address].forEach(function(id) { + batch.put(prefix + 'u/a/' + id + + '/' + input.prevout.hash + + '/' + input.prevout.index, + DUMMY); + }); batch.put(prefix + 'u/t/' + input.prevout.hash @@ -660,11 +688,12 @@ TXPool.prototype._remove = function remove(tx, map, callback) { tx.outputs.forEach(function(output, i) { var address = output.getAddress(); - if (address) { - map[address].forEach(function(id) { - batch.del(prefix + 'u/a/' + id + '/' + hash + '/' + i); - }); - } + if (!address || !map[address].length) + return; + + map[address].forEach(function(id) { + batch.del(prefix + 'u/a/' + id + '/' + hash + '/' + i); + }); batch.del(prefix + 'u/t/' + hash + '/' + i); }); @@ -680,78 +709,6 @@ TXPool.prototype._remove = function remove(tx, map, callback) { }); }; -TXPool.prototype._confirm = function _confirm(tx, map, callback) { - var self = this; - var prefix = this.prefix + '/'; - var hash = tx.hash('hex'); - var height = tx.height; - var ts = tx.ts; - var batch; - - this.getTX(hash, function(err, existing) { - if (err) - return done(err); - - batch = self.db.batch(); - - if (!existing) - return callback(null, false); - - if (!(existing.ts === 0 && tx.ts !== 0)) - return callback(null, true); - - // Tricky - update the tx and coin in storage, - // and remove pending flag to mark as confirmed. - assert(tx.height >= 0); - assert(existing.ps > 0); - - batch.put(prefix + 't/t/' + hash, tx.toExtended()); - batch.del(prefix + 't/p/t/' + hash); - batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.del(prefix + 't/s/s/' + pad32(existing.ps) + '/' + hash); - batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); - - map.all.forEach(function(id) { - batch.del(prefix + 't/p/a/' + id + '/' + hash); - batch.put( - prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.del( - prefix + 't/s/a/' + id + '/' + pad32(existing.ps) + '/' + hash); - batch.put( - prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); - }); - - utils.forEachSerial(tx.outputs, function(output, next, i) { - self.getCoin(hash, i, function(err, coin) { - if (err) - return next(err); - - if (!coin) - return next(); - - coin.height = tx.height; - - batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw()); - - next(); - }); - }, function(err) { - if (err) - return done(err); - - batch.write(function(err) { - if (err) - return done(err); - - self.emit('confirmed', tx, map); - self.emit('tx', tx, map); - - return callback(null, true); - }); - }); - }); -}; - TXPool.prototype.unconfirm = function unconfirm(hash, callback) { var self = this; @@ -823,6 +780,9 @@ TXPool.prototype._unconfirm = function unconfirm(tx, map, callback) { if (!coin) return next(); + if (!address || !map[address].length) + return next(); + coin.height = tx.height; batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw());