diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index f1bf5d35..9157dea7 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -97,10 +97,6 @@ Mempool.prototype._init = function _init() { }); }; -Mempool.prototype.dynamicMemoryUsage = function dynamicMemoryUsage(callback) { - return utils.asyncify(callback)(null, this.totalSize); -}; - Mempool.prototype.open = function open(callback) { if (this.loaded) return utils.nextTick(callback); @@ -168,25 +164,50 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) { }); }; -Mempool.prototype.getRange = function getRange(options, callback) { - return callback(null, []); +Mempool.prototype.getRangeSync = function getRangeSync(options) { + return []; }; -Mempool.prototype.purgeOrphans = function purgeOrphans(callback) { - var self = this; - var batch = this.db.batch(); +Mempool.prototype.getRange = function getRange(options, callback) { + var ret; - callback = utils.ensure(callback); + callback = utils.asyncify(callback); + + try { + ret = this.getRangeSync(options); + } catch (e) { + return callback(e); + } + + return callback(null, ret); +}; + +Mempool.prototype.purgeOrphansSync = function purgeOrphansSync() { + var keys = Object.keys(this.orphans); + var key, i; this.waiting = {}; this.totalOrphans = 0; - Object.keys(this.orphans).forEach(function(key) { - self.totalSize -= self.orphans[key].length; - delete self.orphans[key]; - }); + for (i = 0; i < keys.length; i++) { + key = keys[i]; + this.totalSize -= this.orphans[key].length; + delete this.orphans[key]; + } +}; - return utils.nextTick(callback); +Mempool.prototype.purgeOrphans = function purgeOrphans(callback) { + var ret; + + callback = utils.asyncify(callback); + + try { + ret = this.purgeOrphansSync(); + } catch (e) { + return callback(e); + } + + return callback(null, ret); }; Mempool.prototype.getTXSync = function getTXSync(hash) { @@ -239,23 +260,31 @@ Mempool.prototype.isDoubleSpendSync = function isDoubleSpendSync(tx) { Mempool.prototype.get = Mempool.prototype.getTX = function getTX(hash, callback) { + var tx; + callback = utils.asyncify(callback); try { - return callback(null, this.getTXSync(hash)); + tx = this.getTXSync(hash); } catch (e) { return callback(e); } + + return callback(null, tx); }; Mempool.prototype.getCoin = function getCoin(hash, index, callback) { + var coin; + callback = utils.asyncify(callback); try { - return callback(null, this.getCoinSync(hash, index)); + coin = this.getCoinSync(hash, index); } catch (e) { return callback(e); } + + return callback(null, coin); }; Mempool.prototype.isSpent = function isSpent(hash, index, callback) { @@ -269,13 +298,11 @@ Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) { return callback(null, this.isDoubleSpendSync(tx)); }; -Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, callback) { +Mempool.prototype.getCoinsByAddressSync = function getCoinsByAddressSync(addresses) { var uniq = {}; var coins = []; var i, j, address, keys, key, coin, parts, hash, index; - callback = utils.asyncify(callback); - if (!Array.isArray(addresses)) addresses = [addresses]; @@ -283,37 +310,40 @@ Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, call for (i = 0; i < addresses.length; i++) { address = addresses[i]; - keys = this.addressMap.getKeys(address); + keys = this.addressMap.getCoins(address); for (j = 0; j < keys.length; j++) { key = keys[j]; parts = key.split('/'); hash = parts[0]; index = +parts[1]; - - try { - coin = this.getCoinSync(hash, index); - } catch (e) { - return callback(e); - } - - if (!coin) - continue; - - coins.push(coin); + coin = this.getCoinSync(hash, index); + if (coin) + coins.push(coin); } } + return coins; +}; + +Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, callback) { + var coins; + + callback = utils.asyncify(callback); + + try { + coins = this.getCoinsByAddressSync(addresses); + } catch (e) { + return callback(e); + } + return callback(null, coins); }; -Mempool.prototype.getByAddress = -Mempool.prototype.getTXByAddress = function getTXByAddress(addresses, callback) { +Mempool.prototype.getTXByAddressSync = function getTXByAddressSync(addresses, callback) { var uniq = {}; var txs = []; var i, j, address, hashes, hash, tx; - callback = utils.asyncify(callback); - if (!Array.isArray(addresses)) addresses = [addresses]; @@ -321,18 +351,14 @@ Mempool.prototype.getTXByAddress = function getTXByAddress(addresses, callback) for (i = 0; i < addresses.length; i++) { address = addresses[i]; - hashes = this.addressMap.getKeys(address); + hashes = this.addressMap.getTX(address); for (j = 0; j < hashes.length; j++) { hash = hashes[j]; if (uniq[hash]) continue; - try { - tx = this.getTXSync(hash); - } catch (e) { - return callback(e); - } + tx = this.getTXSync(hash); if (!tx) continue; @@ -343,16 +369,29 @@ Mempool.prototype.getTXByAddress = function getTXByAddress(addresses, callback) } } - return callback(null, txs); + return txs; }; -Mempool.prototype.fillTX = function fillTX(tx, callback) { - var i, input, tx; +Mempool.prototype.getByAddress = +Mempool.prototype.getTXByAddress = function getTXByAddress(addresses, callback) { + var txs; callback = utils.asyncify(callback); + try { + txs = this.getTXByAddressSync(addresses); + } catch (e) { + return callback(e); + } + + return callback(null, txs); +}; + +Mempool.prototype.fillTXSync = function fillTXSync(tx) { + var i, input, tx; + if (tx.isCoinbase()) - return callback(null, tx); + return tx; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; @@ -360,11 +399,7 @@ Mempool.prototype.fillTX = function fillTX(tx, callback) { if (input.coin) continue; - try { - tx = this.getTXSync(input.prevout.hash); - } catch (e) { - return callback(e); - } + tx = this.getTXSync(input.prevout.hash); if (!tx) continue; @@ -372,16 +407,26 @@ Mempool.prototype.fillTX = function fillTX(tx, callback) { input.coin = bcoin.coin(tx, input.prevout.index); } + return tx; +}; + +Mempool.prototype.fillTX = function fillTX(tx, callback) { + callback = utils.asyncify(callback); + + try { + tx = this.fillTXSync(tx); + } catch (e) { + return callback(e); + } + return callback(null, tx); }; -Mempool.prototype.fillCoins = function fillCoins(tx, callback) { - var input; - - callback = utils.asyncify(callback); +Mempool.prototype.fillCoinsSync = function fillCoinsSync(tx) { + var i, input; if (tx.isCoinbase()) - return callback(null, tx); + return tx; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; @@ -396,17 +441,32 @@ Mempool.prototype.fillCoins = function fillCoins(tx, callback) { } } + return tx; +}; + +Mempool.prototype.fillCoins = function fillCoins(tx, callback) { + callback = utils.asyncify(callback); + + try { + tx = this.fillCoinsSync(tx); + } catch (e) { + return callback(e); + } + return callback(null, tx); }; +Mempool.prototype.hasTXSync = function hasTXSync(hash) { + if (hash instanceof bcoin.tx) + hash = hash.hash('hex'); + + return this.txs[hash] != null; +}; + Mempool.prototype.has = Mempool.prototype.hasTX = function hasTX(hash, callback) { callback = utils.asyncify(callback); - - if (hash instanceof bcoin.tx) - hash = hash.hash('hex'); - - return callback(null, !!this.txs[hash]); + return callback(null, this.hasTXSync(hash)); }; Mempool.prototype.add = @@ -459,7 +519,7 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) { return callback(new VerifyError(tx, ret.reason, 0)); } - self._hasTX(tx, function(err, exists) { + self.seenTX(tx, function(err, exists) { if (err) return callback(err); @@ -524,21 +584,67 @@ function AddressMap() { this.map = {}; } -AddressMap.prototype.getKeys = function getKeys(address) { - return this.map[address] || []; +AddressMap.prototype.getTX = function getTX(address) { + var map = this.map[address]; + var keys = []; + var i, key; + + if (!map) + return keys; + + for (i = 0; i < map.length; i++) { + key = map[i]; + if (key.length !== 32) + continue; + keys.push(key.toString('hex')); + } + + return keys; +}; + +AddressMap.prototype.getCoins = function getCoins(address) { + var map = this.map[address]; + var keys = []; + var i, p, key; + + if (!map) + return keys; + + for (i = 0; i < map.length; i++) { + key = map[i]; + + if (key.length !== 36) + continue; + + p = new BufferReader(key); + p.start(); + + try { + key = p.readHash('hex') + '/' + p.readU32(); + } catch (e) { + continue; + } + + p.end(); + + keys.push(key); + } + + return keys; }; AddressMap.prototype.addTX = function addTX(tx) { - var hash = tx.hash('hex'); + var hash = tx.hash(); var addresses = tx.getAddresses(); - var address; - var i; + var address, i, map, index; for (i = 0; i < addresses.length; i++) { address = addresses[i]; if (!this.map[address]) this.map[address] = []; - this.map[address].push(hash); + map = this.map[address]; + index = binarySearch(map, hash, true); + map.splice(index + 1, 0, hash); } }; @@ -564,24 +670,24 @@ function binarySearch(items, key, insert) { return -1; if (start === 0) - return 0; + return -1; return start - 1; } AddressMap.prototype.removeTX = function removeTX(tx) { + var hash = tx.hash(); var addresses = tx.getAddresses(); - var address; - var map, i; + var address, map, index; for (i = 0; i < addresses.length; i++) { address = addresses[i]; map = this.map[address]; if (map) { - i = map.indexOf(hash); - if (i !== -1) - map.splice(i , 1); + index = binarySearch(map, hash); + if (index !== -1) + map.splice(index, 1); if (map.length === 0) delete this.map[address]; } @@ -590,32 +696,42 @@ AddressMap.prototype.removeTX = function removeTX(tx) { AddressMap.prototype.addCoin = function addCoin(coin) { var address = coin.getAddress(); - var key = coin.hash + '/' + coin.index; + var key = this._coinKey(coin.hash, coin.index); + var map, index; if (address) { if (!this.map[address]) this.map[address] = []; - this.map[address].push(key); + map = this.map[address]; + index = binarySearch(map, key, true); + map.splice(index + 1, 0, key); } }; +AddressMap.prototype._coinKey = function _coinKey(hash, index) { + var p = new BufferWriter(); + p.writeHash(hash); + p.writeU32(index); + return p.render(); +}; + AddressMap.prototype.removeCoin = function removeCoin(tx, i) { - var address, key, map, i; + var address, key, map, index; if (tx instanceof bcoin.input) { address = tx.getAddress(); - key = tx.prevout.hash + '/' + tx.prevout.index; + key = this._coinKey(tx.prevout.hash, tx.prevout.index); } else { address = tx.outputs[i].getAddress(); - key = tx.hash('hex') + '/' + i; + key = this._coinKey(tx.hash(), i); } map = this.map[address]; if (map) { - i = map.indexOf(key); - if (i !== -1) - map.splice(i, 1); + index = binarySearch(map, key); + if (index !== -1) + map.splice(index, 1); if (map.length === 0) delete this.map[address]; } @@ -624,35 +740,38 @@ AddressMap.prototype.removeCoin = function removeCoin(tx, i) { Mempool.prototype.addUnchecked = function addUnchecked(tx, callback) { var self = this; var hash = tx.hash('hex'); + var input, output, i, key, coin; this.txs[hash] = tx.toExtended(); this.addressMap.addTX(tx); - tx.inputs.forEach(function(input, i) { - var key = input.prevout.hash + '/' + input.prevout.index; - delete self.coins[key]; - self.spent[key] = hash; - self.addressMap.removeCoin(input); - }); + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + key = input.prevout.hash + '/' + input.prevout.index; + delete this.coins[key]; + this.spent[key] = hash; + this.addressMap.removeCoin(input); + } - tx.outputs.forEach(function(output, i) { - var coin = bcoin.coin(tx, i); - var key = coin.hash + '/' + coin.index; - self.coins[key] = coin.toRaw(); - self.addressMap.addCoin(coin); - }); + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + coin = bcoin.coin(tx, i); + key = coin.hash + '/' + coin.index; + this.coins[key] = coin.toRaw(); + this.addressMap.addCoin(coin); + } - self.totalSize += tx.getSize(); - self.emit('tx', tx); - self.emit('add tx', tx); + this.totalSize += tx.getSize(); + this.emit('tx', tx); + this.emit('add tx', tx); utils.debug('Added tx %s to the mempool.', tx.rhash); - if (self.options.relay) - self.node.broadcast(tx); + if (this.options.relay) + this.node.broadcast(tx); - self.resolveOrphans(tx, function(err, resolved) { + this.resolveOrphans(tx, function(err, resolved) { if (err) return callback(err); @@ -669,7 +788,7 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, callback) { Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) { var self = this; - var hash; + var hash, input, output, i, key, coin; callback = utils.asyncify(callback); @@ -689,24 +808,24 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) { this.addressMap.removeTX(tx); - tx.inputs.forEach(function(input, i) { - var key = input.prevout.hash + '/' + input.prevout.index; - delete self.coins[key]; - delete self.spent[key]; - self.addressMap.removeCoin(input); - }); + for (i = 0; i < tx.inputs.length; i++) { + inputs = tx.outputs[i]; + key = input.prevout.hash + '/' + input.prevout.index; + delete this.coins[key]; + delete this.spent[key]; + this.addressMap.removeCoin(input); + } - tx.outputs.forEach(function(output, i) { - var address = output.getAddress(); - var key = hash + '/' + i; + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + key = hash + '/' + i; + delete this.coins[key]; + delete this.spent[key]; + this.addressMap.removeCoin(tx, i); + } - delete self.coins[key]; - delete self.spent[key]; - self.addressMap.removeCoin(tx, i); - }); - - self.totalSize -= tx.getSize(); - self.emit('remove tx', tx); + this.totalSize -= tx.getSize(); + this.emit('remove tx', tx); return callback(); }; @@ -874,7 +993,7 @@ Mempool.prototype.hasOrphanSync = function hasOrphanSync(hash) { if (hash instanceof bcoin.tx) hash = hash.hash('hex'); - return !!this.orphans[hash]; + return this.orphans[hash] != null; }; Mempool.prototype.getOrphanSync = function getOrphanSync(hash) { @@ -910,31 +1029,19 @@ Mempool.prototype.getOrphan = function getOrphan(hash, callback) { return callback(null, orphan); }; -Mempool.prototype._hasTX = function hasTX(tx, callback) { - var self = this; +Mempool.prototype.seenTX = function hasTX(tx, callback) { var hash = tx.hash('hex'); - this.node.hasTX(hash, function(err, result) { - if (err) - return callback(err); + if (this.hasOrphanSync(hash)) + return utils.asyncify(callback)(null, true); - if (result) - return callback(null, result); - - self.hasOrphan(hash, function(err, result) { - if (err) - return callback(err); - - return callback(null, result); - }); - }); + return this.node.hasTX(hash, callback); }; -Mempool.prototype.storeOrphan = function storeOrphan(tx, callback) { - var self = this; +Mempool.prototype.storeOrphanSync = function storeOrphanSync(tx) { var prevout = {}; - var hash = tx.hash('hex'); - var i, input, p; + var hash = tx.hash(); + var i, input, key; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; @@ -946,74 +1053,99 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback) { assert(prevout.length > 0); - prevout.forEach(function(key) { - if (!self.waiting[key]) - self.waiting[key] = []; + for (i = 0; i < prevout.length; i++) { + key = prevout[i]; - self.waiting[key].push(hash); - }); + if (!this.waiting[key]) + this.waiting[key] = []; - self.totalOrphans++; + this.waiting[key].push(hash); + } - self.orphans[hash] = tx.toExtended(true); + this.totalOrphans++; - if (self.totalOrphans > Mempool.MAX_ORPHAN_TX) - return self.purgeOrphans(callback); + this.orphans[hash.toString('hex')] = tx.toExtended(true); - return utils.nextTick(callback); + if (this.totalOrphans > Mempool.MAX_ORPHAN_TX) + this.purgeOrphansSync(); + + return tx; }; -Mempool.prototype.getBalance = function getBalance(callback) { - var coins = []; - var hashes = Object.keys(this.coins); - var parts, hash, index, i, coin; - var unconfirmed = new bn(0); - var confirmed = new bn(0); +Mempool.prototype.storeOrphan = function storeOrphan(tx, callback) { + var ret; callback = utils.asyncify(callback); + try { + ret = this.storeOrphanSync(tx); + } catch (e) { + return callback(e); + } + + return callback(null, ret); +}; + +Mempool.prototype.getBalanceSync = function getBalanceSync() { + var coins = []; + var hashes = Object.keys(this.coins); + var balance = new bn(0); + var parts, hash, index, i, coin; + for (i = 0; i < hashes.length; i++) { parts = hashes[i].split('/'); hash = parts[0]; index = +parts[1]; - - try { - coin = this.getCoinSync(hash, index); - } catch (e) { - return callback(e); - } - + coin = this.getCoinSync(hash, index); coins.push(coin); } - for (i = 0; i < coins.length; i++) { - coin = coins[i]; - if (coin.height !== -1) - confirmed.iadd(coin.value); - unconfirmed.iadd(coin.value); - } + for (i = 0; i < coins.length; i++) + balance.iadd(coins[i].value); - return callback(null, { - unconfirmed: unconfirmed, - confirmed: confirmed - }); + return { + unconfirmed: balance, + confirmed: new bn(0) + }; }; -Mempool.prototype.getAll = function getAll(callback) { +Mempool.prototype.getBalance = function getBalance(callback) { + var balance; + + callback = utils.asyncify(callback); + + try { + balance = this.getBalanceSync(); + } catch (e) { + return callback(e); + } + + return callback(null, balance); +}; + +Mempool.prototype.getAllSync = function getAllSync() { var txs = []; var hashes = Object.keys(this.txs); var i, tx; + for (i = 0; i < hashes.length; i++) { + tx = this.getTXSync(hashes[i]); + if (tx) + txs.push(tx); + } + + return txs; +}; + +Mempool.prototype.getAll = function getAll(callback) { + var txs; + callback = utils.asyncify(callback); - for (i = 0; i < hashes.length; i++) { - try { - tx = this.getTXSync(hashes[i]); - } catch (e) { - return callback(e); - } - - txs.push(tx); + try { + txs = this.getAllSync(); + } catch (e) { + return callback(e); } return callback(null, txs); @@ -1029,7 +1161,10 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback) { return callback(null, resolved); utils.forEachSerial(hashes, function(orphanHash, next, i) { - var orphan = self.orphans[orphanHash]; + var orphan; + + orphanHash = orphanHash.toString('hex'); + orphan = self.orphans[orphanHash]; if (!orphan) return next(); @@ -1072,8 +1207,12 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback) { }); }; +Mempool.prototype.getSnapshotSync = function getSnapshotSync() { + return Object.keys(this.txs); +}; + Mempool.prototype.getSnapshot = function getSnapshot(callback) { - return utils.asyncify(callback)(null, Object.keys(this.txs)); + return utils.asyncify(callback)(null, this.getSnapshotSync()); }; Mempool.prototype.checkLocks = function checkLocks(tx, flags, callback) {