hostlist: persist to file.

This commit is contained in:
Christopher Jeffrey 2017-03-01 11:27:44 -08:00
parent ac6d7696a8
commit da69c5d888
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 196 additions and 12 deletions

View File

@ -17,6 +17,7 @@ var murmur3 = require('../utils/murmur3');
var Map = require('../utils/map');
var common = require('./common');
var dns = require('./dns');
var fs = require('fs');
/**
* Host List
@ -60,6 +61,10 @@ function HostList(options) {
this.maxFailures = 10;
this.maxRefs = 8;
this.filename = null;
this.flushInterval = 120000;
this.timer = null;
this._initOptions(options);
this._init();
}
@ -107,6 +112,16 @@ HostList.prototype._initOptions = function initOptions(options) {
assert(Array.isArray(options.nodes));
this.rawNodes = options.nodes;
}
if (options.hostLocation != null) {
assert(typeof options.hostLocation === 'string');
this.filename = options.hostLocation;
}
if (options.flushInterval != null) {
assert(options.flushInterval >= 0);
this.flushInterval = options.flushInterval;
}
};
/**
@ -127,6 +142,114 @@ HostList.prototype._init = function init() {
this.setNodes(this.rawNodes);
};
/**
* Open hostlist and read hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.open = co(function* open() {
try {
yield this.read();
} catch (e) {
if (this.logger) {
this.logger.warning('Hosts deserialization failed.');
this.logger.error(e);
}
}
this.start();
});
/**
* Close hostlist.
* @method
* @returns {Promise}
*/
HostList.prototype.close = co(function* close() {
this.stop();
yield this.flush();
});
/**
* Start flush interval.
*/
HostList.prototype.start = function start() {
if (!this.filename)
return;
assert(!this.timer);
this.timer = setInterval(this.flush.bind(this), this.flushInterval);
};
/**
* Stop flush interval.
*/
HostList.prototype.stop = function stop() {
if (!this.filename)
return;
assert(this.timer != null);
clearInterval(this.timer);
this.timer = null;
};
/**
* Read and initialie from hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.read = co(function* read() {
var data, json;
if (!this.filename)
return;
try {
data = yield readFile(this.filename, 'utf8');
} catch (e) {
if (e.code === 'ENOENT')
return;
throw e;
}
json = JSON.parse(data);
this.fromJSON(json);
});
/**
* Flush addrs to hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.flush = co(function* flush() {
var json, data;
if (!this.filename)
return;
if (this.logger)
this.logger.debug('Writing hosts to %s.', this.filename);
json = this.toJSON();
data = JSON.stringify(json);
try {
yield writeFile(this.filename, data, 'utf8');
} catch (e) {
if (this.logger) {
this.logger.warning('Writing hosts failed.');
this.logger.error(e);
}
}
});
/**
* Get list size.
* @returns {Number}
@ -310,6 +433,8 @@ HostList.prototype.add = function add(addr, src) {
var factor = 1;
var i, entry, bucket;
assert(addr.port !== 0);
entry = this.map[addr.hostname];
if (entry) {
@ -863,6 +988,11 @@ HostList.prototype.toJSON = function toJSON() {
HostList.prototype.fromJSON = function fromJSON(json) {
var sources = {};
var map = {};
var fresh = [];
var totalFresh = 0;
var used = [];
var totalUsed = 0;
var i, j, bucket, keys, key, addr, entry, src;
assert(json && typeof json === 'object');
@ -883,52 +1013,74 @@ HostList.prototype.fromJSON = function fromJSON(json) {
entry.src = src;
this.map[entry.key()] = entry;
map[entry.key()] = entry;
}
assert(Array.isArray(json.fresh));
for (i = 0; i < json.fresh.length; i++) {
keys = json.fresh[i];
bucket = this.fresh[i];
assert(bucket, 'No bucket available.');
bucket = new Map();
for (j = 0; j < keys.length; j++) {
key = keys[j];
entry = this.map[key];
entry = map[key];
assert(entry);
if (entry.refCount === 0)
this.totalFresh++;
totalFresh++;
entry.refCount++;
bucket.set(key, entry);
}
assert(bucket.size <= this.maxEntries,
'Bucket size mismatch.');
fresh.push(bucket);
}
assert(fresh.length === this.fresh.length,
'Buckets mismatch.');
assert(Array.isArray(json.used));
for (i = 0; i < json.used.length; i++) {
keys = json.used[i];
bucket = this.used[i];
assert(bucket, 'No bucket available.');
bucket = new List();
for (j = 0; j < keys.length; j++) {
key = keys[j];
entry = this.map[key];
entry = map[key];
assert(entry);
assert(entry.refCount === 0);
assert(!entry.used);
entry.used = true;
this.totalUsed++;
totalUsed++;
bucket.push(entry);
}
assert(bucket.size <= this.maxEntries,
'Bucket size mismatch.');
used.push(bucket);
}
keys = Object.keys(this.map);
assert(used.length === this.used.length,
'Buckets mismatch.');
keys = Object.keys(map);
for (i = 0; i < keys.length; i++) {
key = keys[i];
entry = this.map[key];
entry = map[key];
assert(entry.used || entry.refCount > 0);
}
this.map = map;
this.fresh = fresh;
this.totalFresh = totalFresh;
this.used = used;
this.totalUsed = totalUsed;
return this;
};
@ -1123,6 +1275,33 @@ HostEntry.fromJSON = function fromJSON(json, network) {
return new HostEntry().fromJSON(json, network);
};
/*
* Helpers
*/
function readFile(filename, enc) {
return new Promise(function(resolve, reject) {
var err;
if (fs.unsupported) {
err = new Error('File not found.');
err.code = 'ENOENT';
return reject(err);
}
fs.readFile(filename, enc, co.wrap(resolve, reject));
});
}
function writeFile(filename, data, enc) {
return new Promise(function(resolve, reject) {
if (fs.unsupported)
return resolve();
fs.writeFile(filename, data, enc, co.wrap(resolve, reject));
});
}
/*
* Expose
*/

View File

@ -212,6 +212,8 @@ Pool.prototype._open = co(function* _open() {
else
yield this.chain.open();
yield this.hosts.open();
this.logger.info('Pool loaded (maxpeers=%d).', this.options.maxOutbound);
if (this.options.bip150) {
@ -259,7 +261,7 @@ Pool.prototype.resetChain = function resetChain() {
Pool.prototype._close = co(function* close() {
yield this.disconnect();
this.hosts.reset();
yield this.hosts.close();
});
/**
@ -1328,6 +1330,9 @@ Pool.prototype.handleAddr = co(function* handleAddr(peer, packet) {
if (addr.ts <= 100000000 || addr.ts > now + 10 * 60)
addr.ts = now - 5 * 24 * 60 * 60;
if (addr.port === 0)
continue;
this.hosts.add(addr, peer.address);
}