rewrite mempool.
This commit is contained in:
parent
d7bb115609
commit
2797cd2f44
@ -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');
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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)
|
||||
|
||||
162
lib/bcoin/tx.js
162
lib/bcoin/tx.js
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 = [];
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user