fcoin/lib/bcoin/mempool.js
Christopher Jeffrey 9fc6848dce reject packets.
2016-02-09 01:29:23 -08:00

301 lines
6.6 KiB
JavaScript

/**
* mempool.js - mempool for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var inherits = require('inherits');
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 = bcoin.utils;
var assert = utils.assert;
var fs = bcoin.fs;
/**
* Mempool
*/
function Mempool(pool, options) {
if (!(this instanceof Mempool))
return new Mempool(pool, options);
if (!options)
options = {};
this.options = options;
this.pool = pool;
this.storage = bcoin.db;
this.txs = {};
this.prevout = {};
this.size = 0;
this.count = 0;
this.locked = false;
this._init();
}
Mempool.prototype._init = function _init() {
var self = this;
// Remove now-mined transactions
this.pool.on('block', function(block) {
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');
self.remove(mtx);
});
});
};
Mempool.prototype.get = function get(hash) {
if (hash instanceof bcoin.tx)
hash = hash.hash('hex');
return this.txs[hash];
};
Mempool.prototype.getAll = function getAll(hash) {
return Object.keys(this.txs).map(function(key) {
return this.txs[key];
}, this);
};
Mempool.prototype.has = function has(hash) {
return !!this.get(hash);
};
Mempool.prototype.add = function add(tx, peer, callback) {
var self = this;
var hash = tx.hash('hex');
assert(tx.ts === 0);
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.'));
this._lockTX(tx);
this.storage.fillTX(tx, function(err) {
var i, input, dup, height, ts, priority;
self._unlockTX(tx);
if (err)
return callback(err);
if (!tx.hasPrevout()) {
peer.reject({
data: tx.hash(),
reason: 'no-prevout'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('Previous outputs not found.'));
}
if (!tx.isStandard()) {
peer.reject({
data: tx.hash(),
reason: 'non-standard'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX is not standard.'));
}
if (!tx.isStandardInputs()) {
peer.reject({
data: tx.hash(),
reason: 'non-standard-inputs'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX inputs are not standard.'));
}
if (tx.getOutputValue().cmp(tx.getInputValue()) > 0) {
peer.reject({
data: tx.hash(),
reason: 'nonexistent-coins'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX is spending coins that it does not have.'));
}
height = self.pool.chain.height() + 1;
ts = utils.now();
if (!tx.isFinal(height, ts)) {
peer.reject({
data: tx.hash(),
reason: 'not-final'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX is not final.'));
}
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (input.output.spent) {
peer.reject({
data: tx.hash(),
reason: 'old-outputs'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX is spending old outputs.'));
}
dup = self.prevout[input.prevout.hash];
if (dup) {
// Replace-by-fee
if (input.sequence === 0xffffffff - 1) {
if (dup.getFee().cmp(tx.getFee()) < 0) {
self.remove(dup);
continue;
}
}
peer.reject({
data: tx.hash(),
reason: 'double-spend'
});
pool.setMisbehavior(peer, 100);
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) {
peer.reject({
data: tx.hash(),
reason: 'negative-value'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX is spending negative coins.'));
}
}
if (!tx.verify(true)) {
peer.reject({
data: tx.hash(),
reason: 'script-failed'
});
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.prevout[input.prevout.hash] = tx;
}
// Possibly do something bitcoinxt-like here with priority
priority = tx.getPriority();
self.txs[hash] = tx;
self.count++;
self.size += tx.getSize();
self.storage.saveMempoolTX(tx, function(err) {
if (err)
return callback(err);
return callback();
});
});
};
// Lock a tx to prevent race conditions
Mempool.prototype._lockTX = function _lockTX(tx) {
var hash = tx.hash('hex');
var i, input;
if (!this.txs[hash])
this.txs[hash] = tx;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (!this.prevout[input.prevout.hash])
this.prevout[input.prevout.hash] = tx;
}
};
Mempool.prototype._unlockTX = function _unlockTX(tx) {
var hash = tx.hash('hex');
var i, input;
if (this.txs[hash] === tx)
delete this.txs[hash];
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (this.prevout[input.prevout.hash] === tx)
delete this.prevout[input.prevout.hash];
}
};
Mempool.prototype.remove = function remove(hash, callback) {
var self = this;
var tx, input;
callback = utils.asyncify(callback);
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];
if (this.prevout[input.prevout.hash] === tx)
delete this.prevout[input.prevout.hash];
}
delete this.txs[hash];
this.count--;
this.size -= tx.getSize();
this.storage.removeMempoolTX(tx, function(err) {
if (err)
return callback(err);
return callback();
});
};
// 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;
};
/**
* Expose
*/
module.exports = Mempool;