Address Block Handling Optimizations

- Changes to use ripemd160 hash directly instead of the base58check encoded values
- Speeds block handling performance by ~4 times
This commit is contained in:
Braydon Fuller 2015-09-11 20:58:59 -04:00
parent c205f781a5
commit 1cf34f2dd8
2 changed files with 201 additions and 127 deletions

View File

@ -9,6 +9,7 @@ var errors = index.errors;
var bitcore = require('bitcore'); var bitcore = require('bitcore');
var $ = bitcore.util.preconditions; var $ = bitcore.util.preconditions;
var _ = bitcore.deps._; var _ = bitcore.deps._;
var Hash = bitcore.crypto.Hash;
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var PublicKey = bitcore.PublicKey; var PublicKey = bitcore.PublicKey;
var Address = bitcore.Address; var Address = bitcore.Address;
@ -81,23 +82,21 @@ AddressService.prototype.transactionOutputHandler = function(messages, tx, outpu
return; return;
} }
// Find the address for the output var addressInfo = this._extractAddressInfoFromScript(script);
var address = script.toAddress(this.node.network); if (!addressInfo) {
if (!address && script.isPublicKeyOut()) {
var pubkey = script.chunks[0].buf;
address = Address.fromPublicKey(new PublicKey(pubkey), this.node.network);
} else if (!address){
return; return;
} }
addressInfo.hashHex = addressInfo.hashBuffer.toString('hex');
// Collect data to publish to address subscribers // Collect data to publish to address subscribers
if (messages[address]) { if (messages[addressInfo.hashHex]) {
messages[address].outputIndexes.push(outputIndex); messages[addressInfo.hashHex].outputIndexes.push(outputIndex);
} else { } else {
messages[address] = { messages[addressInfo.hashHex] = {
tx: tx, tx: tx,
outputIndexes: [outputIndex], outputIndexes: [outputIndex],
address: address.toString(), addressInfo: addressInfo,
rejected: rejected rejected: rejected
}; };
} }
@ -130,6 +129,28 @@ AddressService.prototype.transactionHandler = function(txInfo) {
} }
}; };
AddressService.prototype._extractAddressInfoFromScript = function(script) {
var hashBuffer;
var addressType;
if (script.isPublicKeyHashOut()) {
hashBuffer = script.chunks[2].buf;
addressType = Address.PayToPublicKeyHash;
} else if (script.isScriptHashOut()) {
hashBuffer = script.chunks[1].buf;
addressType = Address.PayToScriptHash;
} else if (script.isPublicKeyOut()) {
var pubkey = script.chunks[0].buf;
var address = Address.fromPublicKey(new PublicKey(pubkey), this.node.network);
hashBuffer = address.hashBuffer;
} else {
return false;
}
return {
hashBuffer: hashBuffer,
addressType: addressType
};
};
AddressService.prototype.blockHandler = function(block, addOutput, callback) { AddressService.prototype.blockHandler = function(block, addOutput, callback) {
var txs = block.transactions; var txs = block.transactions;
@ -162,14 +183,13 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
continue; continue;
} }
var address = script.toAddress(this.node.network); var addressInfo = this._extractAddressInfoFromScript(script);
if (!address && script.isPublicKeyOut()) { if (!addressInfo) {
var pubkey = script.chunks[0].buf;
address = Address.fromPublicKey(new PublicKey(pubkey), this.node.network);
} else if (!address){
continue; continue;
} }
addressInfo.hashHex = addressInfo.hashBuffer.toString('hex');
// We need to use the height for indexes (and not the timestamp) because the // We need to use the height for indexes (and not the timestamp) because the
// the timestamp has unreliable sequential ordering. The next block // the timestamp has unreliable sequential ordering. The next block
// can have a time that is previous to the previous block (however not // can have a time that is previous to the previous block (however not
@ -177,18 +197,19 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
// hours in the future. // hours in the future.
var height = block.__height; var height = block.__height;
var addressStr = address.toString();
var scriptHex = output._scriptBuffer.toString('hex'); var scriptHex = output._scriptBuffer.toString('hex');
// To lookup outputs by address and height // To lookup outputs by address and height
var key = [ var key = [
AddressService.PREFIXES.OUTPUTS, AddressService.PREFIXES.OUTPUTS,
addressStr, addressInfo.hashHex,
height, height,
txid, txid,
outputIndex outputIndex
].join('-'); ].join('-');
// TODO use buffers directly to save on disk storage
var value = [output.satoshis, scriptHex].join(':'); var value = [output.satoshis, scriptHex].join(':');
operations.push({ operations.push({
@ -198,19 +219,19 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
}); });
// Collect data for subscribers // Collect data for subscribers
if (txmessages[addressStr]) { if (txmessages[addressInfo.hashHex]) {
txmessages[addressStr].outputIndexes.push(outputIndex); txmessages[addressInfo.hashHex].outputIndexes.push(outputIndex);
} else { } else {
txmessages[addressStr] = { txmessages[addressInfo.hashHex] = {
tx: tx, tx: tx,
height: block.__height, height: block.__height,
outputIndexes: [outputIndex], outputIndexes: [outputIndex],
address: addressStr, addressInfo: addressInfo,
timestamp: block.header.timestamp timestamp: block.header.timestamp
}; };
} }
this.balanceEventHandler(block, address); this.balanceEventHandler(block, addressInfo);
} }
@ -226,35 +247,36 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
for(var inputIndex = 0; inputIndex < inputs.length; inputIndex++) { for(var inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
var input = inputs[inputIndex]; var input = inputs[inputIndex];
var inputAddress = input.script.toAddress(this.node.network);
if (inputAddress) { var inputHashBuffer;
var inputObject = input.toObject(); if (input.script.isPublicKeyHashIn()) {
var inputAddressStr = inputAddress.toString(); inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[1].buf);
} else if (input.script.isScriptHashIn()) {
var height = block.__height; inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf);
} else {
// To be able to query inputs by address and spent height continue;
var inputKey = [
AddressService.PREFIXES.SPENTS,
inputAddressStr,
height,
inputObject.prevTxId,
inputObject.outputIndex
].join('-');
var inputValue = [
txid,
inputIndex
].join(':');
operations.push({
type: action,
key: inputKey,
value: inputValue
});
} }
// To be able to query inputs by address and spent height
var inputKey = [
AddressService.PREFIXES.SPENTS,
inputHashBuffer.toString('hex'),
block.__height,
input.prevTxId.toString('hex'),
input.outputIndex
].join('-');
var inputValue = [
txid,
inputIndex
].join(':');
operations.push({
type: action,
key: inputKey,
value: inputValue
});
} }
} }
@ -266,30 +288,57 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
/** /**
* @param {Object} obj * @param {Object} obj
* @param {Transaction} obj.tx - The transaction * @param {Transaction} obj.tx - The transaction
* @param {String} [obj.address] - The address for the subscription * @param {Object} obj.addressInfo
* @param {Array} [obj.outputIndexes] - Indexes of the inputs that includes the address * @param {String} obj.addressInfo.hashHex - The hex string of address hash for the subscription
* @param {Array} [obj.inputIndexes] - Indexes of the outputs that includes the address * @param {String} obj.addressInfo.hashBuffer - The address hash buffer
* @param {Date} [obj.timestamp] - The time of the block the transaction was included * @param {String} obj.addressInfo.addressType - The address type
* @param {Number} [obj.height] - The height of the block the transaction was included * @param {Array} obj.outputIndexes - Indexes of the inputs that includes the address
* @param {Boolean} [obj.rejected] - If the transaction was not accepted in the mempool * @param {Array} obj.inputIndexes - Indexes of the outputs that includes the address
* @param {Date} obj.timestamp - The time of the block the transaction was included
* @param {Number} obj.height - The height of the block the transaction was included
* @param {Boolean} obj.rejected - If the transaction was not accepted in the mempool
*/ */
AddressService.prototype.transactionEventHandler = function(obj) { AddressService.prototype.transactionEventHandler = function(obj) {
if(this.subscriptions['address/transaction'][obj.address]) { if(this.subscriptions['address/transaction'][obj.addressInfo.hashHex]) {
var emitters = this.subscriptions['address/transaction'][obj.address]; var emitters = this.subscriptions['address/transaction'][obj.addressInfo.hashHex];
var address = new Address({
hashBuffer: obj.addressInfo.hashBuffer,
network: this.node.network,
type: obj.addressInfo.addressType
});
for(var i = 0; i < emitters.length; i++) { for(var i = 0; i < emitters.length; i++) {
emitters[i].emit('address/transaction', obj); emitters[i].emit('address/transaction', {
rejected: obj.rejected,
height: obj.height,
timestamp: obj.timestamp,
inputIndexes: obj.inputIndexes,
outputIndexes: obj.outputIndexes,
address: address,
tx: obj.tx
});
} }
} }
}; };
AddressService.prototype.balanceEventHandler = function(block, address) { /**
if(this.subscriptions['address/balance'][address]) { * @param {Block} block
var emitters = this.subscriptions['address/balance'][address]; * @param {Object} obj
* @param {String} obj.hashHex
* @param {Buffer} obj.hashBuffer
* @param {String} obj.addressType
*/
AddressService.prototype.balanceEventHandler = function(block, obj) {
if(this.subscriptions['address/balance'][obj.hashHex]) {
var emitters = this.subscriptions['address/balance'][obj.hashHex];
var address = new Address({
hashBuffer: obj.hashBuffer,
network: this.node.network,
type: obj.addressType
});
this.getBalance(address, true, function(err, balance) { this.getBalance(address, true, function(err, balance) {
if(err) { if(err) {
return this.emit(err); return this.emit(err);
} }
for(var i = 0; i < emitters.length; i++) { for(var i = 0; i < emitters.length; i++) {
emitters[i].emit('address/balance', address, balance, block); emitters[i].emit('address/balance', address, balance, block);
} }
@ -302,10 +351,11 @@ AddressService.prototype.subscribe = function(name, emitter, addresses) {
$.checkArgument(Array.isArray(addresses), 'Second argument is expected to be an Array of addresses'); $.checkArgument(Array.isArray(addresses), 'Second argument is expected to be an Array of addresses');
for(var i = 0; i < addresses.length; i++) { for(var i = 0; i < addresses.length; i++) {
if(!this.subscriptions[name][addresses[i]]) { var hashHex = bitcore.Address(addresses[i]).hashBuffer.toString('hex');
this.subscriptions[name][addresses[i]] = []; if(!this.subscriptions[name][hashHex]) {
this.subscriptions[name][hashHex] = [];
} }
this.subscriptions[name][addresses[i]].push(emitter); this.subscriptions[name][hashHex].push(emitter);
} }
}; };
@ -318,8 +368,9 @@ AddressService.prototype.unsubscribe = function(name, emitter, addresses) {
} }
for(var i = 0; i < addresses.length; i++) { for(var i = 0; i < addresses.length; i++) {
if(this.subscriptions[name][addresses[i]]) { var hashHex = bitcore.Address(addresses[i]).hashBuffer.toString('hex');
var emitters = this.subscriptions[name][addresses[i]]; if(this.subscriptions[name][hashHex]) {
var emitters = this.subscriptions[name][hashHex];
var index = emitters.indexOf(emitter); var index = emitters.indexOf(emitter);
if(index > -1) { if(index > -1) {
emitters.splice(index, 1); emitters.splice(index, 1);
@ -331,8 +382,8 @@ AddressService.prototype.unsubscribe = function(name, emitter, addresses) {
AddressService.prototype.unsubscribeAll = function(name, emitter) { AddressService.prototype.unsubscribeAll = function(name, emitter) {
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
for(var address in this.subscriptions[name]) { for(var hashHex in this.subscriptions[name]) {
var emitters = this.subscriptions[name][address]; var emitters = this.subscriptions[name][hashHex];
var index = emitters.indexOf(emitter); var index = emitters.indexOf(emitter);
if(index > -1) { if(index > -1) {
emitters.splice(index, 1); emitters.splice(index, 1);
@ -373,21 +424,23 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
var inputs = []; var inputs = [];
var stream; var stream;
var hashHex = bitcore.Address(addressStr).hashBuffer.toString('hex');
if (options.start && options.end) { if (options.start && options.end) {
// The positions will be flipped because the end position should be greater // The positions will be flipped because the end position should be greater
// than the starting position for the stream, and we'll add one to the end key // than the starting position for the stream, and we'll add one to the end key
// so that it's included in the results. // so that it's included in the results.
var endKey = [AddressService.PREFIXES.SPENTS, addressStr, options.start + 1].join('-'); var endKey = [AddressService.PREFIXES.SPENTS, hashHex, options.start + 1].join('-');
var startKey = [AddressService.PREFIXES.SPENTS, addressStr, options.end].join('-'); var startKey = [AddressService.PREFIXES.SPENTS, hashHex, options.end].join('-');
stream = this.node.services.db.store.createReadStream({ stream = this.node.services.db.store.createReadStream({
start: startKey, start: startKey,
end: endKey end: endKey
}); });
} else { } else {
var allKey = [AddressService.PREFIXES.SPENTS, addressStr].join('-'); var allKey = [AddressService.PREFIXES.SPENTS, hashHex].join('-');
stream = this.node.services.db.store.createReadStream({ stream = this.node.services.db.store.createReadStream({
start: allKey, start: allKey,
end: allKey + '~' end: allKey + '~'
@ -449,6 +502,8 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
$.checkArgument(_.isObject(options), 'Second argument is expected to be an options object.'); $.checkArgument(_.isObject(options), 'Second argument is expected to be an options object.');
$.checkArgument(_.isFunction(callback), 'Third argument is expected to be a callback function.'); $.checkArgument(_.isFunction(callback), 'Third argument is expected to be a callback function.');
var hashHex = bitcore.Address(addressStr).hashBuffer.toString('hex');
var outputs = []; var outputs = [];
var stream; var stream;
@ -457,15 +512,15 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
// The positions will be flipped because the end position should be greater // The positions will be flipped because the end position should be greater
// than the starting position for the stream, and we'll add one to the end key // than the starting position for the stream, and we'll add one to the end key
// so that it's included in the results. // so that it's included in the results.
var endKey = [AddressService.PREFIXES.OUTPUTS, addressStr, options.start + 1].join('-'); var endKey = [AddressService.PREFIXES.OUTPUTS, hashHex, options.start + 1].join('-');
var startKey = [AddressService.PREFIXES.OUTPUTS, addressStr, options.end].join('-'); var startKey = [AddressService.PREFIXES.OUTPUTS, hashHex, options.end].join('-');
stream = this.node.services.db.store.createReadStream({ stream = this.node.services.db.store.createReadStream({
start: startKey, start: startKey,
end: endKey end: endKey
}); });
} else { } else {
var allKey = [AddressService.PREFIXES.OUTPUTS, addressStr].join('-'); var allKey = [AddressService.PREFIXES.OUTPUTS, hashHex].join('-');
stream = this.node.services.db.store.createReadStream({ stream = this.node.services.db.store.createReadStream({
start: allKey, start: allKey,
end: allKey + '~' end: allKey + '~'

View File

@ -74,13 +74,15 @@ describe('Address Service', function() {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
am.node.network = Networks.livenet; am.node.network = Networks.livenet;
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX'; var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
var hashHex = bitcore.Address(address).hashBuffer.toString('hex');
var messages = {}; var messages = {};
am.transactionOutputHandler(messages, tx, 0, true); am.transactionOutputHandler(messages, tx, 0, true);
should.exist(messages[address]); should.exist(messages[hashHex]);
var message = messages[address]; var message = messages[hashHex];
message.tx.should.equal(tx); message.tx.should.equal(tx);
message.outputIndexes.should.deep.equal([0]); message.outputIndexes.should.deep.equal([0]);
message.address.should.equal(address); message.addressInfo.hashBuffer.toString('hex').should.equal(hashHex);
message.addressInfo.hashHex.should.equal(hashHex);
message.rejected.should.equal(true); message.rejected.should.equal(true);
}); });
}); });
@ -109,7 +111,7 @@ describe('Address Service', function() {
var data = [ var data = [
{ {
key: { key: {
address: '1F1MAvhTKg2VG29w8cXsiSN2PJ8gSsrJw', hashHex: bitcore.Address('1F1MAvhTKg2VG29w8cXsiSN2PJ8gSsrJw').hashBuffer.toString('hex'),
height: 345003, height: 345003,
txid: 'fdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e16923', txid: 'fdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e16923',
outputIndex: 0 outputIndex: 0
@ -122,7 +124,7 @@ describe('Address Service', function() {
}, },
{ {
key: { key: {
address: '1Q8ec8kG7c7HqgK7uSzQyWsX9tzepRcKEL', hashHex: bitcore.Address('1Q8ec8kG7c7HqgK7uSzQyWsX9tzepRcKEL').hashBuffer.toString('hex'),
height: 345003, height: 345003,
prevTxId: '3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a9', prevTxId: '3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a9',
prevOutputIndex: 32 prevOutputIndex: 32
@ -134,7 +136,7 @@ describe('Address Service', function() {
}, },
{ {
key: { key: {
address: '1Ep5LA4T6Y7zaBPiwruUJurjGFvCJHzJhm', hashHex: bitcore.Address('1Ep5LA4T6Y7zaBPiwruUJurjGFvCJHzJhm').hashBuffer.toString('hex'),
height: 345003, height: 345003,
txid: 'e66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d', txid: 'e66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d',
outputIndex: 1 outputIndex: 1
@ -171,15 +173,15 @@ describe('Address Service', function() {
should.not.exist(err); should.not.exist(err);
operations.length.should.equal(81); operations.length.should.equal(81);
operations[0].type.should.equal('put'); operations[0].type.should.equal('put');
var expected0 = ['outs', key0.address, key0.height, key0.txid, key0.outputIndex].join('-'); var expected0 = ['outs', key0.hashHex, key0.height, key0.txid, key0.outputIndex].join('-');
operations[0].key.should.equal(expected0); operations[0].key.should.equal(expected0);
operations[0].value.should.equal([value0.satoshis, value0.script].join(':')); operations[0].value.should.equal([value0.satoshis, value0.script].join(':'));
operations[3].type.should.equal('put'); operations[3].type.should.equal('put');
var expected3 = ['sp', key3.address, key3.height, key3.prevTxId, key3.prevOutputIndex].join('-'); var expected3 = ['sp', key3.hashHex, key3.height, key3.prevTxId, key3.prevOutputIndex].join('-');
operations[3].key.should.equal(expected3); operations[3].key.should.equal(expected3);
operations[3].value.should.equal([value3.txid, value3.inputIndex].join(':')); operations[3].value.should.equal([value3.txid, value3.inputIndex].join(':'));
operations[64].type.should.equal('put'); operations[64].type.should.equal('put');
var expected64 = ['outs', key64.address, key64.height, key64.txid, key64.outputIndex].join('-'); var expected64 = ['outs', key64.hashHex, key64.height, key64.txid, key64.outputIndex].join('-');
operations[64].key.should.equal(expected64); operations[64].key.should.equal(expected64);
operations[64].value.should.equal([value64.satoshis, value64.script].join(':')); operations[64].value.should.equal([value64.satoshis, value64.script].join(':'));
done(); done();
@ -197,13 +199,13 @@ describe('Address Service', function() {
should.not.exist(err); should.not.exist(err);
operations.length.should.equal(81); operations.length.should.equal(81);
operations[0].type.should.equal('del'); operations[0].type.should.equal('del');
operations[0].key.should.equal(['outs', key0.address, key0.height, key0.txid, key0.outputIndex].join('-')); operations[0].key.should.equal(['outs', key0.hashHex, key0.height, key0.txid, key0.outputIndex].join('-'));
operations[0].value.should.equal([value0.satoshis, value0.script].join(':')); operations[0].value.should.equal([value0.satoshis, value0.script].join(':'));
operations[3].type.should.equal('del'); operations[3].type.should.equal('del');
operations[3].key.should.equal(['sp', key3.address, key3.height, key3.prevTxId, key3.prevOutputIndex].join('-')); operations[3].key.should.equal(['sp', key3.hashHex, key3.height, key3.prevTxId, key3.prevOutputIndex].join('-'));
operations[3].value.should.equal([value3.txid, value3.inputIndex].join(':')); operations[3].value.should.equal([value3.txid, value3.inputIndex].join(':'));
operations[64].type.should.equal('del'); operations[64].type.should.equal('del');
operations[64].key.should.equal(['outs', key64.address, key64.height, key64.txid, key64.outputIndex].join('-')); operations[64].key.should.equal(['outs', key64.hashHex, key64.height, key64.txid, key64.outputIndex].join('-'));
operations[64].value.should.equal([value64.satoshis, value64.script].join(':')); operations[64].value.should.equal([value64.satoshis, value64.script].join(':'));
done(); done();
}); });
@ -277,16 +279,16 @@ describe('Address Service', function() {
it('will emit a transaction if there is a subscriber', function(done) { it('will emit a transaction if there is a subscriber', function(done) {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
var emitter = new EventEmitter(); var emitter = new EventEmitter();
am.subscriptions['address/transaction'] = { var address = bitcore.Address('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter] am.subscriptions['address/transaction'] = {};
}; am.subscriptions['address/transaction'][address.hashBuffer.toString('hex')] = [emitter];
var block = { var block = {
__height: 0, __height: 0,
timestamp: new Date() timestamp: new Date()
}; };
var tx = {}; var tx = {};
emitter.on('address/transaction', function(obj) { emitter.on('address/transaction', function(obj) {
obj.address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'); obj.address.toString().should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
obj.tx.should.equal(tx); obj.tx.should.equal(tx);
obj.timestamp.should.equal(block.timestamp); obj.timestamp.should.equal(block.timestamp);
obj.height.should.equal(block.__height); obj.height.should.equal(block.__height);
@ -294,7 +296,11 @@ describe('Address Service', function() {
done(); done();
}); });
am.transactionEventHandler({ am.transactionEventHandler({
address: '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N', addressInfo: {
hashHex: address.hashBuffer.toString('hex'),
hashBuffer: address.hashBuffer,
addressType: address.type
},
height: block.__height, height: block.__height,
timestamp: block.timestamp, timestamp: block.timestamp,
outputIndexes: [1], outputIndexes: [1],
@ -307,19 +313,22 @@ describe('Address Service', function() {
it('will emit a balance if there is a subscriber', function(done) { it('will emit a balance if there is a subscriber', function(done) {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
var emitter = new EventEmitter(); var emitter = new EventEmitter();
am.subscriptions['address/balance'] = { var address = bitcore.Address('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter] am.subscriptions['address/balance'][address.hashBuffer.toString('hex')] = [emitter];
};
var block = {}; var block = {};
var balance = 1000; var balance = 1000;
am.getBalance = sinon.stub().callsArgWith(2, null, balance); am.getBalance = sinon.stub().callsArgWith(2, null, balance);
emitter.on('address/balance', function(address, bal, b) { emitter.on('address/balance', function(a, bal, b) {
address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'); a.toString().should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
bal.should.equal(balance); bal.should.equal(balance);
b.should.equal(block); b.should.equal(block);
done(); done();
}); });
am.balanceEventHandler(block, '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'); am.balanceEventHandler(block, {
hashHex: address.hashBuffer.toString('hex'),
hashBuffer: address.hashBuffer,
addressType: address.type
});
}); });
}); });
@ -328,34 +337,40 @@ describe('Address Service', function() {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
var emitter = new EventEmitter(); var emitter = new EventEmitter();
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; var address = bitcore.Address('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
var name = 'address/transaction'; var name = 'address/transaction';
am.subscribe(name, emitter, [address]); am.subscribe(name, emitter, [address]);
am.subscriptions['address/transaction'][address].should.deep.equal([emitter]); am.subscriptions['address/transaction'][address.hashBuffer.toString('hex')]
.should.deep.equal([emitter]);
var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W'; var address2 = bitcore.Address('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W');
am.subscribe(name, emitter, [address2]); am.subscribe(name, emitter, [address2]);
am.subscriptions['address/transaction'][address2].should.deep.equal([emitter]); am.subscriptions['address/transaction'][address2.hashBuffer.toString('hex')]
.should.deep.equal([emitter]);
var emitter2 = new EventEmitter(); var emitter2 = new EventEmitter();
am.subscribe(name, emitter2, [address]); am.subscribe(name, emitter2, [address]);
am.subscriptions['address/transaction'][address].should.deep.equal([emitter, emitter2]); am.subscriptions['address/transaction'][address.hashBuffer.toString('hex')]
.should.deep.equal([emitter, emitter2]);
}); });
it('will add an emitter to the subscribers array (balance)', function() { it('will add an emitter to the subscribers array (balance)', function() {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
var emitter = new EventEmitter(); var emitter = new EventEmitter();
var name = 'address/balance'; var name = 'address/balance';
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; var address = bitcore.Address('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
am.subscribe(name, emitter, [address]); am.subscribe(name, emitter, [address]);
am.subscriptions['address/balance'][address].should.deep.equal([emitter]); am.subscriptions['address/balance'][address.hashBuffer.toString('hex')]
.should.deep.equal([emitter]);
var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W'; var address2 = bitcore.Address('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W');
am.subscribe(name, emitter, [address2]); am.subscribe(name, emitter, [address2]);
am.subscriptions['address/balance'][address2].should.deep.equal([emitter]); am.subscriptions['address/balance'][address2.hashBuffer.toString('hex')]
.should.deep.equal([emitter]);
var emitter2 = new EventEmitter(); var emitter2 = new EventEmitter();
am.subscribe(name, emitter2, [address]); am.subscribe(name, emitter2, [address]);
am.subscriptions['address/balance'][address].should.deep.equal([emitter, emitter2]); am.subscriptions['address/balance'][address.hashBuffer.toString('hex')]
.should.deep.equal([emitter, emitter2]);
}); });
}); });
@ -364,35 +379,37 @@ describe('Address Service', function() {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
var emitter = new EventEmitter(); var emitter = new EventEmitter();
var emitter2 = new EventEmitter(); var emitter2 = new EventEmitter();
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; var address = bitcore.Address('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
am.subscriptions['address/transaction'][address] = [emitter, emitter2]; am.subscriptions['address/transaction'][address.hashBuffer.toString('hex')] = [emitter, emitter2];
var name = 'address/transaction'; var name = 'address/transaction';
am.unsubscribe(name, emitter, [address]); am.unsubscribe(name, emitter, [address]);
am.subscriptions['address/transaction'][address].should.deep.equal([emitter2]); am.subscriptions['address/transaction'][address.hashBuffer.toString('hex')]
.should.deep.equal([emitter2]);
}); });
it('will remove emitter from subscribers array (balance)', function() { it('will remove emitter from subscribers array (balance)', function() {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
var emitter = new EventEmitter(); var emitter = new EventEmitter();
var emitter2 = new EventEmitter(); var emitter2 = new EventEmitter();
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; var address = bitcore.Address('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
var name = 'address/balance'; var name = 'address/balance';
am.subscriptions['address/balance'][address] = [emitter, emitter2]; am.subscriptions['address/balance'][address.hashBuffer.toString('hex')] = [emitter, emitter2];
am.unsubscribe(name, emitter, [address]); am.unsubscribe(name, emitter, [address]);
am.subscriptions['address/balance'][address].should.deep.equal([emitter2]); am.subscriptions['address/balance'][address.hashBuffer.toString('hex')]
.should.deep.equal([emitter2]);
}); });
it('should unsubscribe from all addresses if no addresses are specified', function() { it('should unsubscribe from all addresses if no addresses are specified', function() {
var am = new AddressService({node: mocknode}); var am = new AddressService({node: mocknode});
var emitter = new EventEmitter(); var emitter = new EventEmitter();
var emitter2 = new EventEmitter(); var emitter2 = new EventEmitter();
am.subscriptions['address/balance'] = { var address1 = bitcore.Address('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W');
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter, emitter2], var hashHex1 = address1.hashBuffer.toString('hex');
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2, emitter] var address2 = bitcore.Address('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
}; var hashHex2 = address2.hashBuffer.toString('hex');
am.subscriptions['address/balance'][hashHex1] = [emitter, emitter2];
am.subscriptions['address/balance'][hashHex2] = [emitter2, emitter];
am.unsubscribe('address/balance', emitter); am.unsubscribe('address/balance', emitter);
am.subscriptions['address/balance'].should.deep.equal({ am.subscriptions['address/balance'][hashHex1].should.deep.equal([emitter2]);
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter2], am.subscriptions['address/balance'][hashHex2].should.deep.equal([emitter2]);
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2]
});
}); });
}); });
@ -425,6 +442,7 @@ describe('Address Service', function() {
describe('#getInputs', function() { describe('#getInputs', function() {
var am; var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W'; var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashHex = bitcore.Address(address).hashBuffer.toString('hex');
var db = { var db = {
tip: { tip: {
__height: 1 __height: 1
@ -452,8 +470,8 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0; var createReadStreamCallCount = 0;
am.node.services.db.store = { am.node.services.db.store = {
createReadStream: function(ops) { createReadStream: function(ops) {
ops.start.should.equal([AddressService.PREFIXES.SPENTS, address, 12].join('-')); ops.start.should.equal([AddressService.PREFIXES.SPENTS, hashHex, 12].join('-'));
ops.end.should.equal([AddressService.PREFIXES.SPENTS, address, 16].join('-')); ops.end.should.equal([AddressService.PREFIXES.SPENTS, hashHex, 16].join('-'));
createReadStreamCallCount++; createReadStreamCallCount++;
return testStream; return testStream;
} }
@ -486,8 +504,8 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0; var createReadStreamCallCount = 0;
am.node.services.db.store = { am.node.services.db.store = {
createReadStream: function(ops) { createReadStream: function(ops) {
ops.start.should.equal([AddressService.PREFIXES.SPENTS, address].join('-')); ops.start.should.equal([AddressService.PREFIXES.SPENTS, hashHex].join('-'));
ops.end.should.equal([AddressService.PREFIXES.SPENTS, address].join('-') + '~'); ops.end.should.equal([AddressService.PREFIXES.SPENTS, hashHex].join('-') + '~');
createReadStreamCallCount++; createReadStreamCallCount++;
return testStream; return testStream;
} }
@ -535,6 +553,7 @@ describe('Address Service', function() {
describe('#getOutputs', function() { describe('#getOutputs', function() {
var am; var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W'; var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashHex = bitcore.Address('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W').hashBuffer.toString('hex');
var db = { var db = {
tip: { tip: {
__height: 1 __height: 1
@ -566,8 +585,8 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0; var createReadStreamCallCount = 0;
am.node.services.db.store = { am.node.services.db.store = {
createReadStream: function(ops) { createReadStream: function(ops) {
ops.start.should.equal([AddressService.PREFIXES.OUTPUTS, address, 12].join('-')); ops.start.should.equal([AddressService.PREFIXES.OUTPUTS, hashHex, 12].join('-'));
ops.end.should.equal([AddressService.PREFIXES.OUTPUTS, address, 16].join('-')); ops.end.should.equal([AddressService.PREFIXES.OUTPUTS, hashHex, 16].join('-'));
createReadStreamCallCount++; createReadStreamCallCount++;
return testStream; return testStream;
} }