rewrite mempool.

This commit is contained in:
Christopher Jeffrey 2016-03-21 16:29:02 -07:00
parent d7bb115609
commit 2797cd2f44
12 changed files with 791 additions and 560 deletions

View File

@ -24,6 +24,7 @@ bcoin.debugLogs = +process.env.BCOIN_DEBUG === 1;
bcoin.debugFile = +process.env.BCOIN_DEBUGFILE !== 0;
bcoin.profile = +process.env.BCOIN_PROFILE === 1;
bcoin.fresh = +process.env.BCOIN_FRESH === 1;
bcoin.useWorkers = +process.env.BCOIN_WORKERS === 1;
bcoin.ensurePrefix = function ensurePrefix() {
if (bcoin.isBrowser)
@ -122,5 +123,8 @@ bcoin.miner = require('./bcoin/miner');
bcoin.http = !bcoin.isBrowser
? require('./bcoin/ht' + 'tp')
: null;
bcoin.workers = bcoin.useWorkers && !bcoin.isBrowser
? require('./bcoin/work' + 'ers')
: null;
bcoin.protocol.network.set(process.env.BCOIN_NETWORK || 'main');

View File

@ -674,6 +674,9 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
if (!scriptCheck)
continue;
// Disable. Use workers and verifyAsync for now.
continue;
// Verify the scripts
if (!tx.verify(j, true, flags)) {
utils.debug('Block has invalid inputs: %s (%s/%d)',
@ -694,7 +697,16 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
}
}
return callback(null, true);
// Disable. Use workers and verifyAsync for now.
// return callback(null, true);
if (!scriptCheck)
return callback(null, true);
// Verify all txs in parallel.
utils.every(block.txs, function(tx, next) {
tx.verifyAsync(null, true, flags, next);
}, callback);
});
};

View File

@ -75,6 +75,10 @@ Coin.prototype.getSize = function getSize() {
return 4 + 4 + 8 + this.script.getSize() + 32 + 4 + 1;
};
Coin.prototype.isCoinbase = function isCoinbase() {
return false;
};
Coin.prototype.getConfirmations = function getConfirmations(height) {
var top;

View File

@ -207,108 +207,150 @@ Fullnode.prototype.getFullBlock = function getFullBlock(hash, callback) {
};
Fullnode.prototype.getCoin = function getCoin(hash, index, callback) {
var coin;
callback = utils.asyncify(callback);
coin = this.mempool.getCoin(hash, index);
if (coin)
return callback(null, coin);
if (this.mempool.isSpent(hash, index))
return callback(null, null);
this.chain.db.getCoin(hash, index, function(err, coin) {
var self = this;
this.mempool.getCoin(hash, index, function(err, coin) {
if (err)
return callback(err);
if (!coin)
return callback();
if (coin)
return callback(null, coin);
return callback(null, coin);
self.chain.db.getCoin(hash, index, function(err, coin) {
if (err)
return callback(err);
if (!coin)
return callback();
self.mempool.isSpent(hash, index, function(err, spent) {
if (err)
return callback(err);
if (spent)
return callback();
return callback(null, coin);
});
});
});
};
Fullnode.prototype.getCoinByAddress = function getCoinByAddress(addresses, callback) {
var self = this;
var mempool;
callback = utils.asyncify(callback);
mempool = this.mempool.getCoinsByAddress(addresses);
this.chain.db.getCoinsByAddress(addresses, function(err, coins) {
this.mempool.getCoinsByAddress(addresses, function(err, coins) {
if (err)
return callback(err);
return callback(null, mempool.concat(coins.filter(function(coin) {
if (self.mempool.isSpent(coin.hash, coin.index))
return false;
return true;
})));
self.chain.db.getCoinsByAddress(addresses, function(err, blockCoins) {
if (err)
return callback(err);
utils.forEach(blockCoins, function(coin, next) {
self.mempool.isSpent(coin.hash, coin.index, function(err, spent) {
if (err)
return callback(err);
if (!spent)
coins.push(coin);
return next();
});
}, function(err) {
if (err)
return callback(err);
return callback(null, coins);
});
});
});
};
Fullnode.prototype.getTX = function getTX(hash, callback) {
var tx;
var self = this;
callback = utils.asyncify(callback);
tx = this.mempool.getTX(hash);
if (tx)
return callback(null, tx);
this.chain.db.getTX(hash, function(err, tx) {
this.mempool.getTX(hash, function(err, tx) {
if (err)
return callback(err);
if (!tx)
return callback();
if (tx)
return callback(null, tx);
return callback(null, tx);
self.chain.db.getTX(hash, function(err, tx) {
if (err)
return callback(err);
if (!tx)
return callback();
return callback(null, tx);
});
});
};
Fullnode.prototype.hasTX = function hasTX(hash, callback) {
var self = this;
return this.getTX(hash, function(err, tx) {
if (err)
return callback(err);
return callback(null, !!tx);
});
};
Fullnode.prototype.isSpent = function isSpent(hash, index, callback) {
callback = utils.asyncify(callback);
var self = this;
if (this.mempool.isSpent(hash, index))
return callback(null, true);
this.chain.db.isSpent(hash, index, callback);
};
Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses, callback) {
var mempool;
callback = utils.asyncify(callback);
mempool = this.mempool.getTXByAddress(addresses);
this.chain.db.getTXByAddress(addresses, function(err, txs) {
this.mempool.isSpent(hash, index, function(err, spent) {
if (err)
return callback(err);
return callback(null, mempool.concat(txs));
if (spent)
return callback(null, true);
self.chain.db.isSpent(hash, index, callback);
});
};
Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses, callback) {
var self = this;
this.mempool.getTXByAddress(addresses, function(err, mempool) {
if (err)
return callback(err);
self.chain.db.getTXByAddress(addresses, function(err, txs) {
if (err)
return callback(err);
return callback(null, mempool.concat(txs));
});
});
};
Fullnode.prototype.fillCoin = function fillCoin(tx, callback) {
callback = utils.asyncify(callback);
var self = this;
if (this.mempool.fillCoin(tx))
return callback();
this.mempool.fillCoin(tx, function(err, filled) {
if (err)
return callback(err);
this.chain.db.fillCoin(tx, callback);
if (filled)
return callback(null, tx);
self.chain.db.fillCoin(tx, callback);
});
};
Fullnode.prototype.fillTX = function fillTX(tx, callback) {
callback = utils.asyncify(callback);
var self = this;
if (this.mempool.fillTX(tx))
return callback();
this.mempool.fillTX(tx, function(err, filled) {
if (err)
return callback(err);
this.chain.db.fillTX(tx, callback);
if (filled)
return callback(null, tx);
self.chain.db.fillTX(tx, callback);
});
};
/**

View File

@ -15,7 +15,9 @@ module.exports = function ldb(name, options) {
var backend = process.env.BCOIN_DB;
if (!db[file]) {
if (bcoin.isBrowser) {
if (options.db) {
backend = options.db;
} else if (bcoin.isBrowser) {
backend = require('level-js');
} else {
if (!backend || backend === 'leveldb')

View File

@ -28,6 +28,13 @@ function Mempool(node, options) {
this.options = options;
this.node = node;
this.chain = node.chain;
this.db = node.chain.db;
this.tx = new bcoin.txdb('m', this.db, {
indexSpent: true,
indexExtra: false,
indexAddress: false,
mapAddress: false
});
this.txs = {};
this.spent = {};
@ -36,6 +43,18 @@ function Mempool(node, options) {
this.count = 0;
this.locked = false;
this.loaded = false;
this.jobs = [];
this.busy = false;
this.pending = [];
this.pendingTX = {};
this.pendingSize = 0;
this.pendingLimit = 20 << 20;
this.freeCount = 0;
this.lastTime = 0;
this.limitFreeRelay = this.options.limitFreeRelay || 15;
this.requireStandard = this.options.requireStandard !== false;
this.limitFree = this.options.limitFree !== false;
this.rejectInsaneFees = this.options.rejectInsaneFees !== false;
Mempool.global = this;
@ -44,443 +63,349 @@ function Mempool(node, options) {
utils.inherits(Mempool, EventEmitter);
Mempool.prototype._lock = function _lock(func, args, force) {
var self = this;
var block, 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 === Chain.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]);
};
};
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;
});
};
Mempool.prototype._init = function _init() {
var self = this;
utils.nextTick(function() {
if (this.db.loaded) {
this.loaded = true;
return;
}
this.db.once('open', function() {
self.loaded = true;
self.emit('open');
});
};
Mempool.prototype.open = function open(callback) {
if (this.loaded)
return utils.nextTick(callback);
this.once('open', callback);
return this.db.open(callback);
};
Mempool.prototype.addBlock = function addBlock(block) {
var self = this;
callback = utils.ensure(callback);
// Remove now-mined transactions
block.txs.forEach(function(tx) {
var mtx = self.get(tx);
if (!mtx)
return;
mtx.ps = 0;
mtx.ts = block.ts;
mtx.block = block.hash('hex');
mtx.network = true;
self.removeTX(mtx);
});
// XXX should batch this
utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) {
self.tx.remove(tx, next);
}, callback);
};
Mempool.prototype.removeBlock = function removeBlock(block) {
Mempool.prototype.removeBlock = function removeBlock(block, callback) {
var self = this;
block.txs.forEach(function(tx) {
var hash = tx.hash('hex');
// Remove anything that tries to redeem these outputs
tx.outputs.forEach(function(output, i) {
var mtx = self.spent[hash + '/' + i];
if (!mtx)
return;
self.removeTX(mtx);
});
// Add transaction back into mempool
// tx = tx.clone();
tx.ps = utils.now();
tx.ts = 0;
tx.block = null;
tx.network = true;
self.addTX(tx);
});
callback = utils.ensure(callback);
// XXX should batch this
utils.forEachSerial(block.txs, function(tx, next) {
self.tx.add(tx, next);
}, callback);
};
Mempool.prototype.get =
Mempool.prototype.getTX = function getTX(hash) {
Mempool.prototype.getTX = function getTX(hash, callback) {
if (hash instanceof bcoin.tx)
hash = hash.hash('hex');
return this.txs[hash];
return this.tx.getTX(hash, index, callback);
};
Mempool.prototype.getCoin = function getCoin(hash, index) {
var tx = this.get(hash);
if (!tx)
return;
return bcoin.coin(tx, index);
Mempool.prototype.getCoin = function getCoin(hash, index, callback) {
return this.tx.getCoin(hash, index, callback);
};
Mempool.prototype.isSpent = function isSpent(hash, index) {
return !!this.spent[hash + '/' + index];
Mempool.prototype.isSpent = function isSpent(hash, index, callback) {
return this.tx.isSpent(hash, index, callback);
};
Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addresses) {
var txs = this.getByAddress(addresses);
return txs.reduce(function(out, tx) {
return out.concat(tx.outputs.map(function(output, i) {
return bcoin.coin(tx, i);
}));
}, []);
Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, callback) {
return this.tx.getCoinsByAddress(addresses, callback);
};
Mempool.prototype.getByAddress =
Mempool.prototype.getTXByAddress = function getTXByAddress(addresses) {
var self = this;
var txs = [];
var uniq = {};
if (typeof addresses === 'string')
addresses = [addresses];
addresses = utils.uniqs(addresses);
addresses.forEach(function(address) {
var map = self.addresses[address];
if (!map)
return;
Object.keys(map).forEach(function(hash) {
var tx;
if (uniq[hash])
return;
uniq[hash] = true;
tx = self.get(hash);
assert(tx);
txs.push(tx);
});
});
return txs;
return this.tx.getTXByAddress(addresses, callback);
};
Mempool.prototype.fillCoin =
Mempool.prototype.fillTX = function fillTX(tx) {
var i, input, total;
total = 0;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (input.output) {
total++;
continue;
}
if (this.hasTX(input.prevout.hash)) {
input.output = this.getCoin(input.prevout.hash, input.prevout.index);
total++;
}
}
return total === tx.inputs.length;
Mempool.prototype.fillTX = function fillTX(tx, callback) {
return this.tx.fillTX(tx, callback);
};
Mempool.prototype.getAll = function getAll() {
return Object.keys(this.txs).map(function(key) {
return this.txs[key];
}, this);
Mempool.prototype.fillCoin = function fillCoin(tx, callback) {
return this.tx.fillCoin(tx, callback);
};
Mempool.prototype.has =
Mempool.prototype.hasTX = function hasTX(hash) {
return !!this.get(hash);
Mempool.prototype.hasTX = function hasTX(hash, callback) {
return this.get(hash, function(err, tx) {
if (err)
return callback(err);
return callback(null, !!tx);
});
};
Mempool.prototype.add =
Mempool.prototype.addTX = function addTX(tx, peer, callback) {
Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
var self = this;
var flags = constants.flags.STANDARD_VERIFY_FLAGS;
var hash = tx.hash('hex');
var hash, ts, height, now;
var ret = {};
var unlock = this._lock(addTX, [tx, peer, callback], force);
if (!unlock)
return;
hash = tx.hash('hex');
assert(tx.ts === 0);
callback = utils.wrap(callback, unlock);
callback = utils.asyncify(callback);
if (this.locked)
return callback(new Error('Mempool is locked.'));
if (this.count >= 50000)
return callback(new Error('Mempool is full.'));
if (this.size >= 20 * 1024 * 1024)
return callback(new Error('Mempool is full.'));
if (this.txs[hash])
return callback(new Error('Already have TX.'));
if (tx.isCoinbase())
return callback(new Error('What?'));
if (!this.checkTX(tx, peer))
return callback(new Error('TX failed checkTX.'));
return callback(new Error('CheckTransaction failed'));
assert(tx.ts === 0);
if (tx.isCoinbase()) {
this.reject(peer, tx, 'coinbase', 100);
return callback(new Error('coinbase as individual tx'));
}
this._lockTX(tx);
ts = utils.now();
height = this.chain.height + 1;
this.chain.fillCoin(tx, function(err) {
var i, input, output, dup, height, ts, priority;
self._unlockTX(tx);
if (self.requireStandard && !tx.isStandard(flags, ts, height, ret)) {
self.reject(peer, tx, ret.reason, 0);
return callback(new Error('TX is not standard.'));
}
this.node.hasTX(tx, function(err, exists) {
if (err)
return callback(err);
// Do this in the future.
// tx = self.fillCoin(tx);
if (exists)
return callback();
if (!tx.hasPrevout()) {
return callback(new Error('Previous outputs not found.'));
peer.reject({
data: tx.hash(),
reason: 'no-prevout'
});
return callback(new Error('Previous outputs not found.'));
}
self.node.fillCoin(tx, function(err) {
var i, input, output, total, fee, coin;
if (!tx.isStandard(flags)) {
return callback(new Error('TX is not standard.'));
peer.reject({
data: tx.hash(),
reason: 'non-standard'
});
self.node.pool.setMisbehavior(peer, 100);
return callback(new Error('TX is not standard.'));
}
if (err)
return callback(err);
if (!tx.isStandardInputs(flags)) {
return callback(new Error('TX inputs are not standard.'));
peer.reject({
data: tx.hash(),
reason: 'non-standard-inputs'
});
self.node.pool.setMisbehavior(peer, 100);
return callback(new Error('TX inputs are not standard.'));
}
if (!tx.hasPrevout()) {
// Store as orphan:
// return self.tx.add(tx, callback);
return callback(new Error('No prevouts yet.'));
}
if (tx.getOutputValue().cmp(tx.getInputValue()) > 0) {
return callback(new Error('TX is spending coins that it does not have.'));
peer.reject({
data: tx.hash(),
reason: 'nonexistent-coins'
});
self.node.pool.setMisbehavior(peer, 100);
return callback(new Error('TX is spending coins that it does not have.'));
}
if (self.requireStandard && !tx.isStandardInputs(flags))
return callback(new Error('TX inputs are not standard.'));
height = self.node.pool.chain.height + 1;
ts = utils.now();
if (!tx.isFinal(height, ts)) {
return callback(new Error('TX is not final.'));
peer.reject({
data: tx.hash(),
reason: 'not-final'
});
self.node.pool.setMisbehavior(peer, 100);
return callback(new Error('TX is not final.'));
}
if (tx.getSigops(true) > constants.script.maxSigops) {
self.reject(peer, tx, 'bad-txns-too-many-sigops', 0);
return callback(new Error('TX has too many sigops.'));
}
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
dup = self.spent[input.prevout.hash + '/' + input.prevout.index];
if (dup) {
// Replace-by-fee
if (input.sequence === 0xffffffff - 1) {
if (dup.getFee().cmp(tx.getFee()) < 0) {
self.remove(dup);
continue;
total = new bn(0);
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
coin = input.coin;
if (coin.isCoinbase()) {
if (self.chain.height - coin.height < constants.tx.coinbaseMaturity) {
self.reject(peer, tx, 'bad-txns-premature-spend-of-coinbase', 0);
return callback(new Error('Tried to spend coinbase prematurely.'));
}
}
return callback(new Error('TX is double spending.'));
peer.reject({
data: tx.hash(),
reason: 'double-spend'
});
return callback(new Error('TX is double spending.'));
}
}
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
if (output.value.cmpn(0) < 0) {
return callback(new Error('TX is spending negative coins.'));
peer.reject({
data: tx.hash(),
reason: 'negative-value'
});
self.node.pool.setMisbehavior(peer, 100);
return callback(new Error('TX is spending negative coins.'));
}
}
if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0)
return self.reject(peer, tx, 'bad-txns-inputvalues-outofrange', 100);
if (!tx.verify(null, true, flags)) {
return callback(new Error('TX did not verify.'));
peer.reject({
data: tx.hash(),
reason: 'script-failed'
total.iadd(coin.value);
}
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0)
return self.reject(peer, tx, 'bad-txns-inputvalues-outofrange', 100);
if (tx.getOutputValue().cmp(total) > 0) {
self.reject(peer, 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);
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);
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);
return callback(new Error('Insufficient fee.'));
}
if (self.limitFree && fee.cmpn(tx.getMinFee()) < 0) {
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) {
self.reject(peer, tx, 'insufficient priority', 0);
return callback(new Error('Too many free txs at once!'));
}
self.freeCount += tx.getVirtualSize();
}
if (self.rejectInsaneFees && fee.cmpn(tx.getMinFee().muln(10000)) > 0)
return callback(new Error('TX has an insane fee.'));
// Do this in the worker pool.
tx.verifyAsync(null, true, flags, function(err, result) {
if (err)
return callback(err);
if (!result) {
// Just say it's non-mandatory for now.
self.reject(peer, 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);
}
return callback(err);
}
self.emit('tx', tx);
return callback();
});
});
self.node.pool.setMisbehavior(peer, 100);
return callback(new Error('TX did not verify.'));
}
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
self.spent[input.prevout.hash + '/' + input.prevout.index] = tx;
self.size += input.output.getSize();
}
// Possibly do something bitcoinxt-like here with priority
priority = tx.getPriority();
tx.inputs.forEach(function(input) {
var type = input.getType();
var address = input.getAddress();
if (type === 'pubkey' || type === 'multisig')
address = null;
if (!address)
return;
if (!self.addresses[address])
self.addresses[address] = {};
self.addresses[address][hash] = true;
});
tx.outputs.forEach(function(output) {
var type = output.getType();
var address = output.getAddress();
if (type === 'pubkey' || type === 'multisig')
address = null;
if (!address)
return;
if (!self.addresses[address])
self.addresses[address] = {};
self.addresses[address][hash] = true;
});
self.txs[hash] = tx;
self.count++;
self.size += tx.getSize();
self.emit('tx', tx);
});
};
// Lock a tx to prevent race conditions
Mempool.prototype._lockTX = function _lockTX(tx) {
var hash = tx.hash('hex');
var i, input, id;
if (!this.txs[hash])
this.txs[hash] = tx;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
id = input.prevout.hash + '/' + input.prevout.index;
if (!this.spent[id])
this.spent[id] = tx;
}
};
Mempool.prototype._unlockTX = function _unlockTX(tx) {
var hash = tx.hash('hex');
var i, input, id;
if (this.txs[hash] === tx)
delete this.txs[hash];
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
id = input.prevout.hash + '/' + input.prevout.index;
if (this.spent[id] === tx)
delete this.spent[id];
}
Mempool.prototype.getInv = function getInv(callback) {
return this.tx.getAllHashes(callback);
};
Mempool.prototype.remove =
Mempool.prototype.removeTX = function removeTX(hash, callback) {
Mempool.prototype.removeTX = function removeTX(hash, callback, force) {
var self = this;
var tx, input, id, i;
callback = utils.asyncify(callback);
var unlock = this._lock(removeTX, [hash, callback], force);
if (!unlock)
return;
if (hash instanceof bcoin.tx)
hash = hash.hash('hex');
tx = this.txs[hash];
if (!tx)
return callback(new Error('TX does not exist in mempool.'));
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
id = input.prevout.hash + '/' + input.prevout.index;
if (this.spent[id] === tx)
delete this.spent[id];
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);
}
tx.inputs.forEach(function(input) {
var type = input.getType();
var address = input.getAddress();
getTX(function(err, tx) {
if (err)
return callback(err);
if (type === 'pubkey' || type === 'multisig')
address = null;
self.tx.remove(tx, function(err) {
if (err)
return callback(err);
if (!address)
return;
if (self.addresses[address]) {
delete self.addresses[address][hash];
if (Object.keys(self.addresses[address]).length === 0)
delete self.addresses[address];
}
self.emit('remove tx', tx);
});
});
tx.outputs.forEach(function(output) {
var type = output.getType();
var address = output.getAddress();
if (type === 'pubkey' || type === 'multisig')
address = null;
if (!address)
return;
if (self.addresses[address]) {
delete self.addresses[address][hash];
if (Object.keys(self.addresses[address]).length === 0)
delete self.addresses[address];
}
});
delete this.txs[hash];
this.count--;
this.size -= tx.getSize();
this.emit('remove tx', tx);
};
// Need to lock the mempool when
// downloading a new block.
Mempool.prototype.lock = function lock() {
this.locked = true;
};
Mempool.prototype.unlock = function unlock() {
this.locked = false;
};
Mempool.prototype.checkTX = function checkTX(tx, peer) {
@ -489,57 +414,60 @@ Mempool.prototype.checkTX = function checkTX(tx, peer) {
var uniq = {};
if (tx.inputs.length === 0)
return this.reject(peer, tx, 'bad-txns-vin-empty');
return this.reject(peer, tx, 'bad-txns-vin-empty', 100);
if (tx.outputs.length === 0)
return this.reject(peer, tx, 'bad-txns-vout-empty');
return this.reject(peer, tx, 'bad-txns-vout-empty', 100);
if (tx.getSize() > constants.block.maxSize)
return this.reject(peer, tx, 'bad-txns-oversize');
return this.reject(peer, 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');
return this.reject(peer, tx, 'bad-txns-vout-negative', 100);
if (output.value.cmp(constants.maxMoney) > 0)
return this.reject(peer, tx, 'bad-txns-vout-toolarge');
return this.reject(peer, 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');
return this.reject(peer, 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');
return this.reject(peer, tx, 'bad-txns-inputs-duplicate', 100);
uniq[input.out.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');
return this.reject(peer, 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');
return this.reject(peer, tx, 'bad-txns-prevout-null', 10);
}
}
return true;
};
Mempool.prototype.reject = function reject(peer, obj, reason) {
return false;
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() : []
});
// peer.reject({
// reason: reason,
// data: obj.hash ? obj.hash() : []
// });
return false;
};

View File

@ -218,7 +218,8 @@ exports.tx = {
minFee: 10000,
bareMultisig: true,
freeThreshold: exports.coin.muln(144).divn(250),
maxFreeSize: 1000
maxFreeSize: 1000,
coinbaseMaturity: 100
};
exports.tx.dustThreshold = new bn(182)

View File

@ -379,6 +379,19 @@ TX.prototype.verify = function verify(index, force, flags) {
}, this);
};
TX.prototype.verifyAsync = function verifyAsync(index, force, flags, callback) {
var self = this;
utils.nextTick(function() {
var res;
try {
res = self.verify(index, force, flags);
} catch (e) {
return callback(e);
}
return callback(null, res);
});
};
TX.prototype.isCoinbase = function isCoinbase() {
return this.inputs.length === 1 && +this.inputs[0].prevout.hash === 0;
};
@ -677,32 +690,51 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) {
return (cost + 3) / 4 | 0;
};
TX.prototype.isStandard = function isStandard(flags) {
// IsStandardTx
TX.prototype.isStandard = function isStandard(flags, ts, height, ret) {
var i, input, output, type;
var nulldata = 0;
if (!ret)
ret = { reason: null };
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (this.version > constants.tx.version || this.version < 1)
if (this.version > constants.tx.version || this.version < 1) {
ret.reason = 'version';
return false;
}
if (this.getSize() > constants.tx.maxSize)
if (ts != null) {
if (!tx.isFinal(ts, height)) {
ret.reason = 'non-final';
return false;
}
}
if (this.getVirtualSize() > constants.tx.maxSize) {
ret.reason = 'tx-size';
return false;
}
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (input.script.getSize() > 1650)
if (input.script.getSize() > 1650) {
ret.reason = 'scriptsig-size';
return false;
}
// Not accurate?
// XXX Not accurate
if (this.isCoinbase())
continue;
if (flags & constants.flags.VERIFY_SIGPUSHONLY) {
if (!input.script.isPushOnly())
if (!input.script.isPushOnly()) {
ret.reason = 'scriptsig-not-pushonly';
return false;
}
}
}
@ -710,32 +742,38 @@ TX.prototype.isStandard = function isStandard(flags) {
output = this.outputs[i];
type = output.script.getType();
if (!output.script.isStandard())
return false;
if (type === 'unknown')
if (!output.script.isStandard()) {
ret.reason = 'scriptpubkey';
return false;
}
if (type === 'nulldata') {
nulldata++;
continue;
}
if (type === 'multisig' && !constants.tx.bareMultisig)
if (type === 'multisig' && !constants.tx.bareMultisig) {
ret.reason = 'bare-multisig';
return false;
}
if (output.value.cmpn(constants.tx.dustThreshold) < 0)
if (output.value.cmpn(constants.tx.dustThreshold) < 0) {
ret.reason = 'dust';
return false;
}
}
if (nulldata > 1)
if (nulldata > 1) {
ret.reason = 'multi-op-return';
return false;
}
return true;
};
// AreInputsStandard
TX.prototype.isStandardInputs = function isStandardInputs(flags) {
var i, input, args, stack, res, redeem, targs;
var i, input, args, stack, res, redeem, targs, hadWitness;
var maxSigops = constants.script.maxScripthashSigops;
if (flags == null)
@ -755,21 +793,32 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
if (args < 0)
return false;
stack = [];
stack = new bcoin.script.stack([]);
// Bitcoind doesn't do this, but it's possible someone
// could DoS us by sending ridiculous txs to the mempool
// if we don't put this here.
// XXX Not accurate:
// Failsafe to avoid getting dos'd in case we ever
// call isStandardInputs before isStandard.
if (!input.script.isPushOnly())
return false;
res = input.script.execute(stack, this, i, flags);
// TODO: Segwit here.
res = input.script.execute(stack, this, i, flags, 0);
if (!res)
return false;
if ((flags & constants.flags.VERIFY_WITNESS)
&& input.output.isWitnessProgram()) {
hadWitness = true;
// Input script must be empty.
if (input.script.code.length !== 0)
return false;
// Verify the program in the output script
if (!this.isStandardProgram(input.witness, input.output.script, flags))
return false;
}
if ((flags & constants.flags.VERIFY_P2SH)
&& input.output.script.isScripthash()) {
if (stack.length === 0)
@ -795,8 +844,26 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
if (targs < 0)
return false;
args += targs;
if ((flags & constants.flags.VERIFY_WITNESS)
&& redeem.isWitnessProgram()) {
hasWitness = true;
// Input script must be exactly one push of the redeem script.
if (!(input.script.code.length === 1
&& utils.isEqual(input.script.code[0], raw))) {
return false;
}
// Verify the program in the redeem script
if (!this.isStandardProgram(input.witness, redeem, flags))
return false;
}
}
if (hadWitness)
continue;
if (stack.length !== args)
return false;
}
@ -804,8 +871,43 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) {
return true;
};
TX.prototype.isStandardProgram = function isStandardProgram(witness, output, flags) {
var program, witnessScript, j;
assert((flags & constants.flags.VERIFY_WITNESS) !== 0);
assert(output.isWitnessProgram());
program = output.getWitnessProgram();
if (!program.type)
return false;
if (program.version > 0) {
if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
return false;
return true;
}
if (program.type === 'witnesspubkeyhash') {
if (witness.items.length !== 2)
return false;
} else if (program.type === 'witnessscripthash') {
if (witness.items.length === 0)
return false;
} else {
assert(false);
}
for (j = 0; j < witness.items.length; j++) {
if (witness.items[j].length > constants.script.maxSize)
return false;
}
return true;
};
TX.prototype.maxSize = function maxSize() {
return this.getSize();
return this.getVirtualSize();
};
TX.prototype.getPriority = function getPriority(size) {
@ -858,6 +960,22 @@ TX.prototype.isFree = function isFree(size) {
return priority.cmp(constants.tx.freeThreshold) > 0;
};
TX.prototype.getMinFee = function getMinFee(allowFree, size) {
var fee;
size = size || this.maxSize();
if (allowFree && this.isFree(size))
return new bn(0);
fee = constants.tx.minFee.muln(size).divn(1000);
if (fee.cmpn(0) === 0 && constants.tx.minFee.cmpn(0) > 0)
fee = constants.tx.minFee.clone();
return fee;
};
TX.prototype.getHeight = function getHeight() {
if (this.height !== -1)
return this.height;

View File

@ -30,6 +30,9 @@ function TXPool(prefix, db, options) {
this.options = options;
this.busy = false;
this.jobs = [];
if (this.options.mapAddress)
this.options.indexAddress = true;
}
utils.inherits(TXPool, EventEmitter);
@ -72,10 +75,14 @@ TXPool.prototype._lock = function _lock(func, args, force) {
};
TXPool.prototype.getMap = function getMap(tx, callback) {
var input = tx.getInputAddresses();
var output = tx.getOutputAddresses();
var addresses = utils.uniqs(input.concat(output));
var map;
var input, output, addresses, map;
if (!this.options.indexAddress)
return callback();
input = tx.getInputAddresses();
output = tx.getOutputAddresses();
addresses = utils.uniqs(input.concat(output));
function cb(err, map) {
if (err)
@ -102,7 +109,7 @@ TXPool.prototype.getMap = function getMap(tx, callback) {
return callback(null, map);
}
if (!this.options.ids) {
if (!this.options.mapAddress) {
map = addresses.reduce(function(out, address) {
out[address] = [address];
return out;
@ -249,8 +256,10 @@ TXPool.prototype.add = function add(tx, callback) {
if (err)
return callback(err);
if (map.all.length === 0)
return callback(null, false);
if (self.options.mapAddress) {
if (map.all.length === 0)
return callback(null, false);
}
return self._add(tx, map, callback);
});
@ -291,28 +300,32 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
batch.put(prefix + 't/t/' + hash, tx.toExtended());
if (tx.ts === 0) {
assert(tx.ps > 0);
batch.put(prefix + 't/p/t/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY);
} else {
batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY);
}
map.all.forEach(function(id) {
batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY);
if (self.options.indexExtra) {
if (tx.ts === 0) {
batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY);
batch.put(
prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
assert(tx.ps > 0);
batch.put(prefix + 't/p/t/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY);
} else {
batch.put(
prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.put(
prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY);
}
});
if (self.options.indexAddress) {
map.all.forEach(function(id) {
batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY);
if (tx.ts === 0) {
batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY);
batch.put(
prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
} else {
batch.put(
prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.put(
prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
}
});
}
}
// Consume unspent money or add orphans
utils.forEachSerial(tx.inputs, function(input, next, i) {
@ -344,7 +357,7 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
updated = true;
if (address) {
if (self.options.indexAddress && address) {
map[address].forEach(function(id) {
batch.del(
prefix + 'u/a/' + id
@ -358,14 +371,24 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
+ input.prevout.hash
+ '/' + input.prevout.index);
if (self.options.indexSpent) {
batch.put(
prefix + 's/t/'
+ input.prevout.hash
+ '/' + input.prevout.index,
DUMMY);
}
return next();
}
// Only add orphans if this input is ours.
if (!address || !map[address].length)
return next();
if (self.options.mapAddress) {
if (!address || !map[address].length)
return next();
}
self.getTX(input.prevout.hash, function(err, result) {
self.isSpent(input.prevout.hash, input.prevout.index, function(err, result) {
if (err)
return done(err);
@ -394,8 +417,10 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
var key, coin;
// Do not add unspents for outputs that aren't ours.
if (!address || !map[address].length)
return next();
if (self.options.mapAddress) {
if (!address || !map[address].length)
return next();
}
key = hash + '/' + i;
coin = bcoin.coin(tx, i);
@ -450,7 +475,7 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
return next(err);
if (!orphans) {
if (address) {
if (self.options.indexAddress && address) {
map[address].forEach(function(id) {
batch.put(
prefix + 'u/a/' + id
@ -490,6 +515,39 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
});
};
TXPool.prototype.isSpent = function isSpent(hash, index, callback, checkCoin) {
var self = this;
if (this.options.indexSpent) {
return this.db.get('s/t/' + hash + '/' + index, function(err, exists) {
if (err && err.type !== 'NotFoundError')
return callback(err);
return callback(null, !!exists);
});
}
function getCoin(callback) {
if (!checkCoin)
return callback(null, null);
return self.getCoin(hash, index, callback);
}
return getCoin(function(err, coin) {
if (err)
return callback(err);
if (coin)
return callback(null, false);
return self.getTX(hash, function(err, tx) {
if (err)
return callback(err);
return callback(null, !!tx);
});
});
};
TXPool.prototype._confirm = function _confirm(tx, map, callback) {
var self = this;
var prefix = this.prefix + '/';
@ -520,24 +578,31 @@ TXPool.prototype._confirm = function _confirm(tx, map, callback) {
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);
});
if (self.options.indexExtra) {
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);
if (self.options.indexAddress) {
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();
if (self.options.mapAddress) {
if (!address || !map[address].length)
return next();
}
self.getCoin(hash, i, function(err, coin) {
if (err)
@ -571,6 +636,10 @@ TXPool.prototype._confirm = function _confirm(tx, map, callback) {
TXPool.prototype.remove = function remove(hash, callback) {
var self = this;
var first = Array.isArray(hash) ? hash[0] : hash;
if (!this.options.indexExtra && (first instanceof bcoin.tx))
return this.lazyRemove(hash, callback);
if (Array.isArray(hash)) {
return utils.forEachSerial(hash, function(hash, next) {
@ -594,8 +663,10 @@ TXPool.prototype.remove = function remove(hash, callback) {
if (err)
return callback(err);
if (map.all.length === 0)
return callback(null, false);
if (self.options.mapAddress) {
if (map.all.length === 0)
return callback(null, false);
}
return self._remove(tx, map, callback);
});
@ -615,8 +686,10 @@ TXPool.prototype.lazyRemove = function lazyRemove(tx, callback) {
if (err)
return callback(err);
if (map.all.length === 0)
return callback(null, false);
if (self.options.mapAddress) {
if (map.all.length === 0)
return callback(null, false);
}
return self._remove(tx, map, callback);
});
@ -630,24 +703,28 @@ TXPool.prototype._remove = function remove(tx, map, callback) {
batch.del(prefix + 't/t/' + hash);
if (tx.ts === 0) {
batch.del(prefix + 't/p/t/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash);
} else {
batch.del(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash);
}
map.all.forEach(function(id) {
batch.del(prefix + 't/a/' + id + '/' + hash);
if (self.options.indexExtra) {
if (tx.ts === 0) {
batch.del(prefix + 't/p/a/' + id + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash);
batch.del(prefix + 't/p/t/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash);
} else {
batch.del(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash);
batch.del(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash);
}
});
if (self.options.indexAddress) {
map.all.forEach(function(id) {
batch.del(prefix + 't/a/' + id + '/' + hash);
if (tx.ts === 0) {
batch.del(prefix + 't/p/a/' + id + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash);
} else {
batch.del(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash);
}
});
}
}
this.fillTX(tx, function(err) {
if (err)
@ -662,33 +739,47 @@ TXPool.prototype._remove = function remove(tx, map, callback) {
if (!input.output)
return;
if (!address || !map[address].length)
return;
if (self.options.mapAddress) {
if (!address || !map[address].length)
return;
}
map[address].forEach(function(id) {
batch.put(prefix + 'u/a/' + id
+ '/' + input.prevout.hash
+ '/' + input.prevout.index,
DUMMY);
});
if (self.options.indexAddress && address) {
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
+ '/' + input.prevout.index,
input.output.toExtended());
if (self.options.indexSpent) {
batch.del(prefix + 's/t/'
+ input.prevout.hash
+ '/' + input.prevout.index);
}
batch.del(prefix + 'o/' + input.prevout.hash + '/' + input.prevout.index);
});
tx.outputs.forEach(function(output, i) {
var address = output.getAddress();
if (!address || !map[address].length)
return;
if (self.options.mapAddress) {
if (!address || !map[address].length)
return;
}
map[address].forEach(function(id) {
batch.del(prefix + 'u/a/' + id + '/' + hash + '/' + i);
});
if (self.options.indexAddress && address) {
map[address].forEach(function(id) {
batch.del(prefix + 'u/a/' + id + '/' + hash + '/' + i);
});
}
batch.del(prefix + 'u/t/' + hash + '/' + i);
});
@ -731,8 +822,10 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) {
if (err)
return callback(err);
if (map.all.length === 0)
return callback(null, false);
if (self.options.mapAddress) {
if (map.all.length === 0)
return callback(null, false);
}
return self._unconfirm(tx, map, callback);
});
@ -757,17 +850,22 @@ TXPool.prototype._unconfirm = function unconfirm(tx, map, callback) {
tx.block = null;
batch.put(prefix + 't/t/' + hash, tx.toExtended());
batch.put(prefix + 't/p/t/' + hash, DUMMY);
batch.del(prefix + 't/h/h/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(ts) + '/' + hash);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY);
map.all.forEach(function(id) {
batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY);
batch.del(prefix + 't/h/a/' + id + '/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(ts) + '/' + hash);
batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
});
if (self.options.indexExtra) {
batch.put(prefix + 't/p/t/' + hash, DUMMY);
batch.del(prefix + 't/h/h/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(ts) + '/' + hash);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY);
if (self.options.indexAddress) {
map.all.forEach(function(id) {
batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY);
batch.del(prefix + 't/h/a/' + id + '/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(ts) + '/' + hash);
batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
});
}
}
utils.forEachSerial(tx.outputs, function(output, next, i) {
self.getCoin(hash, i, function(err, coin) {
@ -1356,8 +1454,8 @@ TXPool.prototype.getBalanceByAddress = function getBalanceByAddress(address, cal
});
};
TXPool.prototype.getAll = function getAll(callback) {
return this.getAllByAddress(null, callback);
TXPool.prototype.getAllHashes = function getAllHashes(callback) {
return this.getTXHashes(null, callback);
};
TXPool.prototype.getCoins = function getCoins(callback) {

View File

@ -1884,6 +1884,13 @@ utils.serial = function serial(stack, callback) {
})();
};
utils.toMap = function toMap(arr) {
return arr.reduce(function(out, value) {
out[value] = true;
return out;
}, {});
};
function SyncBatch(db) {
this.db = db;
this.ops = [];

View File

@ -98,7 +98,10 @@ WalletDB.prototype._init = function _init() {
});
this.tx = new bcoin.txdb('w', this.db, {
ids: true
indexSpent: false,
indexExtra: true,
indexAddress: true,
mapAddress: true
});
this.tx.on('error', function(err) {
@ -543,16 +546,16 @@ WalletDB.prototype.fillCoin = function fillCoin(tx, callback) {
WalletDB.prototype.removeBlockSPV = function removeBlockSPV(block, callback) {
var self = this;
callback = utils.ensure(callback);
this.tx.getHeightHashes(block.height, function(err, txs) {
if (err)
return callback(err);
txs.forEach(function(tx) {
self.tx.unconfirm(tx);
});
callback();
utils.forEachSerial(txs, function(tx, next) {
self.tx.unconfirm(tx, next);
}, callback);
});
};
@ -576,15 +579,31 @@ function Provider(db) {
EventEmitter.call(this);
this.loaded = true;
this.loaded = false;
this.db = db;
this.id = null;
this._init();
}
utils.inherits(Provider, EventEmitter);
Provider.prototype._init = function _init() {
var self = this;
if (this.db.loaded) {
this.loaded = true;
return;
}
this.db.once('open', function() {
self.loaded = true;
self.emit('open');
});
};
Provider.prototype.open = function open(callback) {
return utils.nextTick(callback);
return this.db.open(callback);
};
Provider.prototype.setID = function setID(id) {

View File

@ -161,10 +161,6 @@ workers.listen = function listen() {
workers.verify = function verify(tx, index, force, flags) {
tx = bcoin.tx.fromExtended(new Buffer(tx, 'hex'), true);
if (tx.getOutputValue().cmp(tx.getInputValue()) > 0) {
utils.debug('TX is spending funds it does not have: %s', tx.rhash);
return false;
}
return tx.verify(index, force, flags);
};