bitcoind: add lru caching for results
This commit is contained in:
parent
7e70bbfa7d
commit
b69d848352
|
@ -9,6 +9,7 @@ var bitcore = require('bitcore-lib');
|
||||||
var Address = bitcore.Address;
|
var Address = bitcore.Address;
|
||||||
var zmq = require('zmq');
|
var zmq = require('zmq');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
var LRU = require('lru-cache');
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
var BitcoinRPC = require('bitcoind-rpc');
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
|
@ -33,6 +34,18 @@ function Bitcoin(options) {
|
||||||
this._reindex = false;
|
this._reindex = false;
|
||||||
this._reindexWait = 1000;
|
this._reindexWait = 1000;
|
||||||
Service.call(this, options);
|
Service.call(this, options);
|
||||||
|
|
||||||
|
// caches valid until there is a new block
|
||||||
|
this.txidsCache = LRU(50000);
|
||||||
|
this.balanceCache = LRU(50000);
|
||||||
|
this.summaryCache = LRU(50000);
|
||||||
|
|
||||||
|
// caches valid indefinetly
|
||||||
|
this.transactionCache = LRU(100000);
|
||||||
|
this.transactionInfoCache = LRU(100000);
|
||||||
|
this.blockCache = LRU(144);
|
||||||
|
this.blockHeaderCache = LRU(288);
|
||||||
|
|
||||||
$.checkState(this.node.datadir, 'Node is missing datadir property');
|
$.checkState(this.node.datadir, 'Node is missing datadir property');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +145,12 @@ Bitcoin.prototype._loadConfiguration = function() {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Bitcoin.prototype._resetCaches = function() {
|
||||||
|
this.txidsCache.reset();
|
||||||
|
this.balanceCache.reset();
|
||||||
|
this.summaryCache.reset();
|
||||||
|
};
|
||||||
|
|
||||||
Bitcoin.prototype._registerEventHandlers = function() {
|
Bitcoin.prototype._registerEventHandlers = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -143,6 +162,7 @@ Bitcoin.prototype._registerEventHandlers = function() {
|
||||||
if (topicString === 'hashtx') {
|
if (topicString === 'hashtx') {
|
||||||
self.emit('tx', message.toString('hex'));
|
self.emit('tx', message.toString('hex'));
|
||||||
} else if (topicString === 'hashblock') {
|
} else if (topicString === 'hashblock') {
|
||||||
|
self._resetCaches();
|
||||||
self.tiphash = message.toString('hex');
|
self.tiphash = message.toString('hex');
|
||||||
self.client.getBlock(self.tiphash, function(err, response) {
|
self.client.getBlock(self.tiphash, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -327,17 +347,26 @@ Bitcoin.prototype.syncPercentage = function(callback) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.prototype.getAddressBalance = function(addressArg, options, callback) {
|
Bitcoin.prototype.getAddressBalance = function(addressArg, options, callback) {
|
||||||
// TODO keep a cache and update the cache by a range of block heights
|
var self = this;
|
||||||
var addresses = [addressArg];
|
var addresses = [addressArg];
|
||||||
if (Array.isArray(addressArg)) {
|
if (Array.isArray(addressArg)) {
|
||||||
addresses = addressArg;
|
addresses = addressArg;
|
||||||
}
|
}
|
||||||
|
var cacheKey = addresses.join('');
|
||||||
|
var balance = self.balanceCache.get(cacheKey);
|
||||||
|
if (balance) {
|
||||||
|
return setImmediate(function() {
|
||||||
|
callback(null, balance);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
this.client.getAddressBalance({addresses: addresses}, function(err, response) {
|
this.client.getAddressBalance({addresses: addresses}, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
self.balanceCache.set(cacheKey, response.result);
|
||||||
callback(null, response.result);
|
callback(null, response.result);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.prototype.getAddressUnspentOutputs = function() {
|
Bitcoin.prototype.getAddressUnspentOutputs = function() {
|
||||||
|
@ -345,17 +374,27 @@ Bitcoin.prototype.getAddressUnspentOutputs = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.prototype.getAddressTxids = function(addressArg, options, callback) {
|
Bitcoin.prototype.getAddressTxids = function(addressArg, options, callback) {
|
||||||
// TODO Keep a cache updated for queries
|
var self = this;
|
||||||
var addresses = [addressArg];
|
var addresses = [addressArg];
|
||||||
if (Array.isArray(addressArg)) {
|
if (Array.isArray(addressArg)) {
|
||||||
addresses = addressArg;
|
addresses = addressArg;
|
||||||
}
|
}
|
||||||
this.client.getAddressTxids({addresses: addresses}, function(err, response) {
|
var cacheKey = addresses.join('');
|
||||||
|
var txids = self.txidsCache.get(cacheKey);
|
||||||
|
if (txids) {
|
||||||
|
return setImmediate(function() {
|
||||||
|
callback(null, txids);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.client.getAddressTxids({addresses: addresses}, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
response.result.reverse();
|
||||||
|
self.txidsCache.set(cacheKey, response.result);
|
||||||
return callback(null, response.result);
|
return callback(null, response.result);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.prototype._getConfirmationsDetail = function(transaction) {
|
Bitcoin.prototype._getConfirmationsDetail = function(transaction) {
|
||||||
|
@ -475,6 +514,19 @@ Bitcoin.prototype._getAddressStrings = function(addresses) {
|
||||||
return addressStrings;
|
return addressStrings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Bitcoin.prototype._paginateTxids = function(fullTxids, from, to) {
|
||||||
|
var totalCount = fullTxids.length;
|
||||||
|
var txids;
|
||||||
|
if (from >= 0 && to >= 0) {
|
||||||
|
var fromOffset = totalCount - from;
|
||||||
|
var toOffset = totalCount - to;
|
||||||
|
txids = fullTxids.slice(toOffset, fromOffset);
|
||||||
|
} else {
|
||||||
|
txids = fullTxids;
|
||||||
|
}
|
||||||
|
return txids;
|
||||||
|
};
|
||||||
|
|
||||||
Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) {
|
Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var addresses = [addressArg];
|
var addresses = [addressArg];
|
||||||
|
@ -489,6 +541,10 @@ Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalCount = txids.length;
|
||||||
|
txids = self._paginateTxids(txids, options.from, options.to);
|
||||||
|
|
||||||
async.mapSeries(
|
async.mapSeries(
|
||||||
txids,
|
txids,
|
||||||
function(txid, next) {
|
function(txid, next) {
|
||||||
|
@ -502,7 +558,7 @@ Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
callback(null, {
|
callback(null, {
|
||||||
totalCount: txids.length,
|
totalCount: totalCount,
|
||||||
items: transactions
|
items: transactions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -514,11 +570,31 @@ Bitcoin.prototype.getAddressSummary = function(addressArg, options, callback) {
|
||||||
// TODO: optional mempool
|
// TODO: optional mempool
|
||||||
var self = this;
|
var self = this;
|
||||||
var summary = {};
|
var summary = {};
|
||||||
|
var summaryTxids = [];
|
||||||
|
|
||||||
|
var addresses = [addressArg];
|
||||||
|
if (Array.isArray(addressArg)) {
|
||||||
|
addresses = addressArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheKey = addresses.join('');
|
||||||
|
|
||||||
if (_.isUndefined(options.queryMempool)) {
|
if (_.isUndefined(options.queryMempool)) {
|
||||||
options.queryMempool = true;
|
options.queryMempool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function querySummary() {
|
||||||
|
async.parallel([
|
||||||
|
function getTxList(done) {
|
||||||
|
self.getAddressTxids(addressArg, options, function(err, txids) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
summaryTxids = txids;
|
||||||
|
summary.appearances = txids.length;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
function getBalance(done) {
|
function getBalance(done) {
|
||||||
self.getAddressBalance(addressArg, options, function(err, data) {
|
self.getAddressBalance(addressArg, options, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -530,32 +606,29 @@ Bitcoin.prototype.getAddressSummary = function(addressArg, options, callback) {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
], function(err) {
|
||||||
function getTxList(done) {
|
|
||||||
self.getAddressTxids(addressArg, options, function(err, txids) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
summary.txids = txids;
|
|
||||||
summary.appearances = txids.length;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var tasks = [];
|
|
||||||
if (!options.noBalance) {
|
|
||||||
tasks.push(getBalance);
|
|
||||||
}
|
|
||||||
if (!options.noTxList) {
|
|
||||||
tasks.push(getTxList);
|
|
||||||
}
|
|
||||||
|
|
||||||
async.parallel(tasks, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
self.summaryCache.set(cacheKey, summary);
|
||||||
|
if (!options.noTxList) {
|
||||||
|
summary.txids = summaryTxids;
|
||||||
|
}
|
||||||
callback(null, summary);
|
callback(null, summary);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.noTxList) {
|
||||||
|
var summaryCache = self.summaryCache.get(cacheKey);
|
||||||
|
if (summaryCache) {
|
||||||
|
callback(null, summaryCache);
|
||||||
|
} else {
|
||||||
|
querySummary();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
querySummary();
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -564,29 +637,36 @@ Bitcoin.prototype.getAddressSummary = function(addressArg, options, callback) {
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getBlock = function(block, callback) {
|
Bitcoin.prototype.getBlock = function(block, callback) {
|
||||||
// TODO apply performance patch to the RPC method for raw data
|
// TODO apply performance patch to the RPC method for raw data
|
||||||
// TODO keep a cache of results
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
function queryHeader(blockhash) {
|
function queryBlock(blockhash) {
|
||||||
self.client.getBlock(blockhash, false, function(err, response) {
|
self.client.getBlock(blockhash, false, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
var block = bitcore.Block.fromString(response.result);
|
var blockCache = bitcore.Block.fromString(response.result);
|
||||||
callback(null, block);
|
self.blockCache.set(block, blockCache);
|
||||||
|
callback(null, blockCache);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cachedBlock = self.blockCache.get(block);
|
||||||
|
if (cachedBlock) {
|
||||||
|
return setImmediate(function() {
|
||||||
|
callback(null, cachedBlock);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
if (_.isNumber(block)) {
|
if (_.isNumber(block)) {
|
||||||
self.client.getBlockHash(block, function(err, response) {
|
self.client.getBlockHash(block, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
var blockhash = response.result;
|
var blockhash = response.result;
|
||||||
queryHeader(blockhash);
|
queryBlock(blockhash);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
queryHeader(block);
|
queryBlock(block);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -614,7 +694,6 @@ Bitcoin.prototype.getBlockHashesByTimestamp = function(high, low, callback) {
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getBlockHeader = function(block, callback) {
|
Bitcoin.prototype.getBlockHeader = function(block, callback) {
|
||||||
// TODO keep a cache of queries
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
function queryHeader(blockhash) {
|
function queryHeader(blockhash) {
|
||||||
|
@ -683,8 +762,14 @@ Bitcoin.prototype.sendTransaction = function(tx, allowAbsurdFees, callback) {
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
|
Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
|
||||||
// TODO keep an LRU cache available of transactions
|
var self = this;
|
||||||
this.client.getRawTransaction(txid, function(err, response) {
|
var tx = self.transactionCache.get(txid);
|
||||||
|
if (tx) {
|
||||||
|
return setImmediate(function() {
|
||||||
|
callback(null, tx);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.client.getRawTransaction(txid, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
@ -693,8 +778,10 @@ Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
|
||||||
}
|
}
|
||||||
var tx = Transaction();
|
var tx = Transaction();
|
||||||
tx.fromString(response.result);
|
tx.fromString(response.result);
|
||||||
|
self.transactionCache.set(txid, tx);
|
||||||
callback(null, tx);
|
callback(null, tx);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -710,9 +797,14 @@ Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
|
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
|
||||||
// TODO keep an LRU cache available of transactions
|
var self = this;
|
||||||
// TODO get information from txindex as an RPC method
|
var tx = self.transactionInfoCache.get(txid);
|
||||||
this.client.getRawTransaction(txid, 1, function(err, response) {
|
if (tx) {
|
||||||
|
return setImmediate(function() {
|
||||||
|
callback(null, tx);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.client.getRawTransaction(txid, 1, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
@ -724,8 +816,10 @@ Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, cal
|
||||||
tx.__blockHash = response.result.blockhash;
|
tx.__blockHash = response.result.blockhash;
|
||||||
tx.__height = response.result.height;
|
tx.__height = response.result.height;
|
||||||
tx.__timestamp = response.result.time;
|
tx.__timestamp = response.result.time;
|
||||||
|
self.transactionInfoCache.set(txid, tx);
|
||||||
callback(null, tx);
|
callback(null, tx);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -733,7 +827,6 @@ Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, cal
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
Bitcoin.prototype.getBestBlockHash = function(callback) {
|
Bitcoin.prototype.getBestBlockHash = function(callback) {
|
||||||
// TODO keep an LRU cache available of transactions
|
|
||||||
this.client.getBestBlockHash(function(err, response) {
|
this.client.getBestBlockHash(function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
|
@ -41,14 +41,15 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^1.3.0",
|
"async": "^1.3.0",
|
||||||
"bindings": "^1.2.1",
|
"bindings": "^1.2.1",
|
||||||
"bitcore-lib": "^0.13.13",
|
|
||||||
"bitcoind-rpc": "^0.3.0",
|
"bitcoind-rpc": "^0.3.0",
|
||||||
|
"bitcore-lib": "^0.13.13",
|
||||||
"body-parser": "^1.13.3",
|
"body-parser": "^1.13.3",
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"commander": "^2.8.1",
|
"commander": "^2.8.1",
|
||||||
"errno": "^0.1.4",
|
"errno": "^0.1.4",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"liftoff": "^2.2.0",
|
"liftoff": "^2.2.0",
|
||||||
|
"lru-cache": "^4.0.1",
|
||||||
"memdown": "^1.0.0",
|
"memdown": "^1.0.0",
|
||||||
"mkdirp": "0.5.0",
|
"mkdirp": "0.5.0",
|
||||||
"npm": "^2.14.1",
|
"npm": "^2.14.1",
|
||||||
|
|
Loading…
Reference in New Issue