Merge pull request #407 from bitpay/opt/multiple-address-history
Opt/multiple address history
This commit is contained in:
commit
462e4e3cdd
@ -19,6 +19,15 @@ exports.encodeSpentIndexSyncKey = function(txidBuffer, outputIndex) {
|
|||||||
return key.toString('binary');
|
return key.toString('binary');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.encodeMempoolAddressIndexKey = function(hashBuffer, hashTypeBuffer) {
|
||||||
|
var key = Buffer.concat([
|
||||||
|
hashBuffer,
|
||||||
|
hashTypeBuffer,
|
||||||
|
]);
|
||||||
|
return key.toString('binary');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) {
|
exports.encodeOutputKey = function(hashBuffer, hashTypeBuffer, height, txidBuffer, outputIndex) {
|
||||||
var heightBuffer = new Buffer(4);
|
var heightBuffer = new Buffer(4);
|
||||||
heightBuffer.writeUInt32BE(height);
|
heightBuffer.writeUInt32BE(height);
|
||||||
|
|||||||
@ -44,6 +44,7 @@ function AddressHistory(args) {
|
|||||||
AddressHistory.prototype._mergeAndSortTxids = function(summaries) {
|
AddressHistory.prototype._mergeAndSortTxids = function(summaries) {
|
||||||
var appearanceIds = {};
|
var appearanceIds = {};
|
||||||
var unconfirmedAppearanceIds = {};
|
var unconfirmedAppearanceIds = {};
|
||||||
|
|
||||||
for (var i = 0; i < summaries.length; i++) {
|
for (var i = 0; i < summaries.length; i++) {
|
||||||
var summary = summaries[i];
|
var summary = summaries[i];
|
||||||
for (var key in summary.appearanceIds) {
|
for (var key in summary.appearanceIds) {
|
||||||
@ -79,16 +80,19 @@ AddressHistory.prototype.get = function(callback) {
|
|||||||
return callback(new TypeError('Maximum number of addresses (' + this.maxAddressesQuery + ') exceeded'));
|
return callback(new TypeError('Maximum number of addresses (' + this.maxAddressesQuery + ') exceeded'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var opts = _.clone(this.options);
|
||||||
|
opts.noBalance = true;
|
||||||
|
|
||||||
if (this.addresses.length === 1) {
|
if (this.addresses.length === 1) {
|
||||||
var address = this.addresses[0];
|
var address = this.addresses[0];
|
||||||
self.node.services.address.getAddressSummary(address, this.options, function(err, summary) {
|
self.node.services.address.getAddressSummary(address, opts, function(err, summary) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
return self._paginateWithDetails.call(self, summary.txids, callback);
|
return self._paginateWithDetails.call(self, summary.txids, callback);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var opts = _.clone(this.options);
|
|
||||||
opts.fullTxList = true;
|
opts.fullTxList = true;
|
||||||
async.mapLimit(
|
async.mapLimit(
|
||||||
self.addresses,
|
self.addresses,
|
||||||
|
|||||||
@ -57,6 +57,7 @@ var AddressService = function(options) {
|
|||||||
}
|
}
|
||||||
this.mempoolIndex = null; // Used for larger mempool indexes
|
this.mempoolIndex = null; // Used for larger mempool indexes
|
||||||
this.mempoolSpentIndex = {}; // Used for small quick synchronous lookups
|
this.mempoolSpentIndex = {}; // Used for small quick synchronous lookups
|
||||||
|
this.mempoolAddressIndex = {}; // Used to check if an address is on the spend pool
|
||||||
};
|
};
|
||||||
|
|
||||||
inherits(AddressService, BaseService);
|
inherits(AddressService, BaseService);
|
||||||
@ -70,6 +71,7 @@ AddressService.prototype.start = function(callback) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
async.series([
|
async.series([
|
||||||
|
|
||||||
function(next) {
|
function(next) {
|
||||||
// Flush any existing mempool index
|
// Flush any existing mempool index
|
||||||
if (fs.existsSync(self.mempoolIndexPath)) {
|
if (fs.existsSync(self.mempoolIndexPath)) {
|
||||||
@ -274,6 +276,25 @@ AddressService.prototype.transactionHandler = function(txInfo, callback) {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddressService.prototype._updateAddressIndex = function(key, add) {
|
||||||
|
var currentValue = this.mempoolAddressIndex[key] || 0;
|
||||||
|
|
||||||
|
if(add) {
|
||||||
|
if (currentValue > 0) {
|
||||||
|
this.mempoolAddressIndex[key] = currentValue + 1;
|
||||||
|
} else {
|
||||||
|
this.mempoolAddressIndex[key] = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentValue <= 1) {
|
||||||
|
delete this.mempoolAddressIndex[key];
|
||||||
|
} else {
|
||||||
|
this.mempoolAddressIndex[key]--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function will update the mempool address index with the necessary
|
* This function will update the mempool address index with the necessary
|
||||||
* information for further lookups.
|
* information for further lookups.
|
||||||
@ -306,6 +327,10 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addressIndexKey = encoding.encodeMempoolAddressIndexKey(addressInfo.hashBuffer, addressInfo.hashTypeBuffer);
|
||||||
|
|
||||||
|
this._updateAddressIndex(addressIndexKey, add);
|
||||||
|
|
||||||
// Update output index
|
// Update output index
|
||||||
var outputIndexBuffer = new Buffer(4);
|
var outputIndexBuffer = new Buffer(4);
|
||||||
outputIndexBuffer.writeUInt32BE(outputIndex);
|
outputIndexBuffer.writeUInt32BE(outputIndex);
|
||||||
@ -398,6 +423,9 @@ AddressService.prototype.updateMempoolIndex = function(tx, add, callback) {
|
|||||||
value: inputValue
|
value: inputValue
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var addressIndexKey = encoding.encodeMempoolAddressIndexKey(inputHashBuffer, inputHashType);
|
||||||
|
|
||||||
|
this._updateAddressIndex(addressIndexKey, add);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
@ -1434,26 +1462,29 @@ AddressService.prototype._getAddressConfirmedOutputsSummary = function(address,
|
|||||||
|
|
||||||
var txid = output.txid;
|
var txid = output.txid;
|
||||||
var outputIndex = output.outputIndex;
|
var outputIndex = output.outputIndex;
|
||||||
|
|
||||||
// Bitcoind's isSpent only works for confirmed transactions
|
|
||||||
var spentDB = self.node.services.bitcoind.isSpent(txid, outputIndex);
|
|
||||||
result.totalReceived += output.satoshis;
|
result.totalReceived += output.satoshis;
|
||||||
result.appearanceIds[txid] = output.height;
|
result.appearanceIds[txid] = output.height;
|
||||||
|
|
||||||
if (!spentDB) {
|
if(!options.noBalance) {
|
||||||
result.balance += output.satoshis;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.queryMempool) {
|
// Bitcoind's isSpent only works for confirmed transactions
|
||||||
// Check to see if this output is spent in the mempool and if so
|
var spentDB = self.node.services.bitcoind.isSpent(txid, outputIndex);
|
||||||
// we will subtract it from the unconfirmedBalance (a.k.a unconfirmedDelta)
|
|
||||||
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
if(!spentDB) {
|
||||||
new Buffer(txid, 'hex'), // TODO: get buffer directly
|
result.balance += output.satoshis;
|
||||||
outputIndex
|
}
|
||||||
);
|
|
||||||
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
if(options.queryMempool) {
|
||||||
if (spentMempool) {
|
// Check to see if this output is spent in the mempool and if so
|
||||||
result.unconfirmedBalance -= output.satoshis;
|
// we will subtract it from the unconfirmedBalance (a.k.a unconfirmedDelta)
|
||||||
|
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
||||||
|
new Buffer(txid, 'hex'), // TODO: get buffer directly
|
||||||
|
outputIndex
|
||||||
|
);
|
||||||
|
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
||||||
|
if(spentMempool) {
|
||||||
|
result.unconfirmedBalance -= output.satoshis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1505,6 +1536,11 @@ AddressService.prototype._getAddressMempoolSummary = function(address, options,
|
|||||||
var addressStr = address.toString();
|
var addressStr = address.toString();
|
||||||
var hashBuffer = address.hashBuffer;
|
var hashBuffer = address.hashBuffer;
|
||||||
var hashTypeBuffer = constants.HASH_TYPES_MAP[address.type];
|
var hashTypeBuffer = constants.HASH_TYPES_MAP[address.type];
|
||||||
|
var addressIndexKey = encoding.encodeMempoolAddressIndexKey(hashBuffer, hashTypeBuffer);
|
||||||
|
|
||||||
|
if(!this.mempoolAddressIndex[addressIndexKey]) {
|
||||||
|
return callback(null, result);
|
||||||
|
}
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function(next) {
|
function(next) {
|
||||||
@ -1529,14 +1565,16 @@ AddressService.prototype._getAddressMempoolSummary = function(address, options,
|
|||||||
|
|
||||||
result.unconfirmedAppearanceIds[output.txid] = output.timestamp;
|
result.unconfirmedAppearanceIds[output.txid] = output.timestamp;
|
||||||
|
|
||||||
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
if(!options.noBalance) {
|
||||||
new Buffer(output.txid, 'hex'), // TODO: get buffer directly
|
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
|
||||||
output.outputIndex
|
new Buffer(output.txid, 'hex'), // TODO: get buffer directly
|
||||||
);
|
output.outputIndex
|
||||||
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
);
|
||||||
// Only add this to the balance if it's not spent in the mempool already
|
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
|
||||||
if (!spentMempool) {
|
// Only add this to the balance if it's not spent in the mempool already
|
||||||
result.unconfirmedBalance += output.satoshis;
|
if(!spentMempool) {
|
||||||
|
result.unconfirmedBalance += output.satoshis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next(null, result);
|
next(null, result);
|
||||||
|
|||||||
@ -122,7 +122,9 @@ describe('Address Service History', function() {
|
|||||||
history.get(function() {
|
history.get(function() {
|
||||||
history.node.services.address.getAddressSummary.callCount.should.equal(1);
|
history.node.services.address.getAddressSummary.callCount.should.equal(1);
|
||||||
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
|
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
|
||||||
history.node.services.address.getAddressSummary.args[0][1].should.equal(options);
|
history.node.services.address.getAddressSummary.args[0][1].should.deep.equal({
|
||||||
|
noBalance: true
|
||||||
|
});
|
||||||
history._paginateWithDetails.callCount.should.equal(1);
|
history._paginateWithDetails.callCount.should.equal(1);
|
||||||
history._paginateWithDetails.args[0][0].should.equal(txids);
|
history._paginateWithDetails.args[0][0].should.equal(txids);
|
||||||
history._mergeAndSortTxids.callCount.should.equal(0);
|
history._mergeAndSortTxids.callCount.should.equal(0);
|
||||||
@ -154,7 +156,8 @@ describe('Address Service History', function() {
|
|||||||
history.node.services.address.getAddressSummary.callCount.should.equal(2);
|
history.node.services.address.getAddressSummary.callCount.should.equal(2);
|
||||||
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
|
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
|
||||||
history.node.services.address.getAddressSummary.args[0][1].should.deep.equal({
|
history.node.services.address.getAddressSummary.args[0][1].should.deep.equal({
|
||||||
fullTxList: true
|
fullTxList: true,
|
||||||
|
noBalance: true
|
||||||
});
|
});
|
||||||
history._paginateWithDetails.callCount.should.equal(1);
|
history._paginateWithDetails.callCount.should.equal(1);
|
||||||
history._paginateWithDetails.args[0][0].should.equal(txids);
|
history._paginateWithDetails.args[0][0].should.equal(txids);
|
||||||
|
|||||||
@ -9,6 +9,7 @@ var bitcorenode = require('../../../');
|
|||||||
var AddressService = bitcorenode.services.Address;
|
var AddressService = bitcorenode.services.Address;
|
||||||
var blockData = require('../../data/livenet-345003.json');
|
var blockData = require('../../data/livenet-345003.json');
|
||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
|
var _ = bitcore.deps._;
|
||||||
var memdown = require('memdown');
|
var memdown = require('memdown');
|
||||||
var leveldown = require('leveldown');
|
var leveldown = require('leveldown');
|
||||||
var Networks = bitcore.Networks;
|
var Networks = bitcore.Networks;
|
||||||
@ -1971,6 +1972,8 @@ describe('Address Service', function() {
|
|||||||
am.mempoolIndex.batch = function(operations, callback) {
|
am.mempoolIndex.batch = function(operations, callback) {
|
||||||
callback.should.be.a('function');
|
callback.should.be.a('function');
|
||||||
Object.keys(am.mempoolSpentIndex).length.should.equal(14);
|
Object.keys(am.mempoolSpentIndex).length.should.equal(14);
|
||||||
|
Object.keys(am.mempoolAddressIndex).length.should.equal(5);
|
||||||
|
_.values(am.mempoolAddressIndex).should.deep.equal([1,1,12,1,1]);
|
||||||
for (var i = 0; i < operations.length; i++) {
|
for (var i = 0; i < operations.length; i++) {
|
||||||
operations[i].type.should.equal('put');
|
operations[i].type.should.equal('put');
|
||||||
}
|
}
|
||||||
@ -2006,6 +2009,7 @@ describe('Address Service', function() {
|
|||||||
for (var i = 0; i < operations.length; i++) {
|
for (var i = 0; i < operations.length; i++) {
|
||||||
operations[i].type.should.equal('del');
|
operations[i].type.should.equal('del');
|
||||||
}
|
}
|
||||||
|
Object.keys(am.mempoolAddressIndex).length.should.equal(0);
|
||||||
};
|
};
|
||||||
am.updateMempoolIndex(tx, false);
|
am.updateMempoolIndex(tx, false);
|
||||||
});
|
});
|
||||||
@ -2435,6 +2439,54 @@ describe('Address Service', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('#_updateAddressIndex', function() {
|
||||||
|
it('should add using 2 keys', function() {
|
||||||
|
var as = new AddressService({
|
||||||
|
mempoolMemoryIndex: true,
|
||||||
|
node: mocknode
|
||||||
|
});
|
||||||
|
|
||||||
|
_.values(as.mempoolAddressIndex).should.deep.equal([]);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index2', true);
|
||||||
|
as._updateAddressIndex('index2', true);
|
||||||
|
as.mempoolAddressIndex.should.deep.equal({
|
||||||
|
"index1": 4,
|
||||||
|
"index2": 2
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add/remove using 2 keys', function() {
|
||||||
|
var as = new AddressService({
|
||||||
|
mempoolMemoryIndex: true,
|
||||||
|
node: mocknode
|
||||||
|
});
|
||||||
|
_.values(as.mempoolAddressIndex).should.deep.equal([]);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index1', true);
|
||||||
|
as._updateAddressIndex('index1', false);
|
||||||
|
|
||||||
|
as._updateAddressIndex('index2', true);
|
||||||
|
as._updateAddressIndex('index2', true);
|
||||||
|
as._updateAddressIndex('index2', false);
|
||||||
|
as._updateAddressIndex('index2', false);
|
||||||
|
as.mempoolAddressIndex.should.deep.equal({
|
||||||
|
"index1": 3
|
||||||
|
});
|
||||||
|
as._updateAddressIndex('index2', false);
|
||||||
|
as.mempoolAddressIndex.should.deep.equal({
|
||||||
|
"index1": 3
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('#_getAddressMempoolSummary', function() {
|
describe('#_getAddressMempoolSummary', function() {
|
||||||
it('skip if options not enabled', function(done) {
|
it('skip if options not enabled', function(done) {
|
||||||
var testnode = {
|
var testnode = {
|
||||||
@ -2527,6 +2579,11 @@ describe('Address Service', function() {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
as.mempoolSpentIndex[spentIndexSyncKey] = true;
|
as.mempoolSpentIndex[spentIndexSyncKey] = true;
|
||||||
|
|
||||||
|
var hashTypeBuffer = constants.HASH_TYPES_MAP[address.type];
|
||||||
|
var addressIndex = encoding.encodeMempoolAddressIndexKey(address.hashBuffer, hashTypeBuffer);
|
||||||
|
as.mempoolAddressIndex[addressIndex] = 1;
|
||||||
|
|
||||||
as._getInputsMempool = sinon.stub().callsArgWith(3, null, mempoolInputs);
|
as._getInputsMempool = sinon.stub().callsArgWith(3, null, mempoolInputs);
|
||||||
as._getOutputsMempool = sinon.stub().callsArgWith(3, null, mempoolOutputs);
|
as._getOutputsMempool = sinon.stub().callsArgWith(3, null, mempoolOutputs);
|
||||||
as._getAddressMempoolSummary(address, options, resultBase, function(err, result) {
|
as._getAddressMempoolSummary(address, options, resultBase, function(err, result) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user