mempool work. accurate reject messages.

This commit is contained in:
Christopher Jeffrey 2016-03-26 03:17:54 -07:00
parent 37c488c802
commit 6194c66d8f
11 changed files with 537 additions and 222 deletions

View File

@ -377,7 +377,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
}
if (!block.verify(ret)) {
self.emit('verify-error', block, ret.reason, ret.score, peer);
self.emit('verify-error',
block, 'invalid', ret.reason, ret.score, peer);
return done(null, false);
}
@ -401,13 +402,15 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
// Ensure the timestamp is correct
if (block.ts <= medianTime) {
utils.debug('Block time is lower than median: %s', block.rhash);
self.emit('verify-error', block, 'time-too-old', 0, peer);
self.emit('verify-error',
block, 'invalid', 'time-too-old', 0, peer);
return done(null, false);
}
if (block.bits !== self.getTarget(prev, block)) {
utils.debug('Block is using wrong target: %s', block.rhash);
self.emit('verify-error', block, 'bad-diffbits', 100, peer);
self.emit('verify-error',
block, 'invalid', 'bad-diffbits', 100, peer);
return done(null, false);
}
@ -426,7 +429,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
// once the majority of blocks are using it.
if (block.version < 2 && prev.isOutdated(2)) {
utils.debug('Block is outdated (v2): %s', block.rhash);
self.emit('verify-error', block, 'bad-version', 0, peer);
self.emit('verify-error',
block, 'obsolete', 'bad-version', 0, peer);
return done(null, false);
}
@ -434,7 +438,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
// once the majority of blocks are using it.
if (block.version < 3 && prev.isOutdated(3)) {
utils.debug('Block is outdated (v3): %s', block.rhash);
self.emit('verify-error', block, 'bad-version', 0, peer);
self.emit('verify-error',
block, 'obsolete', 'bad-version', 0, peer);
return done(null, false);
}
@ -442,7 +447,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
// once the majority of blocks are using it.
if (block.version < 4 && prev.isOutdated(4)) {
utils.debug('Block is outdated (v4): %s', block.rhash);
self.emit('verify-error', block, 'bad-version', 0, peer);
self.emit('verify-error',
block, 'obsolete', 'bad-version', 0, peer);
return done(null, false);
}
@ -451,7 +457,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
if (network.segwitHeight !== -1 && height >= network.segwitHeight) {
if (block.version < 5 && prev.isOutdated(5)) {
utils.debug('Block is outdated (v5): %s', block.rhash);
self.emit('verify-error', block, 'bad-version', 0, peer);
self.emit('verify-error',
block, 'obsolete', 'bad-version', 0, peer);
return done(null, false);
}
}
@ -493,7 +500,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
if (coinbaseHeight) {
if (block.getCoinbaseHeight() !== height) {
utils.debug('Block has bad coinbase height: %s', block.rhash);
self.emit('verify-error', block, 'bad-cb-height', 100, peer);
self.emit('verify-error',
block, 'invalid', 'bad-cb-height', 100, peer);
return done(null, false);
}
}
@ -501,13 +509,15 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
if (block.version >= 5 && segwit) {
if (block.commitmentHash !== block.getCommitmentHash()) {
utils.debug('Block failed witnessroot test: %s', block.rhash);
self.emit('verify-error', block, 'bad-blk-wit-length', 100, peer);
self.emit('verify-error',
block, 'invalid', 'bad-blk-wit-length', 100, peer);
return done(null, false);
}
} else {
if (block.hasWitness()) {
utils.debug('Unexpected witness data found: %s', block.rhash);
self.emit('verify-error', block, 'unexpected-witness', 100, peer);
self.emit('verify-error',
block, 'invalid', 'unexpected-witness', 100, peer);
return done(null, false);
}
}
@ -523,7 +533,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
// regards to nSequence and nLockTime.
if (!tx.isFinal(height, ts)) {
utils.debug('TX is not final: %s (%s)', block.rhash, i);
self.emit('verify-error', block, 'bad-txns-nonfinal', 10, peer);
self.emit('verify-error',
block, 'invalid', 'bad-txns-nonfinal', 10, peer);
return done(null, false);
}
}
@ -557,7 +568,8 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, peer,
if (result) {
utils.debug('Block is overwriting txids: %s', block.rhash);
if (!(network.type === 'main' && (height === 91842 || height === 91880))) {
self.emit('verify-error', block, 'bad-txns-BIP30', 100, peer);
self.emit('verify-error',
block, 'invalid', 'bad-txns-BIP30', 100, peer);
return next(null, false);
}
}
@ -606,7 +618,8 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, peer, c
if (sigops > constants.script.maxBlockSigops) {
utils.debug('Block has too many sigops: %s', block.rhash);
self.emit('verify-error', block, 'bad-blk-sigops', 100, peer);
self.emit('verify-error',
block, 'invalid', 'bad-blk-sigops', 100, peer);
return callback(null, false);
}
@ -629,7 +642,8 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, peer, c
utils.revHex(input.prevout.hash) + '/' + input.prevout.index);
if (height < network.checkpoints.lastHeight)
throw new Error('BUG: Spent inputs in historical data!');
self.emit('verify-error', block, 'bad-txns-inputs-missingorspent', 100, peer);
self.emit('verify-error',
block, 'invalid', 'bad-txns-inputs-missingorspent', 100, peer);
return callback(null, false);
}
@ -682,7 +696,8 @@ Chain.prototype._checkReward = function _checkReward(block) {
actual.iadd(block.txs[i].getFee());
if (claimed.cmp(actual) > 0) {
self.emit('verify-error', block, 'bad-cb-amount', 100, peer);
self.emit('verify-error',
block, 'invalid', 'bad-cb-amount', 100, peer);
return false;
}
@ -974,7 +989,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
chain: !!self.invalid[prevHash]
}, peer);
self.invalid[hash] = true;
self.emit('verify-error', block, 'duplicate', 0, peer);
self.emit('verify-error',
block, 'duplicate', 'duplicate', 0, peer);
return done();
}
@ -990,7 +1006,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
seen: false,
chain: false
}, peer);
self.emit('verify-error', block, ret.reason, ret.score, peer);
self.emit('verify-error',
block, 'invalid', ret.reason, ret.score, peer);
return done();
}
@ -1024,7 +1041,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
seen: true
}, peer);
self.emit('verify-error', block, 'bad-prevblk', 0, peer);
self.emit('verify-error',
block, 'invalid', 'bad-prevblk', 0, peer);
return done();
}
@ -1048,7 +1066,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
hash: hash,
seen: false
}, peer);
self.emit('verify-error', block, 'bad-prevblk', 0, peer);
self.emit('verify-error',
block, 'invalid', 'bad-prevblk', 0, peer);
return done();
}
@ -1078,7 +1097,8 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
checkpoint: true
}, peer);
self.emit('verify-error', block, 'checkpoint mismatch', 100, peer);
self.emit('verify-error',
block, 'checkpoint', 'checkpoint mismatch', 100, peer);
return done();
}

View File

@ -10,34 +10,17 @@ var utils = bcoin.utils;
var network = bcoin.protocol.network;
var db = {};
module.exports = function ldb(name, options) {
var file = bcoin.prefix + '/' + name + '-' + network.type + '.db';
var backend = typeof options.db === 'string'
? options.db
: process.env.BCOIN_DB;
/**
* LDB
*/
function ldb(name, options) {
var file = getLocation(name);
if (!db[file]) {
if (!options)
options = {};
if (!backend || backend === 'leveldb')
backend = 'leveldown';
else if (backend === 'rocksdb')
backend = 'rocksdown';
else if (backend === 'lmdb')
backend = 'lmdb';
else if (backend === 'memory')
backend = 'memdown';
if (bcoin.isBrowser && backend !== 'memdown') {
backend = require('level-js');
} else {
if (backend !== 'memdown')
bcoin.ensurePrefix();
backend = require(backend);
}
db[file] = new LowlevelUp(file, {
keyEncoding: 'ascii',
valueEncoding: 'binary',
@ -59,12 +42,57 @@ module.exports = function ldb(name, options) {
// optimizeCompaction: 'level',
// memtableBudget: 512 << 20,
db: backend
db: getBackend(options.db)
});
}
return db[file];
};
}
function getLocation(name) {
return bcoin.prefix + '/' + name + '-' + network.type + '.db';
}
function getBackend(backend) {
if (typeof backend !== 'string')
backend = process.env.BCOIN_DB;
if (!backend || backend === 'leveldb')
backend = 'leveldown';
else if (backend === 'rocksdb')
backend = 'rocksdown';
else if (backend === 'lmdb')
backend = 'lmdb';
else if (backend === 'memory')
backend = 'memdown';
// Require directly for browserify
if (backend === 'memdown')
return require('memdown');
if (bcoin.isBrowser)
return require('level-js');
bcoin.ensurePrefix();
return require(backend);
}
function destroy(name, backend, callback) {
var file = getLocation(name);
if (!callback) {
callback = backend;
backend = null;
}
backend = getBackend(backend);
if (!backend.destroy)
return utils.nextTick(callback);
backend.destroy(file, callback);
}
/**
* LowlevelUp
@ -168,3 +196,13 @@ LowlevelUp.prototype.getProperty = function getProperty(name) {
LowlevelUp.prototype.approximateSize = function approximateSize(start, end, callback) {
return this.binding.approximateSize(start, end, callback);
};
/**
* Expose
*/
exports = ldb;
exports.LowlevelUp = LowlevelUp;
exports.destroy = destroy;
module.exports = exports;

View File

@ -9,6 +9,7 @@ var EventEmitter = require('events').EventEmitter;
var bcoin = require('../bcoin');
var bn = require('bn.js');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = require('./utils');
var assert = utils.assert;
var BufferWriter = require('./writer');
@ -32,34 +33,20 @@ function Mempool(node, options) {
this.node = node;
this.chain = node.chain;
this.db = bcoin.ldb('mempool', {
db: 'memdown'
});
this.tx = new bcoin.txdb('m', this.db, {
indexSpent: true,
indexExtra: false,
indexAddress: false,
mapAddress: false,
verify: false
});
this.loaded = false;
this.jobs = [];
this.busy = false;
this.locker = new bcoin.locker(this, this.add, 20 << 20);
this.pending = [];
this.pendingTX = {};
this.pendingSize = 0;
this.pendingLimit = 20 << 20;
this.locker = new bcoin.locker(this, this.add, this.pendingLimit);
this.db = null;
this.tx = null;
this.size = 0;
this.freeCount = 0;
this.lastTime = 0;
this.limitFree = this.options.limitFree !== false;
this.limitFreeRelay = this.options.limitFreeRelay || 15;
this.relayPriority = this.options.relayPriority !== false;
this.requireStandard = this.options.requireStandard !== false;
this.rejectInsaneFees = this.options.rejectInsaneFees !== false;
@ -73,6 +60,10 @@ utils.inherits(Mempool, EventEmitter);
Mempool.flags = constants.flags.STANDARD_VERIFY_FLAGS;
Mempool.mandatory = constants.flags.MANDATORY_VERIFY_FLAGS;
Mempool.ANCESTOR_LIMIT = 25;
Mempool.MAX_MEMPOOL_SIZE = 300 << 20;
Mempool.MEMPOOL_EXPIRY = 72 * 60 * 60;
Mempool.prototype._lock = function _lock(func, args, force) {
return this.locker.lock(func, args, force);
};
@ -83,20 +74,53 @@ Mempool.prototype.purgePending = function purgePending() {
Mempool.prototype._init = function _init() {
var self = this;
var unlock = this._lock(utils.nop, []);
if (this.db.loaded) {
this.loaded = true;
return;
}
bcoin.ldb.destroy('mempool', 'memdown', function(err) {
if (err) {
unlock();
return self.emit('error', err);
}
this.db.once('open', function() {
self.loaded = true;
self.emit('open');
self.db = bcoin.ldb('mempool', {
db: 'memdown'
});
self.tx = new bcoin.txdb('m', self.db, {
indexExtra: false,
indexAddress: false,
mapAddress: false,
verify: false
});
self.db.open(function(err) {
if (err) {
unlock();
return self.emit('error', err);
}
self.dynamicMemoryUsage(function(err, size) {
if (err)
self.emit('error', err);
else
self.size = size;
unlock();
self.loaded = true;
self.emit('open');
});
});
});
};
Mempool.prototype.dynamicMemoryUsage = function dynamicMemoryUsage(callback) {
return this.db.approximateSize('m', 'm~', callback);
};
Mempool.prototype.open = function open(callback) {
return this.db.open(callback);
if (this.loaded)
return utils.nextTick(callback);
return this.once('open', callback);
};
Mempool.prototype.addBlock = function addBlock(block, callback, force) {
@ -107,14 +131,9 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) {
callback = utils.wrap(callback, unlock);
this.open(function(err) {
if (err)
return callback(err);
utils.forEachSerial(block.txs, function(tx, next) {
self.tx.removeUnchecked(tx, next);
}, callback);
});
utils.forEachSerial(block.txs, function(tx, next) {
self.removeUnchecked(tx, next);
}, callback);
};
Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
@ -126,10 +145,91 @@ Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
callback = utils.wrap(callback, unlock);
utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) {
self.tx.addUnchecked(tx, next);
self.addUnchecked(tx, next);
}, callback);
};
Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
var self = this;
if (this.size <= Mempool.MAX_MEMPOOL_SIZE)
return callback(null, true);
this.db.getRange({
start: 0,
end: utils.now() - Mempool.MEMPOOL_EXPIRY
}, function(err, txs) {
if (err)
return callback(err);
utils.forEachSerial(function(tx, next) {
self.removeUnchecked(tx, next);
}, function(err) {
if (err)
return callback(err);
self.purgeOrphans(function(err) {
if (err)
return callback(err);
return callback(self.size <= Mempool.MAX_MEMPOOL_SIZE);
});
});
});
};
Mempool.prototype.purgeOrphans = function purgeOrphans(callback) {
var self = this;
var batch = this.db.batch();
callback = utils.ensure(callback);
utils.forEachSerial(['m/D', 'm/d'], function(type, callback) {
var iter = self.db.iterator({
gte: type,
lte: type + '~',
keys: true,
values: false,
fillCache: false,
keyAsBuffer: false
});
(function next() {
iter.next(function(err, key, value) {
if (err) {
return iter.end(function() {
callback(err);
});
}
if (key === undefined)
return iter.end(callback);
batch.del(key);
next();
});
})();
}, function(err) {
if (err)
return callback(err);
batch.write(function(err) {
if (err)
return callback(err);
self.dynamicMemoryUsage(function(err, size) {
if (err)
return callback(err);
self.size = size;
return callback();
});
});
});
};
Mempool.prototype.get =
Mempool.prototype.getTX = function getTX(hash, callback) {
if (hash instanceof bcoin.tx)
@ -197,18 +297,18 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
callback = utils.asyncify(callback);
if (!this.checkTX(tx, peer))
return callback(new VerifyError('CheckTransaction failed', -1));
return callback(new VerifyError('invalid', 'CheckTransaction failed', -1));
if (tx.isCoinbase()) {
peer.sendReject(tx, 'coinbase', 100);
return callback(new VerifyError('coinbase as individual tx', 100));
peer.sendReject(tx, 'invalid', 'coinbase', 100);
return callback(new VerifyError('invalid', 'coinbase', 100));
}
ts = utils.now();
height = this.chain.height + 1;
if (this.requireStandard && !tx.isStandard(Mempool.flags, ts, height, ret)) {
peer.sendReject(tx, ret.reason, 0);
peer.sendReject(tx, 'nonstandard', ret.reason, 0);
return callback(new VerifyError(ret.reason, 0));
}
@ -216,33 +316,57 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
if (err)
return callback(err);
if (exists)
if (exists) {
peer.sendReject(tx, 'alreadyknown', 'txn-already-in-mempool', 0);
return callback();
}
self.tx.isDoubleSpend(tx, function(err, doubleSpend) {
if (err)
return callback(err);
if (doubleSpend) {
peer.sendReject(tx, 'bad-txns-inputs-spent', 0);
return callback(new VerifyError('bad-txns-inputs-spent', 0));
peer.sendReject(tx, 'duplicate', 'bad-txns-inputs-spent', 0);
return callback(new VerifyError(
'duplicate',
'bad-txns-inputs-spent',
0));
}
self.node.fillCoin(tx, function(err) {
if (err)
return callback(err);
if (!tx.hasPrevout())
if (!tx.hasPrevout()) {
if (self.size > Mempool.MAX_MEMPOOL_SIZE) {
return callback(
new VerifyError('insufficientfee',
'mempool full',
0));
}
return self.storeOrphan(tx, callback);
}
self.verify(tx, function(err) {
if (err) {
if (err.type === 'VerifyError' && err.score >= 0)
peer.sendReject(tx, err.reason, err.score);
peer.sendReject(tx, err.code, err.reason, err.score);
return callback(err);
}
self.addUnchecked(tx, peer, callback);
self.limitMempoolSize(function(err, result) {
if (err)
return callback(err);
if (!result) {
return callback(
new VerifyError('insufficientfee',
'mempool full',
0));
}
self.addUnchecked(tx, peer, callback);
});
});
});
});
@ -255,7 +379,9 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
if (err)
return callback(err);
self.size += tx.getSize();
self.emit('tx', tx);
self.emit('add tx', tx);
utils.debug('Added tx %s to the mempool.', tx.rhash);
@ -274,15 +400,40 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
});
};
Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) {
var self = this;
this.tx.removeUnchecked(tx, function(err) {
if (err)
return callback(err);
self.size -= tx.getSize();
self.emit('remove tx', tx);
return callback();
});
};
Mempool.prototype.verify = function verify(tx, callback) {
var self = this;
var total, input, coin, i, fee, now;
var height = this.chain.height + 1;
var total, input, coin, i, fee, now, free, minFee;
if (this.requireStandard && !tx.isStandardInputs(Mempool.flags))
return callback(new VerifyError('TX inputs are not standard.', -1));
if (network.type !== 'segwit') {
if (tx.hasWitness())
return callback(new VerifyError('nonstandard', 'no-witness-yet', 0));
}
if (tx.getSigops(true) > constants.script.maxSigops)
return callback(new VerifyError('bad-txns-too-many-sigops', 0));
if (this.requireStandard && !tx.isStandardInputs(Mempool.flags)) {
return callback(new VerifyError(
'nonstandard',
'bad-txns-nonstandard-inputs',
0));
}
if (tx.getSigops(true) > constants.script.maxSigops) {
return callback(new VerifyError(
'nonstandard',
'bad-txns-too-many-sigops',
0));
}
total = new bn(0);
for (i = 0; i < tx.inputs.length; i++) {
@ -290,34 +441,61 @@ Mempool.prototype.verify = function verify(tx, callback) {
coin = input.output;
if (coin.coinbase) {
if (this.chain.height - coin.height < constants.tx.coinbaseMaturity)
return callback(new VerifyError('bad-txns-premature-spend-of-coinbase', 0));
if (this.chain.height - coin.height < constants.tx.coinbaseMaturity) {
return callback(new VerifyError(
'invalid',
'bad-txns-premature-spend-of-coinbase',
0));
}
}
if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0)
return callback(new VerifyError('bad-txns-inputvalues-outofrange', 100));
if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0) {
return callback(new VerifyError(
'invalid',
'bad-txns-inputvalues-outofrange',
100));
}
total.iadd(coin.value);
}
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0)
return callback(new VerifyError('bad-txns-inputvalues-outofrange', 100));
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) {
return callback(new VerifyError(
'invalid',
'bad-txns-inputvalues-outofrange',
100));
}
if (tx.getOutputValue().cmp(total) > 0)
return callback(new VerifyError('bad-txns-in-belowout', 100));
return callback(new VerifyError('invalid', 'bad-txns-in-belowout', 100));
fee = total.sub(tx.getOutputValue());
if (fee.cmpn(0) < 0)
return callback(new VerifyError('bad-txns-fee-negative', 100));
return callback(new VerifyError('invalid', 'bad-txns-fee-negative', 100));
if (fee.cmp(constants.maxMoney) > 0)
return callback(new VerifyError('bad-txns-fee-outofrange', 100));
return callback(new VerifyError('invalid', 'bad-txns-fee-outofrange', 100));
if (this.limitFree && fee.cmp(tx.getMinFee(true)) < 0)
return callback(new VerifyError('insufficient fee', 0));
minFee = tx.getMinFee();
if (fee.cmp(minFee) < 0) {
if (this.relayPriority && fee.cmpn(0) === 0) {
free = tx.isFree(height);
if (!free) {
return callback(new VerifyError(
'insufficientfee',
'insufficient priority',
0));
}
} else {
return callback(new VerifyError(
'insufficientfee',
'insufficient fee',
0));
}
}
if (this.limitFree && fee.cmpn(tx.getMinFee()) < 0) {
if (this.limitFree && free) {
now = utils.now();
if (!this.lastTime)
@ -326,33 +504,85 @@ Mempool.prototype.verify = function verify(tx, callback) {
this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime);
this.lastTime = now;
if (this.freeCount > this.limitFreeRelay * 10 * 1000)
return callback(new VerifyError('insufficient priority', 0));
if (this.freeCount > this.limitFreeRelay * 10 * 1000) {
return callback(new VerifyError(
'insufficientfee',
'rate limited free transaction',
0));
}
this.freeCount += tx.getVirtualSize();
}
if (this.rejectInsaneFees && fee.cmpn(tx.getMinFee().muln(10000)) > 0)
return callback(new VerifyError('TX has an insane fee.', -1));
if (this.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0)
return callback(new VerifyError('highfee', 'absurdly-high-fee', 0));
// Do this in the worker pool.
tx.verifyAsync(null, true, Mempool.flags, function(err, result) {
this.countAncestors(tx, function(err, count) {
if (err)
return callback(err);
if (!result) {
return tx.verifyAsync(null, true, Mempool.mandatory, function(err, result) {
if (err)
return callback(err);
if (!result)
return callback(new VerifyError('mandatory-script-verify-flag', 0));
return callback(new VerifyError('non-mandatory-script-verify-flag', 0));
});
if (count > Mempool.ANCESTOR_LIMIT) {
return callback(new VerifyError(
'nonstandard',
'too-long-mempool-chain',
0));
}
return callback();
// Do this in the worker pool.
tx.verifyAsync(null, true, Mempool.flags, function(err, result) {
if (err)
return callback(err);
if (!result) {
return tx.verifyAsync(null, true, Mempool.mandatory, function(err, result) {
if (err)
return callback(err);
if (!result) {
return callback(new VerifyError(
'nonstandard',
'mandatory-script-verify-flag',
0));
}
return callback(
new VerifyError('nonstandard',
'non-mandatory-script-verify-flag',
0));
});
}
return callback();
});
});
};
Mempool.prototype.countAncestors = function countAncestors(tx, callback) {
var self = this;
var inputs = new Array(tx.inputs.length);
utils.forEachSerial(tx.inputs, function(input, next, i) {
inputs[i] = 0;
self.getTX(input.prevout.hash, function(err, tx) {
if (err)
return next(err);
if (!tx)
return next();
self.countAncestors(tx, function(err, max) {
if (err)
return next(err);
inputs[i] += max;
next();
});
});
}, function(err) {
if (err)
return callback(err);
return callback(null, inputs.sort().pop());
});
};
@ -507,7 +737,7 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
});
};
Mempool.prototype.getInv = function getInv(callback) {
Mempool.prototype.getSnapshot = function getSnapshot(callback) {
return this.tx.getAllHashes(callback);
};
@ -516,60 +746,73 @@ Mempool.prototype.checkTX = function checkTX(tx, peer) {
};
Mempool.checkTX = function checkTX(tx, peer) {
var i, input, output, size;
var total = new bn(0);
var uniq = {};
var total = new bn(0);
var i, input, output, size;
if (!peer)
peer = DUMMY_PEER;
if (tx.inputs.length === 0)
return peer.sendReject(tx, 'bad-txns-vin-empty', 100);
return peer.sendReject(tx, 'invalid', 'bad-txns-vin-empty', 100);
if (tx.outputs.length === 0)
return peer.sendReject(tx, 'bad-txns-vout-empty', 100);
return peer.sendReject(tx, 'invalid', 'bad-txns-vout-empty', 100);
if (tx.getVirtualSize() > constants.block.maxSize)
return peer.sendReject(tx, 'bad-txns-oversize', 100);
return peer.sendReject(tx, 'invalid', 'bad-txns-oversize', 100);
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
if (output.value.cmpn(0) < 0)
return peer.sendReject(tx, 'bad-txns-vout-negative', 100);
return peer.sendReject(tx, 'invalid', 'bad-txns-vout-negative', 100);
if (output.value.cmp(constants.maxMoney) > 0)
return peer.sendReject(tx, 'bad-txns-vout-toolarge', 100);
return peer.sendReject(tx, 'invalid', 'bad-txns-vout-toolarge', 100);
total.iadd(output.value);
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0)
return peer.sendReject(tx, 'bad-txns-txouttotal-toolarge', 100);
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) {
return peer.sendReject(tx,
'invalid',
'bad-txns-txouttotal-toolarge',
100);
}
}
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (uniq[input.prevout.hash])
return peer.sendReject(tx, 'bad-txns-inputs-duplicate', 100);
return peer.sendReject(tx, 'invalid', 'bad-txns-inputs-duplicate', 100);
uniq[input.prevout.hash] = true;
}
if (tx.isCoinbase()) {
size = tx.inputs[0].script.getSize();
if (size < 2 || size > 100)
return peer.sendReject(tx, 'bad-cb-length', 100);
return peer.sendReject(tx, 'invalid', 'bad-cb-length', 100);
} else {
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (+input.prevout.hash === 0)
return peer.sendReject(tx, 'bad-txns-prevout-null', 10);
return peer.sendReject(tx, 'invalid', 'bad-txns-prevout-null', 10);
}
}
return true;
};
function VerifyError(reason, score) {
/**
* VerifyError
*/
function VerifyError(code, reason, score) {
Error.call(this);
if (Error.captureStackTrace)
Error.captureStackTrace(this, VerifyError);
this.type = 'VerifyError';
this.code = code;
this.message = reason;
this.reason = score === -1 ? null : reason;
this.score = score;

View File

@ -882,7 +882,7 @@ MTX.prototype.selectCoins = function selectCoins(unspent, options) {
// Calculate max possible size after signing.
size = tx.maxSize(options.m, options.n);
// if (newkb == null && tx.isFree(size)) {
// if (newkb == null && tx.isFree(this.chain.height + 1, size)) {
// fee = new bn(0);
// break;
// }

View File

@ -679,8 +679,8 @@ Peer.prototype.setMisbehavior = function setMisbehavior(dos) {
return this.pool.setMisbehavior(this, dos);
};
Peer.prototype.sendReject = function sendReject(obj, reason, dos) {
return this.pool.reject(this, obj, reason, dos);
Peer.prototype.sendReject = function sendReject(obj, code, reason, dos) {
return this.pool.reject(this, obj, code, reason, dos);
};
/**

View File

@ -191,8 +191,8 @@ Pool.prototype._init = function _init() {
}
});
this.chain.on('verify-error', function(block, reason, score, peer) {
peer.sendReject(block, reason, score);
this.chain.on('verify-error', function(block, code, reason, score, peer) {
peer.sendReject(block, code, reason, score);
});
this.chain.on('fork', function(block, data, peer) {
@ -1885,14 +1885,15 @@ Pool.prototype.isMisbehaving = function isMisbehaving(host) {
return false;
};
Pool.prototype.reject = function reject(peer, obj, reason, dos) {
Pool.prototype.reject = function reject(peer, obj, code, reason, dos) {
if (dos != null)
peer.setMisbehavior(dos);
utils.debug('Rejecting %s %s: reason=%s',
obj.type, obj.hash('hex'), reason);
utils.debug('Rejecting %s %s: ccode=%s reason=%s',
obj.type, obj.hash('hex'), code, reason);
peer.reject({
ccode: code,
reason: reason,
data: obj.hash()
});

View File

@ -249,7 +249,12 @@ exports.reject = {
nonstandard: 0x40,
dust: 0x41,
insufficientfee: 0x42,
checkpoint: 0x43
checkpoint: 0x43,
// Internal codes (NOT FOR USE ON NETWORK)
internal: 0x100,
highfee: 0x100,
alreadyknown: 0x101,
conflict: 0x102
};
exports.rejectByVal = Object.keys(exports.reject).reduce(function(out, name) {

View File

@ -580,9 +580,13 @@ Framer.headers = function _headers(block, writer) {
Framer.reject = function reject(details, writer) {
var p = new BufferWriter(writer);
var ccode = details.ccode;
if (ccode >= constants.reject.internal)
ccode = constants.reject.invalid;
p.writeVarString(details.message || '', 'ascii');
p.writeU8(constants.reject[details.ccode] || constants.reject.malformed);
p.writeU8(constants.reject[ccode] || constants.reject.invalid);
p.writeVarString(details.reason || '', 'ascii');
if (details.data)
p.writeHash(details.data);

View File

@ -916,10 +916,11 @@ TX.prototype.maxSize = function maxSize() {
return this.getVirtualSize();
};
TX.prototype.getPriority = function getPriority(size) {
var sum, i, input, age, height;
TX.prototype.getPriority = function getPriority(height, size) {
var sum, i, input, age;
height = this.height;
if (height == null)
height = this.height;
if (height === -1)
height = null;
@ -927,7 +928,9 @@ TX.prototype.getPriority = function getPriority(size) {
if (!this.hasPrevout())
return new bn(0);
size = size || this.maxSize();
if (size == null)
size = this.maxSize();
sum = new bn(0);
for (i = 0; i < this.inputs.length; i++) {
@ -950,29 +953,31 @@ TX.prototype.getPriority = function getPriority(size) {
return sum.divn(size);
};
TX.prototype.isFree = function isFree(size) {
TX.prototype.isFree = function isFree(height, size) {
var priority;
if (!this.hasPrevout())
return false;
size = size || this.maxSize();
if (height == null)
height = this.height;
if (size == null)
size = this.maxSize();
if (size >= constants.tx.maxFreeSize)
return false;
priority = this.getPriority();
priority = this.getPriority(height, size);
return priority.cmp(constants.tx.freeThreshold) > 0;
};
TX.prototype.getMinFee = function getMinFee(allowFree, size) {
TX.prototype.getMinFee = function getMinFee(size) {
var fee;
size = size || this.maxSize();
if (allowFree && this.isFree(size))
return new bn(0);
if (size == null)
size = this.maxSize();
fee = new bn(constants.tx.minFee).muln(size).divn(1000);

View File

@ -1546,6 +1546,7 @@ TXPool.prototype.addUnchecked = function addUnchecked(tx, callback, force) {
batch = this.db.batch();
batch.put(prefix + 't/t/' + hash, tx.toExtended());
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY);
tx.getAddresses().forEach(function(address) {
batch.put(prefix + 't/a/' + address + '/' + hash, DUMMY);
@ -1586,68 +1587,77 @@ TXPool.prototype.addUnchecked = function addUnchecked(tx, callback, force) {
});
};
TXPool.prototype.removeUnchecked = function removeUnchecked(tx, callback, force) {
TXPool.prototype.removeUnchecked = function removeUnchecked(hash, callback, force) {
var self = this;
var prefix = this.prefix + '/';
var hash = tx.hash('hex');
var batch;
var unlock = this._lock(removeUnchecked, [tx, callback], force);
var unlock = this._lock(removeUnchecked, [hash, callback], force);
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
batch = this.db.batch();
if (hash.hash)
hash = hash.hash('hex');
batch.del(prefix + 't/t/' + hash);
batch.del(prefix + 'D/' + hash);
tx.getAddresses().forEach(function(address) {
batch.del(prefix + 't/a/' + address + '/' + hash);
});
tx.inputs.forEach(function(input) {
var key = input.prevout.hash + '/' + input.prevout.index;
var address;
if (tx.isCoinbase())
return;
if (!input.output)
return;
address = input.getAddress();
batch.del(prefix + 'u/t/' + key);
batch.del(prefix + 's/t/' + key);
if (address)
batch.del(prefix + 'u/a/' + address + '/' + key);
});
tx.outputs.forEach(function(output, i) {
var key = hash + '/' + i;
var address = output.getAddress();
batch.del(prefix + 'u/t/' + key);
if (address)
batch.del(prefix + 'u/a/' + address + '/' + key);
});
batch.write(function(err) {
this.getTX(hash, function(err, tx) {
if (err)
return callback(err);
self.emit('remove tx', tx);
return callback();
if (!tx)
return callback();
batch = self.db.batch();
batch.del(prefix + 't/t/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash);
batch.del(prefix + 'D/' + hash);
tx.getAddresses().forEach(function(address) {
batch.del(prefix + 't/a/' + address + '/' + hash);
});
tx.inputs.forEach(function(input) {
var key = input.prevout.hash + '/' + input.prevout.index;
var address;
if (tx.isCoinbase())
return;
if (!input.output)
return;
address = input.getAddress();
batch.del(prefix + 'u/t/' + key);
batch.del(prefix + 's/t/' + key);
if (address)
batch.del(prefix + 'u/a/' + address + '/' + key);
});
tx.outputs.forEach(function(output, i) {
var key = hash + '/' + i;
var address = output.getAddress();
batch.del(prefix + 'u/t/' + key);
if (address)
batch.del(prefix + 'u/a/' + address + '/' + key);
});
batch.write(function(err) {
if (err)
return callback(err);
self.emit('remove tx', tx);
return callback();
});
});
};
TXPool.prototype.zap = function zap(tip, callback, force) {
TXPool.prototype.zap = function zap(now, age, callback, force) {
var self = this;
var now = tip.ts;
var age = 10 * 60 * 60;
var unlock = this._lock(zap, [tip, callback], force);
if (!unlock)
@ -1655,23 +1665,13 @@ TXPool.prototype.zap = function zap(tip, callback, force) {
callback = utils.wrap(callback, unlock);
// return this.getRange(null, {
// start: 0,
// end: now - age
// }, function(err, txs) {
// });
this.getPending(function(err, txs) {
return this.getRange(null, {
start: 0,
end: now - age
}, function(err, txs) {
if (err)
return callback(err);
txs = txs.filter(function(tx) {
return now > tx.ps + age;
});
if (txs.length === 0)
return callback();
self.fillTX(txs, function(err) {
if (err)
return callback(err);

View File

@ -98,7 +98,6 @@ WalletDB.prototype._init = function _init() {
});
this.tx = new bcoin.txdb('w', this.db, {
indexSpent: true,
indexExtra: true,
indexAddress: true,
mapAddress: true,