837 lines
20 KiB
JavaScript
837 lines
20 KiB
JavaScript
/**
|
|
* mempool.js - mempool for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* https://github.com/indutny/bcoin
|
|
*/
|
|
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
var bcoin = require('../bcoin');
|
|
var bn = require('bn.js');
|
|
var constants = bcoin.protocol.constants;
|
|
var network = bcoin.protocol.network;
|
|
var utils = require('./utils');
|
|
var assert = utils.assert;
|
|
var BufferWriter = require('./writer');
|
|
var BufferReader = require('./reader');
|
|
var DUMMY_PEER = { sendReject: function() {} };
|
|
|
|
/**
|
|
* Mempool
|
|
*/
|
|
|
|
function Mempool(node, options) {
|
|
if (!(this instanceof Mempool))
|
|
return new Mempool(node, options);
|
|
|
|
EventEmitter.call(this);
|
|
|
|
if (!options)
|
|
options = {};
|
|
|
|
this.options = options;
|
|
this.node = node;
|
|
this.chain = node.chain;
|
|
|
|
this.loaded = false;
|
|
|
|
this.locker = new bcoin.locker(this, this.add, 20 << 20);
|
|
|
|
this.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;
|
|
|
|
Mempool.global = this;
|
|
|
|
this._init();
|
|
}
|
|
|
|
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);
|
|
};
|
|
|
|
Mempool.prototype.purgePending = function purgePending() {
|
|
return this.locker.purgePending();
|
|
};
|
|
|
|
Mempool.prototype._init = function _init() {
|
|
var self = this;
|
|
var unlock = this._lock(utils.nop, []);
|
|
|
|
bcoin.ldb.destroy('mempool', 'memdown', function(err) {
|
|
if (err) {
|
|
unlock();
|
|
return self.emit('error', err);
|
|
}
|
|
|
|
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) {
|
|
if (this.loaded)
|
|
return utils.nextTick(callback);
|
|
|
|
return this.once('open', callback);
|
|
};
|
|
|
|
Mempool.prototype.addBlock = function addBlock(block, callback, force) {
|
|
var self = this;
|
|
var unlock = this._lock(addBlock, [block, callback], force);
|
|
if (!unlock)
|
|
return;
|
|
|
|
callback = utils.wrap(callback, unlock);
|
|
|
|
utils.forEachSerial(block.txs, function(tx, next) {
|
|
self.removeUnchecked(tx, next);
|
|
}, callback);
|
|
};
|
|
|
|
Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
|
|
var self = this;
|
|
var unlock = this._lock(removeBlock, [block, callback], force);
|
|
if (!unlock)
|
|
return;
|
|
|
|
callback = utils.wrap(callback, unlock);
|
|
|
|
utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) {
|
|
self.addUnchecked(tx, next);
|
|
}, callback);
|
|
};
|
|
|
|
Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) {
|
|
var self = this;
|
|
|
|
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)
|
|
hash = hash.hash('hex');
|
|
return this.tx.getTX(hash, callback);
|
|
};
|
|
|
|
Mempool.prototype.getCoin = function getCoin(hash, index, callback) {
|
|
return this.tx.getCoin(hash, index, callback);
|
|
};
|
|
|
|
Mempool.prototype.isSpent = function isSpent(hash, index, callback) {
|
|
return this.tx.isSpent(hash, index, callback);
|
|
};
|
|
|
|
Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, callback) {
|
|
return this.tx.getCoinsByAddress(addresses, callback);
|
|
};
|
|
|
|
Mempool.prototype.getByAddress =
|
|
Mempool.prototype.getTXByAddress = function getTXByAddress(addresses, callback) {
|
|
return this.tx.getTXByAddress(addresses, callback);
|
|
};
|
|
|
|
Mempool.prototype.fillTX = function fillTX(tx, callback) {
|
|
return this.tx.fillTX(tx, callback);
|
|
};
|
|
|
|
Mempool.prototype.fillCoin = function fillCoin(tx, callback) {
|
|
return this.tx.fillCoin(tx, callback);
|
|
};
|
|
|
|
Mempool.prototype.has =
|
|
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, force) {
|
|
var self = this;
|
|
var hash, ts, height, now;
|
|
var ret = {};
|
|
|
|
var unlock = this._lock(addTX, [tx, peer, callback], force);
|
|
if (!unlock)
|
|
return;
|
|
|
|
if (typeof peer === 'function') {
|
|
callback = peer;
|
|
peer = null;
|
|
}
|
|
|
|
if (!peer)
|
|
peer = DUMMY_PEER;
|
|
|
|
hash = tx.hash('hex');
|
|
|
|
assert(tx.ts === 0);
|
|
|
|
callback = utils.wrap(callback, unlock);
|
|
callback = utils.asyncify(callback);
|
|
|
|
if (!this.chain.segwitActive) {
|
|
if (tx.hasWitness())
|
|
return callback(new VerifyError('nonstandard', 'no-witness-yet', 0));
|
|
}
|
|
|
|
if (!this.checkTX(tx, peer))
|
|
return callback(new VerifyError('invalid', 'CheckTransaction failed', -1));
|
|
|
|
if (tx.isCoinbase()) {
|
|
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, 'nonstandard', ret.reason, 0);
|
|
return callback(new VerifyError(ret.reason, 0));
|
|
}
|
|
|
|
this._hasTX(tx, function(err, exists) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
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, '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 (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.code, err.reason, err.score);
|
|
return callback(err);
|
|
}
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
|
|
var self = this;
|
|
self.tx.addUnchecked(tx, function(err) {
|
|
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);
|
|
|
|
self.resolveOrphans(tx, function(err, resolved) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
utils.forEachSerial(resolved, function(tx, next) {
|
|
self.addUnchecked(tx, peer, function(err) {
|
|
if (err)
|
|
self.emit('error', err);
|
|
next();
|
|
}, true);
|
|
}, 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 height = this.chain.height + 1;
|
|
var total, input, coin, i, fee, now, free, minFee;
|
|
var flags = Mempool.flags;
|
|
var mandatory = Mempool.mandatory;
|
|
|
|
if (this.chain.segwitActive) {
|
|
flags |= constants.flags.VERIFY_WITNESS;
|
|
mandatory |= constants.flags.VERIFY_WITNESS;
|
|
}
|
|
|
|
if (this.requireStandard && !tx.isStandardInputs(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++) {
|
|
input = tx.inputs[i];
|
|
coin = input.output;
|
|
|
|
if (coin.coinbase) {
|
|
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(
|
|
'invalid',
|
|
'bad-txns-inputvalues-outofrange',
|
|
100));
|
|
}
|
|
|
|
total.iadd(coin.value);
|
|
}
|
|
|
|
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('invalid', 'bad-txns-in-belowout', 100));
|
|
|
|
fee = total.sub(tx.getOutputValue());
|
|
|
|
if (fee.cmpn(0) < 0)
|
|
return callback(new VerifyError('invalid', 'bad-txns-fee-negative', 100));
|
|
|
|
if (fee.cmp(constants.maxMoney) > 0)
|
|
return callback(new VerifyError('invalid', 'bad-txns-fee-outofrange', 100));
|
|
|
|
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 && free) {
|
|
now = utils.now();
|
|
|
|
if (!this.lastTime)
|
|
this.lastTime = now;
|
|
|
|
this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime);
|
|
this.lastTime = now;
|
|
|
|
if (this.freeCount > this.limitFreeRelay * 10 * 1000) {
|
|
return callback(new VerifyError(
|
|
'insufficientfee',
|
|
'rate limited free transaction',
|
|
0));
|
|
}
|
|
|
|
this.freeCount += tx.getSize();
|
|
}
|
|
|
|
if (this.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0)
|
|
return callback(new VerifyError('highfee', 'absurdly-high-fee', 0));
|
|
|
|
this.countAncestors(tx, function(err, count) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (count > Mempool.ANCESTOR_LIMIT) {
|
|
return callback(new VerifyError(
|
|
'nonstandard',
|
|
'too-long-mempool-chain',
|
|
0));
|
|
}
|
|
|
|
// Do this in the worker pool.
|
|
tx.verifyAsync(null, true, flags, function(err, result) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!result) {
|
|
return tx.verifyAsync(null, true, mandatory, function(err, result) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (result) {
|
|
return callback(new VerifyError(
|
|
'nonstandard',
|
|
'non-mandatory-script-verify-flag',
|
|
0));
|
|
}
|
|
|
|
return callback(new VerifyError(
|
|
'nonstandard',
|
|
'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();
|
|
|
|
inputs[i] += 1;
|
|
|
|
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());
|
|
});
|
|
};
|
|
|
|
Mempool.prototype._hasTX = function hasTX(tx, callback, force) {
|
|
var self = this;
|
|
var hash = tx.hash('hex');
|
|
|
|
this.node.hasTX(hash, function(err, result) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (result)
|
|
return callback(null, result);
|
|
|
|
self.db.get('m/D/' + hash, function(err, tx) {
|
|
if (err && err.type !== 'NotFoundError')
|
|
return callback(err);
|
|
|
|
return callback(null, !!tx);
|
|
});
|
|
});
|
|
};
|
|
|
|
Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
|
|
var self = this;
|
|
var outputs = {};
|
|
var batch = this.db.batch();
|
|
var hash = tx.hash('hex');
|
|
var i, input, p;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
if (!input.output)
|
|
outputs[input.prevout.hash] = true;
|
|
}
|
|
|
|
outputs = Object.keys(outputs);
|
|
|
|
assert(outputs.length > 0);
|
|
|
|
utils.forEachSerial(outputs, function(key, next) {
|
|
self.db.get('m/d/' + key, function(err, buf) {
|
|
if (err && err.type !== 'NotFoundError')
|
|
return next(err);
|
|
|
|
p = new BufferWriter();
|
|
|
|
if (buf)
|
|
p.writeBytes(buf);
|
|
|
|
p.writeHash(hash);
|
|
|
|
batch.put('m/d/' + key, p.render());
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
batch.put('m/D/' + hash, tx.toExtended(true));
|
|
batch.write(callback);
|
|
});
|
|
};
|
|
|
|
Mempool.prototype.getBalance = function getBalance(callback) {
|
|
return this.tx.getBalance(callback);
|
|
};
|
|
|
|
Mempool.prototype.getAll = function getAll(callback) {
|
|
return this.tx.getAll(callback);
|
|
};
|
|
|
|
Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force) {
|
|
var self = this;
|
|
var hash = tx.hash('hex');
|
|
var hashes = [];
|
|
var resolved = [];
|
|
var batch = this.db.batch();
|
|
var p;
|
|
|
|
this.db.get('m/d/' + hash, function(err, buf) {
|
|
if (err && err.type !== 'NotFoundError')
|
|
return callback(err);
|
|
|
|
if (!buf)
|
|
return callback(null, resolved);
|
|
|
|
p = new BufferReader(buf);
|
|
|
|
p.start();
|
|
|
|
try {
|
|
while (p.left())
|
|
hashes.push(p.readHash('hex'));
|
|
} catch (e) {
|
|
return callback(e);
|
|
}
|
|
|
|
p.end();
|
|
|
|
utils.forEachSerial(hashes, function(orphanHash, next, i) {
|
|
self.db.get('m/D/' + orphanHash, function(err, orphan) {
|
|
if (err && err.type !== 'NotFoundError')
|
|
return next(err);
|
|
|
|
if (!orphan)
|
|
return next();
|
|
|
|
try {
|
|
orphan = bcoin.tx.fromExtended(orphan, true);
|
|
} catch (e) {
|
|
return next(e);
|
|
}
|
|
|
|
orphan.inputs.forEach(function(input) {
|
|
if (!input.output && input.prevout.hash === hash)
|
|
input.output = bcoin.coin(tx, input.prevout.index);
|
|
});
|
|
|
|
if (orphan.hasPrevout()) {
|
|
batch.del('m/D/' + orphanHash);
|
|
return self.verify(orphan, function(err) {
|
|
if (err) {
|
|
if (err.type === 'VerifyError')
|
|
return next();
|
|
return next(err);
|
|
}
|
|
resolved.push(orphan);
|
|
return next();
|
|
});
|
|
}
|
|
|
|
batch.put('m/D/' + orphanHash, orphan.toExtended(true));
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
function done(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
return callback(null, resolved);
|
|
}
|
|
|
|
batch.del('m/d/' + hash);
|
|
|
|
return batch.write(done);
|
|
});
|
|
});
|
|
};
|
|
|
|
Mempool.prototype.getSnapshot = function getSnapshot(callback) {
|
|
return this.tx.getAllHashes(callback);
|
|
};
|
|
|
|
Mempool.prototype.checkTX = function checkTX(tx, peer) {
|
|
return Mempool.checkTX(tx, peer);
|
|
};
|
|
|
|
Mempool.checkTX = function checkTX(tx, peer) {
|
|
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, 'invalid', 'bad-txns-vin-empty', 100);
|
|
|
|
if (tx.outputs.length === 0)
|
|
return peer.sendReject(tx, 'invalid', 'bad-txns-vout-empty', 100);
|
|
|
|
if (tx.getVirtualSize() > constants.block.maxSize)
|
|
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, 'invalid', 'bad-txns-vout-negative', 100);
|
|
|
|
if (output.value.cmp(constants.maxMoney) > 0)
|
|
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,
|
|
'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, '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, '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, 'invalid', 'bad-txns-prevout-null', 10);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
utils.inherits(VerifyError, Error);
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = Mempool;
|