diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 3469e310..f1b5615d 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -99,7 +99,6 @@ Chain.msg = function msg(code) { Chain.prototype._init = function _init() { var self = this; - var s; this.loading = true; @@ -114,6 +113,7 @@ Chain.prototype._init = function _init() { self.loading = false; self.emit('load'); + utils.debug('Chain successfully loaded.'); }); }; @@ -153,10 +153,10 @@ Chain.prototype._addIndex = function _addIndex(entry, save) { this.index.heights[entry.hash] = entry.height; this.index.count++; - if (!this.tip || entry.height > this.tip.height) + if (!this.tip || entry.height > this.tip.height) { this.tip = entry; - - this.emit('tip', this.tip); + this.emit('tip', this.tip); + } return Chain.codes.okay; }; @@ -180,6 +180,7 @@ Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) { Chain.prototype.resetHeight = function resetHeight(height) { var self = this; + var i, existing; assert(height < this.index.count); @@ -191,10 +192,11 @@ Chain.prototype.resetHeight = function resetHeight(height) { this.orphan.count = 0; this.orphan.size = 0; - for (var i = height + 1; height < this.index.count; i++) { - var existing = this.db.get(i); - this.db.del(i); + for (i = height + 1; height < this.index.count; i++) { + existing = this.db.get(i); + assert(existing); delete this.index.heights[existing.hash]; + this.db.del(i); } this.tip = this.db.get(height); @@ -493,8 +495,7 @@ Chain.prototype.getTip = function getTip() { }; Chain.prototype.isFull = function isFull() { - var last = this.tip.ts; - var delta = utils.now() - last; + var delta = utils.now() - this.tip.ts; return delta < 40 * 60; }; @@ -702,46 +703,73 @@ Chain.prototype.fromJSON = function fromJSON(json) { var BLOCK_SIZE = 112; -function ChainDB(chain) { - var exists; +function ChainDB(chain, options) { + if (!(this instanceof ChainDB)) + return new ChainDB(chain); + if (!options) + options = {}; + + this.options = options; this.chain = chain; - this.file = process.env.HOME + '/bcoin-' + network.type + '.blockchain'; + this.file = options.file; + + if (!this.file) + this.file = process.env.HOME + '/bcoin-' + network.type + '.blockchain'; this._queue = []; this._cache = {}; this._bufferPool = {}; this._nullBlock = new Buffer(BLOCK_SIZE); this._nullBlock.fill(0); + this.tip = -1; + this.size = 0; + this.fd = null; + // Need to cache up to the retarget interval + // if we're going to be checking the damn + // target all the time. + if (network.powAllowMinDifficultyBlocks) + this._cacheWindow = network.powDiffInterval + 1; + else + this._cacheWindow = network.block.majorityWindow + 1; + + this._init(); +} + +ChainDB.prototype._init = function _init() { try { fs.unlinkSync(this.file); } catch (e) { ; } - try { - fs.accessSync(file); - exists = true; - } catch (e) { - exists = false; - } - - if (!exists) { + if (!this.exists()) { fs.writeFileSync(this.file, new Buffer(0)); fs.truncateSync(this.file, 0); } + this.size = this.getSize(); this.fd = fs.openSync(this.file, 'r+'); -} +}; ChainDB.prototype.getBuffer = function(size) { if (!this._bufferPool[size]) this._bufferPool[size] = new Buffer(size); + return this._bufferPool[size]; }; -ChainDB.prototype.size = function size() { +ChainDB.prototype.exists = function exists() { + try { + fs.statSync(file); + return true; + } catch (e) { + return false; + } +}; + +ChainDB.prototype.getSize = function getSize() { try { return fs.statSync(this.file).size; } catch (e) { @@ -750,11 +778,20 @@ ChainDB.prototype.size = function size() { }; ChainDB.prototype.count = function count() { - return this.size() / BLOCK_SIZE | 0; + return this.size / BLOCK_SIZE | 0; +}; + +ChainDB.prototype.cache = function cache(entry) { + if (entry.height > this.tip) { + this.tip = entry.height; + delete this._cache[entry.height - this._cacheWindow]; + this._cache[entry.height] = entry; + assert(Object.keys(this._cache).length <= this._cacheWindow); + } }; ChainDB.prototype.get = function get(height) { - var data; + var data, entry; if (this._cache[height]) return this._cache[height]; @@ -762,25 +799,37 @@ ChainDB.prototype.get = function get(height) { if (this._queue[height]) return this._queue[height]; + if (height < 0 || height == null) + return; + + if ((height + 1) * BLOCK_SIZE > this.size) + return; + data = this._read(BLOCK_SIZE, height * BLOCK_SIZE); if (!data) return; + // Ignore if it is a null block. if (utils.read32(data, 0) === 0) return; - return ChainBlock.fromRaw(this.chain, height, data); + entry = ChainBlock.fromRaw(this.chain, height, data); + + // Cache the past 1001 blocks in memory + // (necessary for isSuperMajority) + this.cache(entry); + + return entry; }; ChainDB.prototype.save = function save(entry, callback) { var self = this; var raw, offset; - // Cache the past 2016 blocks in memory - this._cache[entry.height] = entry; - delete this._cache[entry.height - network.powDiffInterval]; - assert(Object.keys(this._cache).length < network.powDiffInterval + 1); + // Cache the past 1001 blocks in memory + // (necessary for isSuperMajority) + this.cache(entry); // Something is already writing. Cancel it // and synchronously write the data after @@ -834,10 +883,12 @@ ChainDB.prototype.del = function del(height) { // If we deleted several blocks at the end, go back // to the last non-null block and truncate the file // beyond that point. - if ((height + 1) * BLOCK_SIZE === this.size()) { + if ((height + 1) * BLOCK_SIZE === this.size) { while (this.isNull(height)) height--; fs.ftruncateSync(this.fd, (height + 1) * BLOCK_SIZE); + this.size = (height + 1) * BLOCK_SIZE; + this.tip = height; } return true; @@ -888,8 +939,10 @@ ChainDB.prototype._write = function _write(data, offset, callback) { size -= bytes; offset += bytes; - if (index === data.length) + if (index === data.length) { + self.size += data.length; return callback(null, true); + } callee(); }); @@ -908,8 +961,10 @@ ChainDB.prototype._writeSync = function _writeSync(data, offset) { index += bytes; size -= bytes; offset += bytes; - if (index === data.length) + if (index === data.length) { + self.size += data.length; return true; + } } return false; @@ -1024,6 +1079,7 @@ ChainBlock.prototype.toRaw = function toRaw() { ChainBlock.fromRaw = function fromRaw(chain, height, p) { return new ChainBlock(chain, { + height: height, hash: utils.toHex(utils.dsha256(p.slice(0, 80))), version: utils.read32(p, 0), prevBlock: utils.toHex(p.slice(4, 36)), @@ -1031,7 +1087,6 @@ ChainBlock.fromRaw = function fromRaw(chain, height, p) { ts: utils.readU32(p, 68), bits: utils.readU32(p, 72), nonce: utils.readU32(p, 76), - height: height, chainwork: new bn(p.slice(80, 112), 'be') }); }; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index e4a86f9a..b9bb452e 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -1577,6 +1577,7 @@ Pool.prototype.usableSeed = function usableSeed(priority, connecting) { var i, addr; var original = this.originalSeeds; var seeds = this.seeds; + var all = original.concat(seeds); var tries = this.peers.tries.regular; if (priority) @@ -1653,8 +1654,8 @@ Pool.prototype.usableSeed = function usableSeed(priority, connecting) { // If we have no block peers, always return // an address. if (!priority) { - if (this.peers.pending.length + this.peers.block.length === 0) - return original[Math.random() * (original.length - 1) | 0]; + if (all.length === 1) + return all[Math.random() * (all.length - 1) | 0]; } // This should never happen: priority sockets