diff --git a/lib/bcoin.js b/lib/bcoin.js index 7bd96408..c4206f26 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -43,6 +43,7 @@ bcoin.ecdsa.signature = require('elliptic/lib/elliptic/ec/signature'); bcoin.ecdsa.keypair = require('elliptic/lib/elliptic/ec/key'); bcoin.utils = require('./bcoin/utils'); +bcoin.lru = require('./bcoin/lru'); bcoin.protocol = require('./bcoin/protocol'); bcoin.bloom = require('./bcoin/bloom'); bcoin.script = require('./bcoin/script'); diff --git a/lib/bcoin/blockdb.js b/lib/bcoin/blockdb.js index 0df7f03e..120bf8aa 100644 --- a/lib/bcoin/blockdb.js +++ b/lib/bcoin/blockdb.js @@ -38,6 +38,8 @@ function BlockDB(options) { this.parser = new bcoin.protocol.parser(); this.data = new BlockData(); + this.unspentCache = new bcoin.lru(32 * 1024 * 1024); + this.txCache = new bcoin.lru(32 * 1024 * 1024); this.index = new levelup(this.file, { keyEncoding: 'ascii', @@ -414,6 +416,15 @@ BlockDB.prototype._getCoinsByAddress = function _getCoinsByAddress(address, call var index = +parts[1]; var record = self.parseOffset(data.value); pending++; + if (self.unspentCache.has(hash + '/' + index)) { + coins.push(self.unspentCache.get(hash + '/' + index)); + pending--; + if (done) { + if (!pending) + return callback(null, coins); + } + return; + } self.data.getAsync(record.size, record.offset, function(err, data) { var coin; @@ -435,6 +446,7 @@ BlockDB.prototype._getCoinsByAddress = function _getCoinsByAddress(address, call value: data.value, spent: false }); + self.unspentCache.set(hash + '/' + index, coin); coins.push(coin); } @@ -543,14 +555,24 @@ BlockDB.prototype._getTXByAddress = function _getTXByAddress(address, callback) }); stream.on('data', function(data) { - // var parts = data.key.split('/').slice(3); - // var hash = parts[0]; + var parts = data.key.split('/').slice(3); + var hash = parts[0]; // Could store block hash in key // var blockHash = parts[1]; var record = self.parseOffset(data.value); pending++; + if (self.txCache.has(hash)) { + coins.push(self.txCache.get(hash)); + pending--; + if (done) { + if (!pending) + return callback(null, coins); + } + return; + } + self.data.getAsync(record.size, record.offset, function(err, data) { var tx, entry; @@ -568,6 +590,7 @@ BlockDB.prototype._getTXByAddress = function _getTXByAddress(address, callback) tx.height = record.height; tx.ts = entry.ts; tx.block = entry.hash; + self.txCache.set(hash, tx); txs.push(tx); } diff --git a/lib/bcoin/coin.js b/lib/bcoin/coin.js index e2102e0c..0d4cb0e0 100644 --- a/lib/bcoin/coin.js +++ b/lib/bcoin/coin.js @@ -74,6 +74,10 @@ Coin.prototype.__defineGetter__('chain', function() { return this._chain || bcoin.chain.global; }); +Coin.prototype.getSize = function getSize() { + return 4 + 4 + 8 + bcoin.script.getSize(this.script) + 32 + 4 + 1; +}; + Coin.prototype.getConfirmations = function getConfirmations(height) { var top; diff --git a/lib/bcoin/lru.js b/lib/bcoin/lru.js new file mode 100644 index 00000000..118cf83c --- /dev/null +++ b/lib/bcoin/lru.js @@ -0,0 +1,157 @@ +/** + * lru.js - LRU cache for bcoin + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * https://github.com/indutny/bcoin + */ + +function LRU(maxSize) { + if (!(this instanceof LRU)) + return new LRU(maxSize); + + this.data = {}; + this.size = 0; + this.maxSize = maxSize; + this.head = null; + this.tail = null; +} + +LRU.prototype._getSize = function _getSize(value) { + if (value == null) + return 1; + + if (typeof value === 'number') + return 4; + + if (value._raw) + return value._raw.length; + + if (value.getSize) + return value.getSize(); + + if (typeof value.length === 'number') + return value.length; + + return 1; +}; + +LRU.prototype._compact = function _compact() { + var item; + + if (this.size < this.maxSize) + return; + + for (item = this.head; item; item = item.next) { + if (this.size <= this.maxSize / 2 | 0) + break; + this.size -= this._getSize(item.value); + delete this.data[item.key]; + } + + if (!item) { + this.head = null; + this.tail = null; + return; + } + + this.head = item; + item.prev = null; +}; + +LRU.prototype.set = function set(key, value) { + var item = this.data[key]; + if (item) { + this.size -= this._getSize(item.value); + this.size += this._getSize(value); + item.value = value; + this.get(key); + this._compact(); + return; + } + + item = { key: key, value: value }; + + this.data[key] = item; + + if (!this.head) { + this.head = item; + this.tail = item; + } else { + this.tail.next = item; + item.prev = this.tail; + this.tail = item; + } + + this.size += this._getSize(value); + + this._compact(); +}; + +LRU.prototype.get = function get(key) { + var item = this.data[key]; + var prev, next, tail; + + if (!item) + return; + + if (this.tail === item) + return item.value; + + prev = item.prev; + next = item.next; + tail = this.tail; + + this.tail = item; + + if (this.head === item) + this.head = next || item; + + if (prev) + prev.next = next; + + if (next) + next.prev = prev; + + item.next = null; + item.prev = tail; + + return item.value; +}; + +LRU.prototype.has = function get(key) { + return this.data[key] != null; +}; + +LRU.prototype.remove = function remove(key) { + var item = this.data[key]; + var prev, next; + + if (!item) + return false; + + this.size -= this._getSize(item.value); + + delete this.data[key]; + + prev = item.prev; + next = item.next; + + if (prev) + prev.next = next; + + if (next) + next.prev = prev; + + if (this.tail === item) + this.tail = prev || null; + + if (this.head === item) + this.head = next || null; + + return true; +}; + +/** + * Expose + */ + +module.exports = LRU;