mempool: persist to disk.

This commit is contained in:
Christopher Jeffrey 2017-02-28 11:32:32 -08:00
parent 08936c8970
commit 7bd89b35c3
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 334 additions and 33 deletions

View File

@ -46,6 +46,7 @@ limit-free: true
limit-free-relay: 15
reject-absurd-fees: true
replace-by-fee: false
persistent-mempool: false
#
# Pool
@ -89,6 +90,9 @@ known-peers: ./known-peers
coinbase-flags: mined by bcoin
# payout-address: 1111111111111111111114oLvT2,1111111111111111111114oLvT2
max-block-weight: 4000000
reserved-block-weight: 4000
reserved-block-sigops: 400
#
# HTTP

View File

@ -6,13 +6,14 @@
'use strict';
var util = require('../utils/util');
var pad32 = util.pad32;
var layout = {
R: 'R',
e: function e(id, hash) {
return 'e' + pad32(id) + hex(hash);
V: 'V',
e: function e(hash) {
return 'e' + hex(hash);
},
ee: function ee(key) {
return key.slice(1, 65);
}
};

View File

@ -9,17 +9,21 @@
/*
* Database Layout:
* R -> tip hash
* V -> db version
* e[id][hash] -> entry
*/
var layout = {
R: new Buffer([0x52]),
e: function e(id, hash) {
var key = new Buffer(37);
V: new Buffer([0x76]),
e: function e(hash) {
var key = new Buffer(33);
key[0] = 0x65;
key.writeUInt32BE(id, 1, true);
write(key, hash, 5);
write(key, hash, 1);
return key;
},
ee: function ee(key) {
return key.toString('hex', 1, 33);
}
};

View File

@ -24,6 +24,9 @@ var Coin = require('../primitives/coin');
var TXMeta = require('../primitives/txmeta');
var MempoolEntry = require('./mempoolentry');
var Network = require('../protocol/network');
var encoding = require('../utils/encoding');
var layout = require('./layout');
var LDB = require('../db/ldb');
var VerifyError = errors.VerifyError;
var VerifyResult = errors.VerifyResult;
@ -73,11 +76,14 @@ function Mempool(options) {
this.locker = this.chain.locker;
this.cache = new MempoolCache(this.options);
this.size = 0;
this.totalOrphans = 0;
this.totalTX = 0;
this.freeCount = 0;
this.lastTime = 0;
this.lastUpdate = 0;
this.waiting = {};
this.orphans = {};
@ -100,7 +106,20 @@ util.inherits(Mempool, AsyncObject);
Mempool.prototype._open = co(function* open() {
var size = (this.options.maxSize / 1024).toFixed(2);
var i, entries, entry;
yield this.chain.open();
yield this.cache.open();
if (this.options.persistent) {
entries = yield this.cache.load();
for (i = 0; i < entries.length; i++) {
entry = entries[i];
this.trackEntry(entry);
}
}
this.logger.info('Mempool loaded (maxsize=%dkb).', size);
});
@ -110,9 +129,9 @@ Mempool.prototype._open = co(function* open() {
* @returns {Promise}
*/
Mempool.prototype._close = function close() {
return Promise.resolve();
};
Mempool.prototype._close = co(function* close() {
yield this.cache.close();
});
/**
* Notify the mempool that a new block has come
@ -127,7 +146,7 @@ Mempool.prototype._close = function close() {
Mempool.prototype.addBlock = co(function* addBlock(block, txs) {
var unlock = yield this.locker.lock();
try {
return this._addBlock(block, txs);
return yield this._addBlock(block, txs);
} finally {
unlock();
}
@ -142,7 +161,7 @@ Mempool.prototype.addBlock = co(function* addBlock(block, txs) {
* @returns {Promise}
*/
Mempool.prototype._addBlock = function addBlock(block, txs) {
Mempool.prototype._addBlock = co(function* addBlock(block, txs) {
var entries = [];
var i, entry, tx, hash;
@ -172,13 +191,17 @@ Mempool.prototype._addBlock = function addBlock(block, txs) {
// There may be a locktime in a TX that is now valid.
this.rejects.reset();
this.cache.sync(block.hash);
yield this.cache.flush();
if (entries.length === 0)
return;
this.logger.debug(
'Removed %d txs from mempool for block %d.',
entries.length, block.height);
};
});
/**
* Notify the mempool that a block has been disconnected
@ -220,7 +243,7 @@ Mempool.prototype._removeBlock = co(function* removeBlock(block, txs) {
continue;
try {
yield this._addTX(tx);
yield this.insertTX(tx);
total++;
} catch (e) {
this.emit('error', e);
@ -232,6 +255,10 @@ Mempool.prototype._removeBlock = co(function* removeBlock(block, txs) {
this.rejects.reset();
this.cache.sync(block.prevBlock);
yield this.cache.flush();
if (total === 0)
return;
@ -249,7 +276,7 @@ Mempool.prototype._removeBlock = co(function* removeBlock(block, txs) {
Mempool.prototype.reset = co(function* reset() {
var unlock = yield this.locker.lock();
try {
return this._reset();
return yield this._reset();
} finally {
unlock();
}
@ -260,7 +287,7 @@ Mempool.prototype.reset = co(function* reset() {
* @private
*/
Mempool.prototype._reset = function reset() {
Mempool.prototype._reset = co(function* reset() {
this.logger.info('Mempool reset (%d txs removed).', this.totalTX);
this.size = 0;
@ -281,7 +308,12 @@ Mempool.prototype._reset = function reset() {
this.fees.reset();
this.rejects.reset();
};
if (this.options.persistent) {
yield this.cache.wipe();
this.cache.clear();
}
});
/**
* Ensure the size of the mempool stays below 300mb.
@ -620,12 +652,6 @@ Mempool.prototype.addTX = co(function* addTX(tx) {
var unlock = yield this.locker.lock(hash);
try {
return yield this._addTX(tx);
} catch (err) {
if (err.type === 'VerifyError') {
if (!tx.hasWitness() && !err.malleated)
this.rejects.add(tx.hash());
}
throw err;
} finally {
unlock();
}
@ -640,6 +666,33 @@ Mempool.prototype.addTX = co(function* addTX(tx) {
*/
Mempool.prototype._addTX = co(function* _addTX(tx) {
var result;
try {
result = yield this.insertTX(tx);
} catch (err) {
if (err.type === 'VerifyError') {
if (!tx.hasWitness() && !err.malleated)
this.rejects.add(tx.hash());
}
throw err;
}
if (util.now() - this.lastUpdate > 10) {
yield this.cache.flush();
this.lastUpdate = util.now();
}
});
/**
* Add a transaction to the mempool without a lock.
* @method
* @private
* @param {TX} tx
* @returns {Promise}
*/
Mempool.prototype.insertTX = co(function* insertTX(tx) {
var lockFlags = common.lockFlags.STANDARD_LOCKTIME_FLAGS;
var hash = tx.hash('hex');
var ret = new VerifyResult();
@ -987,6 +1040,8 @@ Mempool.prototype.addEntry = co(function* addEntry(entry, view) {
this.logger.debug('Added tx %s to mempool.', tx.txid());
this.cache.save(entry);
yield this.handleOrphans(tx);
});
@ -1017,6 +1072,8 @@ Mempool.prototype.removeEntry = function removeEntry(entry, limit) {
if (this.fees)
this.fees.removeTX(hash);
this.cache.remove(tx.hash());
this.emit('remove entry', entry);
};
@ -1373,7 +1430,7 @@ Mempool.prototype.handleOrphans = co(function* handleOrphans(tx) {
orphan = resolved[i];
try {
yield this._addTX(orphan);
yield this.insertTX(orphan);
} catch (err) {
if (err.type === 'VerifyError') {
this.logger.debug(
@ -1621,8 +1678,10 @@ Mempool.prototype.trackEntry = function trackEntry(entry, view) {
this.spents[key] = entry;
}
if (this.options.indexAddress)
if (this.options.indexAddress) {
assert(view, 'No view passed to trackEntry.');
this.indexEntry(entry, view);
}
this.size += this.memUsage(tx);
this.totalTX++;
@ -1865,6 +1924,15 @@ function MempoolOptions(options) {
this.expiryTime = policy.MEMPOOL_EXPIRY_TIME;
this.minRelay = this.network.minRelay;
this.location = null;
this.db = 'memory';
this.maxFiles = 64;
this.cacheSize = 32 << 20;
this.compression = true;
this.bufferKeys = !util.isBrowser;
this.persistent = false;
this.fromOptions(options);
}
@ -1962,6 +2030,38 @@ MempoolOptions.prototype.fromOptions = function fromOptions(options) {
this.minRelay = options.minRelay;
}
if (options.location != null) {
assert(typeof options.location === 'string');
this.location = options.location;
}
if (options.db != null) {
assert(typeof options.db === 'string');
this.db = options.db;
}
if (options.maxFiles != null) {
assert(util.isNumber(options.maxFiles));
this.maxFiles = options.maxFiles;
}
if (options.cacheSize != null) {
assert(util.isNumber(options.cacheSize));
this.cacheSize = options.cacheSize;
}
if (options.compression != null) {
assert(typeof options.compression === 'boolean');
this.compression = options.compression;
}
if (options.persistent != null) {
assert(typeof options.persistent === 'boolean');
this.persistent = options.persistent;
assert(this.persistent !== this.indexAddress,
'Cannot have persistent mempool with address indexing.');
}
return this;
};
@ -2162,8 +2262,12 @@ CoinIndex.prototype.remove = function remove(outpoint) {
delete this.map[key];
};
/*
* Helpers
/**
* Orphan
* @constructor
* @ignore
* @param {TX} tx
* @param {Hash[]} missing
*/
function Orphan(tx, missing) {
@ -2175,6 +2279,185 @@ Orphan.prototype.toTX = function toTX() {
return TX.fromRaw(this.raw);
};
/**
* Mempool Cache
* @ignore
* @constructor
* @param {Object} options
*/
function MempoolCache(options) {
if (!(this instanceof MempoolCache))
return new MempoolCache(options);
this.logger = options.logger;
this.chain = options.chain;
this.db = null;
this.batch = null;
if (options.persistent)
this.db = LDB(options);
}
MempoolCache.VERSION = 0;
MempoolCache.prototype.getVersion = co(function* getVersion() {
var data = yield this.db.get(layout.V);
if (!data)
return -1;
return data.readUInt32LE(0, true);
});
MempoolCache.prototype.getTip = co(function* getTip() {
var hash = yield this.db.get(layout.R);
if (!hash)
return;
return hash.toString('hex');
});
MempoolCache.prototype.getEntries = function getEntries() {
return this.db.values({
gte: layout.e(encoding.ZERO_HASH),
lte: layout.e(encoding.MAX_HASH),
parse: MempoolEntry.fromRaw
});
};
MempoolCache.prototype.getKeys = function getKeys() {
return this.db.keys({
gte: layout.e(encoding.ZERO_HASH),
lte: layout.e(encoding.MAX_HASH)
});
};
MempoolCache.prototype.open = co(function* open() {
if (!this.db)
return;
yield this.db.open();
this.batch = this.db.batch();
});
MempoolCache.prototype.close = co(function* close() {
if (!this.db)
return;
yield this.db.close();
this.batch = null;
});
MempoolCache.prototype.save = function save(entry) {
if (!this.db)
return;
this.batch.put(layout.e(entry.tx.hash()), entry.toRaw());
};
MempoolCache.prototype.remove = function remove(hash) {
if (!this.db)
return;
this.batch.del(layout.e(hash));
};
MempoolCache.prototype.sync = function sync(hash) {
if (!this.db)
return;
this.batch.put(layout.R, new Buffer(hash, 'hex'));
};
MempoolCache.prototype.clear = function clear() {
this.batch.clear();
this.batch = this.db.batch();
};
MempoolCache.prototype.flush = co(function* flush() {
if (!this.db)
return;
yield this.batch.write();
this.batch = this.db.batch();
});
MempoolCache.prototype.init = co(function* init(hash) {
var batch = this.db.batch();
batch.put(layout.V, encoding.U32(MempoolCache.VERSION));
batch.put(layout.R, new Buffer(hash, 'hex'));
yield batch.write();
});
MempoolCache.prototype.load = co(function* load() {
var version = yield this.getVersion();
var i, tip, entries, entry;
if (version === -1) {
version = MempoolCache.VERSION;
tip = this.chain.tip.hash;
this.logger.info(
'Mempool cache is empty. Writing tip %s.',
util.revHex(tip));
yield this.init(tip);
}
if (version !== MempoolCache.VERSION) {
this.logger.warning(
'Mempool cache version mismatch (%d != %d)!',
version,
MempoolCache.VERSION);
this.logger.warning('Invalidating mempool cache.');
yield this.wipe();
return [];
}
tip = yield this.getTip();
if (tip !== this.chain.tip.hash) {
this.logger.warning(
'Mempool tip not consistent with chain tip (%s != %s)!',
util.revHex(tip),
this.chain.tip.rhash());
this.logger.warning('Invalidating mempool cache.');
yield this.wipe();
return [];
}
entries = yield this.getEntries();
this.logger.info(
'Loaded mempool from disk (%d entries).',
entries.length);
return entries;
});
MempoolCache.prototype.wipe = co(function* wipe() {
var batch = this.db.batch();
var keys = yield this.getKeys();
var i, key;
for (i = 0; i < keys.length; i++) {
key = keys[i];
batch.del(key);
}
batch.put(layout.V, encoding.U32(MempoolCache.VERSION));
batch.put(layout.R, new Buffer(this.chain.tip.hash, 'hex'));
yield batch.write();
this.logger.info('Removed %d mempool entries from disk.', keys.length);
});
/*
* Expose
*/

View File

@ -195,7 +195,7 @@ MempoolEntry.prototype.isFree = function isFree(height) {
*/
MempoolEntry.prototype.getSize = function getSize() {
return this.tx.getSize() + 49;
return this.tx.getSize() + 53;
};
/**
@ -210,7 +210,7 @@ MempoolEntry.prototype.toRaw = function toRaw() {
bw.writeU32(this.size);
bw.writeU32(this.sigops);
bw.writeDouble(this.priority);
bw.writeU32(this.fee);
bw.writeU64(this.fee);
bw.writeU32(this.ts);
bw.writeU64(this.value);
bw.writeU8(this.dependencies ? 1 : 0);
@ -233,7 +233,7 @@ MempoolEntry.prototype.fromRaw = function fromRaw(data) {
this.size = br.readU32();
this.sigops = br.readU32();
this.priority = br.readDouble();
this.fee = br.readU32();
this.fee = br.readU64();
this.ts = br.readU32();
this.value = br.readU64();
this.dependencies = br.readU8() === 1;

View File

@ -256,6 +256,7 @@ config.toOptions = function toOptions(data) {
options.requireStandard = bool(data.requirestandard);
options.rejectAbsurdFees = bool(data.rejectabsurdfees);
options.replaceByFee = bool(data.replacebyfee);
options.persistentMempool = bool(data.persistentmempool);
// Pool
options.selfish = bool(data.selfish);
@ -282,6 +283,8 @@ config.toOptions = function toOptions(data) {
options.payoutAddress = list(data.payoutaddress);
options.coinbaseFlags = str(data.coinbaseflags);
options.maxBlockWeight = num(data.maxblockweight);
options.reservedBlockWeight = num(data.reservedblockweight);
options.reservedBlockSigops = num(data.reservedblocksigops);
// HTTP
options.sslCert = file(data.sslcert, prefix);

View File

@ -75,6 +75,9 @@ function FullNode(options) {
logger: this.logger,
chain: this.chain,
fees: this.fees,
db: this.options.db,
location: this.location('mempool'),
persistent: this.options.persistentMempool,
maxSize: this.options.mempoolSize,
limitFree: this.options.limitFree,
limitFreeRelay: this.options.limitFreeRelay,
@ -120,7 +123,9 @@ function FullNode(options) {
fees: this.fees,
address: this.options.payoutAddress,
coinbaseFlags: this.options.coinbaseFlags,
maxWeight: this.options.maxBlockWeight
maxWeight: this.options.maxBlockWeight,
reservedWeight: this.options.reservedBlockWeight,
reservedSigops: this.options.reservedBlockSigops
});
// Wallet database needs access to fees.

View File

@ -76,6 +76,7 @@
"./lib/http/rpcclient": "./browser/empty.js",
"./lib/http/server": "./browser/empty.js",
"./lib/http/wallet": "./browser/empty.js",
"./lib/mempool/layout": "./lib/mempool/layout-browser.js",
"./lib/net/dns": "./lib/net/dns-browser.js",
"./lib/net/tcp": "./lib/net/tcp-browser.js",
"./lib/utils/native": "./browser/empty.js",