/** * mempool.js - mempool for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * https://github.com/indutny/bcoin */ var EventEmitter = require('events').EventEmitter; var bcoin = require('../bcoin'); var bn = require('bn.js'); var constants = bcoin.protocol.constants; var network = bcoin.protocol.network; var utils = require('./utils'); var assert = utils.assert; var BufferWriter = require('./writer'); var BufferReader = require('./reader'); var VerifyError = utils.VerifyError; /** * Mempool */ function Mempool(node, options) { if (!(this instanceof Mempool)) return new Mempool(node, options); EventEmitter.call(this); if (!options) options = {}; this.options = options; this.node = node; this.chain = node.chain; this.loaded = false; this.locker = new bcoin.locker(this, this.add, 20 << 20); this.totalSize = 0; this.totalOrphans = 0; this.coins = {}; this.txs = {}; this.psIndex = new BinaryIndex(); this.addressMap = new AddressMap(); this.orphans = {}; this.waiting = {}; this.spent = {}; this.freeCount = 0; this.lastTime = 0; this.limitFree = this.options.limitFree !== false; this.limitFreeRelay = this.options.limitFreeRelay || 15; this.relayPriority = this.options.relayPriority !== false; this.requireStandard = this.options.requireStandard !== false; this.rejectInsaneFees = this.options.rejectInsaneFees !== false; this.relay = this.options.relay || false; Mempool.global = this; this._init(); } utils.inherits(Mempool, EventEmitter); Mempool.flags = constants.flags.STANDARD_VERIFY_FLAGS; Mempool.mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; Mempool.lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; Mempool.ANCESTOR_LIMIT = 25; Mempool.MAX_MEMPOOL_SIZE = 300 << 20; Mempool.MEMPOOL_EXPIRY = 72 * 60 * 60; Mempool.MAX_ORPHAN_TX = 100; Mempool.prototype._lock = function _lock(func, args, force) { return this.locker.lock(func, args, force); }; Mempool.prototype.purgePending = function purgePending() { return this.locker.purgePending(); }; Mempool.prototype._init = function _init() { var self = this; var unlock = this._lock(utils.nop, []); assert(unlock); this.chain.open(function(err) { unlock(); if (err) self.emit('error', err); self.loaded = true; self.emit('open'); }); }; Mempool.prototype.open = function open(callback) { if (this.loaded) return utils.nextTick(callback); return this.once('open', callback); }; Mempool.prototype.close = Mempool.prototype.destroy = function destroy(callback) { return utils.nextTick(utils.ensure(callback)); }; Mempool.prototype.addBlock = function addBlock(block, callback, force) { var self = this; var unlock = this._lock(addBlock, [block, callback], force); if (!unlock) return; callback = utils.wrap(callback, unlock); utils.forEachSerial(block.txs, function(tx, next) { self.removeUnchecked(tx.hash('hex'), next); }, callback); }; Mempool.prototype.removeBlock = function removeBlock(block, callback, force) { var self = this; var unlock = this._lock(removeBlock, [block, callback], force); if (!unlock) return; callback = utils.wrap(callback, unlock); utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) { self.addUnchecked(tx, next); }, callback); }; Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) { var self = this; var txs; if (this.totalSize <= Mempool.MAX_MEMPOOL_SIZE) return utils.asyncify(callback)(null, true); try { txs = this.getRangeSync({ start: 0, end: utils.now() - Mempool.MEMPOOL_EXPIRY }); } catch (e) { return utils.asyncify(callback)(e); } utils.forEachSerial(function(tx, next) { self.removeUnchecked(tx, next); }, function(err) { if (err) return callback(err); try { self.purgeOrphansSync(); } catch (e) { return callback(e); } return callback(null, self.totalSize <= Mempool.MAX_MEMPOOL_SIZE); }); }; Mempool.prototype.getRangeSync = function getRangeSync(options) { var hashes = this.psIndex.range(options.start, options.end); var 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.purgeOrphansSync = function purgeOrphansSync() { var keys = Object.keys(this.orphans); var key, i; this.waiting = {}; this.totalOrphans = 0; for (i = 0; i < keys.length; i++) { key = keys[i]; this.totalSize -= this.orphans[key].length; delete this.orphans[key]; } }; Mempool.prototype.getSync = Mempool.prototype.getTXSync = function getTXSync(hash) { var tx; if (hash instanceof bcoin.tx) hash = hash.hash('hex'); tx = this.txs[hash]; if (!tx) return; return bcoin.tx.fromExtended(tx); }; Mempool.prototype.getCoinSync = function getCoinSync(hash, index) { var key = hash + '/' + index; var coin; coin = this.coins[key]; if (!coin) return; coin = bcoin.coin.fromRaw(coin); coin.hash = hash; coin.index = index; return coin; }; Mempool.prototype.isSpentSync = function isSpentSync(hash, index) { var key = hash + '/' + index; return this.spent[key] != null; }; Mempool.prototype.isDoubleSpendSync = function isDoubleSpendSync(tx) { var i, input; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; if (this.isSpentSync(input.prevout.hash, input.prevout.index)) return true; } return false; }; Mempool.prototype.getCoinsByAddressSync = function getCoinsByAddressSync(addresses) { var coins = []; var i, j, address, keys, key, coin; if (!Array.isArray(addresses)) addresses = [addresses]; addresses = utils.uniqs(addresses); for (i = 0; i < addresses.length; i++) { address = addresses[i]; keys = this.addressMap.getCoins(address); for (j = 0; j < keys.length; j++) { key = keys[j]; coin = this.getCoinSync(key[0], key[1]); if (coin) coins.push(coin); } } return coins; }; Mempool.prototype.getByAddressSync = Mempool.prototype.getTXByAddressSync = function getTXByAddressSync(addresses, callback) { var uniq = {}; var txs = []; var i, j, address, hashes, hash, tx; if (!Array.isArray(addresses)) addresses = [addresses]; addresses = utils.uniqs(addresses); for (i = 0; i < addresses.length; i++) { address = addresses[i]; hashes = this.addressMap.getTX(address); for (j = 0; j < hashes.length; j++) { hash = hashes[j]; if (uniq[hash]) continue; tx = this.getTXSync(hash); if (!tx) continue; uniq[hash] = true; txs.push(tx); } } return txs; }; Mempool.prototype.fillTXSync = function fillTXSync(tx) { var i, input, prev; if (tx.isCoinbase()) return tx; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; if (input.coin) continue; prev = this.getTXSync(input.prevout.hash); if (!prev) continue; input.coin = bcoin.coin(prev, input.prevout.index); } return tx; }; Mempool.prototype.fillCoinsSync = function fillCoinsSync(tx) { var i, input; if (tx.isCoinbase()) return tx; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; if (input.coin) continue; try { input.coin = this.getCoinSync(input.prevout.hash, input.prevout.index); } catch (e) { return callback(e); } } return tx; }; Mempool.prototype.hasSync = Mempool.prototype.hasTXSync = function hasTXSync(hash) { if (hash instanceof bcoin.tx) hash = hash.hash('hex'); return this.txs[hash] != null; }; Mempool.prototype.add = Mempool.prototype.addTX = function addTX(tx, callback, force) { var self = this; var flags = Mempool.flags; var lockFlags = Mempool.lockFlags; var ret = {}; var now; var unlock = this._lock(addTX, [tx, callback], force); if (!unlock) return; if (this.chain.segwitActive) { flags |= constants.flags.VERIFY_WITNESS; flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM; } callback = utils.wrap(callback, unlock); callback = utils.asyncify(callback); if (tx.ts !== 0) { return callback(new VerifyError(tx, 'alreadyknown', 'txn-already-in-mempool', 0)); } if (!this.chain.segwitActive) { if (tx.hasWitness()) return callback(new VerifyError(tx, 'nonstandard', 'no-witness-yet', 0)); } if (!tx.isSane(ret)) return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score)); if (tx.isCoinbase()) return callback(new VerifyError(tx, 'invalid', 'coinbase', 100)); this.chain.checkFinal(this.chain.tip, tx, lockFlags, function(err, isFinal) { if (err) return callback(err); if (!isFinal) return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0)); if (self.requireStandard) { if (!tx.isStandard(flags, ret)) return callback(new VerifyError(tx, ret.reason, 0)); } self.seenTX(tx, function(err, exists) { if (err) return callback(err); if (exists) { return callback(new VerifyError(tx, 'alreadyknown', 'txn-already-in-mempool', 0)); } self.isDoubleSpend(tx, function(err, doubleSpend) { if (err) return callback(err); if (doubleSpend) { return callback(new VerifyError(tx, 'duplicate', 'bad-txns-inputs-spent', 0)); } self.fillAllCoins(tx, function(err) { if (err) return callback(err); if (!tx.hasCoins()) { if (self.totalSize > Mempool.MAX_MEMPOOL_SIZE) { return callback(new VerifyError(tx, 'insufficientfee', 'mempool full', 0)); } utils.debug('Added orphan %s to mempool.', tx.rhash); return self.storeOrphan(tx, callback); } self.verify(tx, function(err) { if (err) return callback(err); self.limitMempoolSize(function(err, result) { if (err) return callback(err); if (!result) { return callback(new VerifyError(tx, 'insufficientfee', 'mempool full', 0)); } self.addUnchecked(tx, callback); }); }); }); }); }); }); }; // Use bitcoinj-style confidence calculation Mempool.prototype.getConfidence = function getConfidence(hash, callback) { var self = this; var tx; callback = utils.asyncify(callback); if (hash instanceof bcoin.tx) { tx = hash; hash = tx.hash('hex'); } else { try { tx = this.getTXSync(hash); } catch (e) { return callback(e); } } if (tx && this.isDoubleSpendSync(tx)) return callback(null, constants.confidence.INCONFLICT); if (this.hasTXSync(hash)) return callback(null, constants.confidence.PENDING); function getBlock(callback) { if (tx && tx.block) return callback(null, tx.block); return self.chain.db.getTX(hash, function(err, existing) { if (err) return callback(err); if (!existing) return callback(); return callback(null, existing.block); }); } return getBlock(function(err, block) { if (err) return callback(err); if (!block) return callback(null, constants.confidence.UNKNOWN); self.chain.db.isMainChain(block, function(err, result) { if (err) return callback(err); if (result) return callback(null, constants.confidence.BUILDING); return callback(null, constants.confidence.DEAD); }); }); }; Mempool.prototype.fillAllTX = function fillAllTX(tx, callback) { var self = this; this.fillTX(tx, function(err) { if (err) return callback(err); if (tx.hasCoins()) return callback(null, tx); self.chain.db.fillTX(tx, callback); }); }; Mempool.prototype.fillAllCoins = function fillAllCoins(tx, callback) { var self = this; var doubleSpend = false; this.fillCoins(tx, function(err) { if (err) return callback(err); if (tx.hasCoins()) return callback(null, tx); utils.forEach(tx.inputs, function(input, next) { var hash = input.prevout.hash; var index = input.prevout.index; if (self.isSpentSync(hash, index)) { doubleSpend = true; return next(); } self.chain.db.getCoin(hash, index, function(err, coin) { if (err) return next(err); if (!coin) return next(); input.coin = coin; next(); }); }, function(err) { if (err) return callback(err); return callback(null, tx, doubleSpend); }); }); }; Mempool.prototype.addUnchecked = function addUnchecked(tx, callback) { var self = this; var hash = tx.hash(); var hex = hash.toString('hex'); var input, output, i, key, coin; this.txs[hex] = tx.toExtended(); this.addressMap.addTX(tx); this.psIndex.insert(tx); 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); } for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; key = hex + '/' + i; coin = bcoin.coin(tx, i); this.coins[key] = coin.toRaw(); this.addressMap.addCoin(coin); } this.totalSize += tx.getSize(); this.emit('tx', tx); this.emit('add tx', tx); utils.debug('Added tx %s to the mempool.', tx.rhash); if (this.options.relay) this.node.broadcast(tx); this.resolveOrphans(tx, function(err, resolved) { if (err) return callback(err); utils.forEachSerial(resolved, function(tx, next) { self.addUnchecked(tx, function(err) { if (err) self.emit('error', err); utils.debug('Resolved orphan %s in mempool.', tx.rhash); next(); }, true); }, callback); }); }; Mempool.prototype.removeUnchecked = function removeUnchecked(hash, callback) { var self = this; var tx, input, output, i, key, coin; if (hash instanceof bcoin.tx) { tx = hash; hash = tx.hash('hex'); } else { try { tx = this.getTXSync(hash); } catch (e) { return utils.asyncify(callback)(e); } } if (!tx) return utils.nextTick(callback); this.fillAllTX(tx, function(err, tx) { if (err) return callback(err); delete self.txs[hash]; try { self.removeOrphanSync(hash); } catch (e) { return callback(e); } self.addressMap.removeTX(tx); self.psIndex.remove(tx); for (i = 0; i < tx.inputs.length; i++) { inputs = tx.outputs[i]; key = input.prevout.hash + '/' + input.prevout.index; delete self.spent[key]; delete self.coins[key]; self.addressMap.removeCoin(input); if (self.hasTXSync(input.prevout.hash)) { self.coins[key] = input.coin.toRaw(); self.addressMap.addCoin(input.coin); } } for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; key = hash + '/' + i; delete self.coins[key]; delete self.spent[key]; self.addressMap.removeCoin(tx, i); } self.totalSize -= tx.getSize(); self.emit('remove tx', tx); return callback(); }); }; Mempool.prototype.removeOrphanSync = function removeOrphanSync(tx) { var prevout, i, hex, hash, prev, map, index; if (typeof tx === 'string') tx = this.getOrphanSync(tx); hash = tx.hash(); hex = hash.toString('hex'); prevout = tx.getPrevout(); if (!this.orphans[hex]) return false; delete this.orphans[hex]; for (i = 0; i < prevout.length; i++) { prev = prevout[i]; map = this.waiting[prev]; if (!map) continue; index = binarySearch(map, hash); if (index !== -1) { map.splice(index, 1); if (map.length === 0) delete this.waiting[prev]; } } return true; }; Mempool.prototype.verify = function verify(tx, callback) { var self = this; var height = this.chain.height + 1; var lockFlags = Mempool.lockFlags; var flags = Mempool.flags; var mandatory = Mempool.mandatory; var ret = {}; var fee, now, free, minFee; if (this.chain.segwitActive) { flags |= constants.flags.VERIFY_WITNESS; flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM; mandatory |= constants.flags.VERIFY_WITNESS; } this.checkLocks(tx, lockFlags, function(err, result) { if (err) return callback(err); if (!result) { return callback(new VerifyError(tx, 'nonstandard', 'non-BIP68-final', 0)); } if (self.requireStandard && !tx.hasStandardInputs(flags)) { return callback(new VerifyError(tx, 'nonstandard', 'bad-txns-nonstandard-inputs', 0)); } if (tx.getSigops(true) > constants.tx.maxSigops) { return callback(new VerifyError(tx, 'nonstandard', 'bad-txns-too-many-sigops', 0)); } if (!tx.checkInputs(height, ret)) return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score)); fee = tx.getFee(); minFee = tx.getMinFee(); if (fee.cmp(minFee) < 0) { if (self.relayPriority) { free = tx.isFree(height); if (!free) { return callback(new VerifyError(tx, 'insufficientfee', 'insufficient priority', 0)); } } else { return callback(new VerifyError(tx, 'insufficientfee', 'insufficient fee', 0)); } } if (self.limitFree && free) { now = utils.now(); if (!self.lastTime) self.lastTime = now; self.freeCount *= Math.pow(1 - 1 / 600, now - self.lastTime); self.lastTime = now; if (self.freeCount > self.limitFreeRelay * 10 * 1000) { return callback(new VerifyError(tx, 'insufficientfee', 'rate limited free transaction', 0)); } self.freeCount += tx.getSize(); } if (self.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0) return callback(new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0)); self.countAncestors(tx, function(err, count) { if (err) return callback(err); if (count > Mempool.ANCESTOR_LIMIT) { return callback(new VerifyError(tx, 'nonstandard', 'too-long-mempool-chain', 0)); } // Do this in the worker pool. tx.verifyAsync(null, true, flags, function(err, result) { if (err) return callback(err); if (!result) { return tx.verifyAsync(null, true, mandatory, function(err, result) { if (err) return callback(err); if (result) { return callback(new VerifyError(tx, 'nonstandard', 'non-mandatory-script-verify-flag', 0)); } return callback(new VerifyError(tx, 'nonstandard', 'mandatory-script-verify-flag', 0)); }); } return callback(); }); }); }); }; Mempool.prototype.countAncestorsSync = function countAncestorsSync(tx) { var self = this; var inputs = new Array(tx.inputs.length); var i, input, prev; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; prev = this.getTXSync(input.prevout.hash); inputs[i] = 0; if (!prev) continue; inputs[i] += 1; inputs[i] += this.countAncestorsSync(prev); } return inputs.sort().pop(); }; Mempool.prototype.hasOrphanSync = function hasOrphanSync(hash) { if (hash instanceof bcoin.tx) hash = hash.hash('hex'); return this.orphans[hash] != null; }; Mempool.prototype.getOrphanSync = function getOrphanSync(hash) { var orphan; if (hash instanceof bcoin.tx) hash = hash.hash('hex'); orphan = this.orphans[hash]; if (!orphan) return; return bcoin.tx.fromExtended(orphan, true); }; Mempool.prototype.seenTX = function seenTX(tx, callback) { var hash = tx.hash('hex'); if (this.hasOrphanSync(hash)) return utils.asyncify(callback)(null, true); if (this.hasTXSync(hash)) return utils.asyncify(callback)(null, true); return this.chain.db.hasTX(hash, callback); }; Mempool.prototype.storeOrphanSync = function storeOrphanSync(tx) { var prevout = {}; var hash = tx.hash(); var i, input, key, map, index; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; if (!input.coin) prevout[input.prevout.hash] = true; } prevout = Object.keys(prevout); assert(prevout.length > 0); for (i = 0; i < prevout.length; i++) { key = prevout[i]; if (!this.waiting[key]) this.waiting[key] = []; map = this.waiting[key]; index = binarySearch(map, hash, true); map.splice(index + 1, 0, hash); } this.totalOrphans++; this.orphans[hash.toString('hex')] = tx.toExtended(true); if (this.totalOrphans > Mempool.MAX_ORPHAN_TX) this.purgeOrphansSync(); return tx; }; 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]; coin = this.getCoinSync(hash, index); coins.push(coin); } for (i = 0; i < coins.length; i++) balance.iadd(coins[i].value); return { unconfirmed: balance, confirmed: new bn(0) }; }; 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.resolveOrphans = function resolveOrphans(tx, callback) { var self = this; var hash = tx.hash('hex'); var hashes = this.waiting[hash]; var resolved = []; if (!hashes) return callback(null, resolved); utils.forEachSerial(hashes, function(orphanHash, next, i) { var orphan; orphanHash = orphanHash.toString('hex'); orphan = self.orphans[orphanHash]; if (!orphan) return next(); try { orphan = bcoin.tx.fromExtended(orphan, true); } catch (e) { return next(e); } orphan.fillCoins(tx); if (orphan.hasCoins()) { self.totalOrphans--; delete self.orphans[orphanHash]; return self.verify(orphan, function(err) { if (err) { if (err.type === 'VerifyError') return next(); return next(err); } resolved.push(orphan); return next(); }); } self.orphans[orphanHash] = orphan.toExtended(true); next(); }, function(err) { if (err) return callback(err); delete self.waiting[hash]; return callback(null, resolved); }); }; Mempool.prototype.getSnapshotSync = function getSnapshotSync() { return Object.keys(this.txs); }; Mempool.prototype.checkLocks = function checkLocks(tx, flags, callback) { var self = this; var tip = this.chain.tip; var index = new bcoin.chainblock(this.chain, { hash: utils.toHex(constants.zeroHash), version: tip.version, prevBlock: tip.hash, merkleRoot: utils.toHex(constants.zeroHash), ts: utils.now(), bits: 0, nonce: 0, height: tip.height + 1, chainwork: tip.chainwork }); return this.chain.checkLocks(tx, flags, index, callback); }; /** * Async Wrappers */ Mempool.prototype.getRange = function getRange(options, callback) { var ret; callback = utils.asyncify(callback); try { ret = this.getRangeSync(options); } catch (e) { return callback(e); } return callback(null, ret); }; 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.get = Mempool.prototype.getTX = function getTX(hash, callback) { var tx; callback = utils.asyncify(callback); try { 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 { coin = this.getCoinSync(hash, index); } catch (e) { return callback(e); } return callback(null, coin); }; Mempool.prototype.isSpent = function isSpent(hash, index, callback) { callback = utils.asyncify(callback); return callback(null, this.isSpentSync(hash, index)); }; Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) { callback = utils.asyncify(callback); return callback(null, this.isDoubleSpendSync(tx)); }; 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) { var txs; callback = utils.asyncify(callback); try { txs = this.getTXByAddressSync(addresses); } catch (e) { return callback(e); } return callback(null, txs); }; 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) { callback = utils.asyncify(callback); try { tx = this.fillCoinsSync(tx); } catch (e) { return callback(e); } return callback(null, tx); }; Mempool.prototype.has = Mempool.prototype.hasTX = function hasTX(hash, callback) { callback = utils.asyncify(callback); return callback(null, this.hasTXSync(hash)); }; Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) { var ret; callback = utils.asyncify(callback); try { ret = this.removeOrphanSync(tx); } catch (e) { return callback(e); } return callback(null, ret); }; Mempool.prototype.countAncestors = function countAncestors(tx, callback) { var count; callback = utils.asyncify(callback); try { count = this.countAncestorsSync(tx); } catch (e) { return callback(e); } return callback(null, count); }; Mempool.prototype.hasOrphan = function hasOrphan(hash, callback) { callback = utils.asyncify(callback); return callback(null, this.hasOrphanSync(hash)); }; Mempool.prototype.getOrphan = function getOrphan(hash, callback) { var orphan; callback = utils.asyncify(callback); try { orphan = this.getOrphanSync(hash); } catch (e) { return callback(e); } return callback(null, orphan); }; 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.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.getAll = function getAll(callback) { var txs; callback = utils.asyncify(callback); try { txs = this.getAllSync(); } catch (e) { return callback(e); } return callback(null, txs); }; Mempool.prototype.getSnapshot = function getSnapshot(callback) { return utils.asyncify(callback)(null, this.getSnapshotSync()); }; /** * AddressMap */ function AddressMap() { this.map = { tx: {}, coin: {} }; } AddressMap.prototype.getTX = function getTX(address) { var map = this.map.tx[address]; var keys = []; var i, key; if (!map) return keys; for (i = 0; i < map.length; i++) { key = map[i]; assert(key.length === 32); keys.push(key.toString('hex')); } return keys; }; AddressMap.prototype.getCoins = function getCoins(address) { var map = this.map.coin[address]; var keys = []; var i, p, key; if (!map) return keys; for (i = 0; i < map.length; i++) { key = map[i]; assert(key.length === 36); 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(); var addresses = tx.getAddresses(); var address, i, map, index; for (i = 0; i < addresses.length; i++) { address = addresses[i]; if (!this.map.tx[address]) this.map.tx[address] = []; map = this.map.tx[address]; index = binarySearch(map, hash, true); map.splice(index + 1, 0, hash); } }; AddressMap.prototype.removeTX = function removeTX(tx) { var hash = tx.hash(); var addresses = tx.getAddresses(); var address, map, index; for (i = 0; i < addresses.length; i++) { address = addresses[i]; map = this.map.tx[address]; if (map) { index = binarySearch(map, hash); if (index !== -1) { map.splice(index, 1); if (map.length === 0) delete this.map.tx[address]; } } } }; AddressMap.prototype.addCoin = function addCoin(coin) { var address = coin.getAddress(); var key = this._coinKey(coin.hash, coin.index); var map, index; if (address) { if (!this.map.coin[address]) this.map.coin[address] = []; map = this.map.coin[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, index; if (tx instanceof bcoin.input) { address = tx.getAddress(); key = this._coinKey(tx.prevout.hash, tx.prevout.index); } else { address = tx.outputs[i].getAddress(); key = this._coinKey(tx.hash(), i); } map = this.map.coin[address]; if (map) { index = binarySearch(map, key); if (index !== -1) { map.splice(index, 1); if (map.length === 0) delete this.map.coin[address]; } } }; /** * BinaryIndex */ function BinaryIndex() { this.index = []; this.data = []; } BinaryIndex.prototype.insert = function insert(tx) { var ps = new Buffer(4); var index; utils.writeU32BE(ps, tx.ps, 0); index = binarySearch(this.index, ps, true); this.index.splice(index + 1, 0, ps); this.data.splice(index + 1, 0, tx.hash()); }; BinaryIndex.prototype.remove = function remove(tx) { var index = binarySearch(this.data, tx.hash()); if (index !== -1) { this.index.splice(index, 1); this.data.splice(index, 1); } }; BinaryIndex.prototype.range = function range(start, end) { var hashes = []; var s = new Buffer(4); var i, ps; utils.writeU32BE(s, start, 0); i = binarySearch(this.index, s, true); for (; i < this.index.length; i++) { ps = utils.readU32BE(this.index[i], 0); if (ps < start || ps > end) return hashes; hashes.push(this.data[i].toString('hex')); } return hashes; }; /** * Helpers */ function binarySearch(items, key, insert) { var start = 0; var end = items.length - 1; var pos, cmp; while (start <= end) { pos = (start + end) >>> 1; cmp = utils.cmp(items[pos], key); if (cmp === 0) return pos; if (cmp < 0) start = pos + 1; else end = pos - 1; } if (!insert) return -1; return start - 1; } /** * Expose */ module.exports = Mempool;