Improve db storage efficiency by ~2 times

Encodes and decodes keys and values for leveldb storage more efficiently.
This commit is contained in:
Braydon Fuller 2015-09-14 09:01:51 -04:00
parent d3641f3b0a
commit f88eee5a1c
2 changed files with 215 additions and 158 deletions

View File

@ -10,6 +10,7 @@ var bitcore = require('bitcore');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var Hash = bitcore.crypto.Hash;
var BufferReader = bitcore.encoding.BufferReader;
var EventEmitter = require('events').EventEmitter;
var PublicKey = bitcore.PublicKey;
var Address = bitcore.Address;
@ -34,10 +35,13 @@ AddressService.dependencies = [
];
AddressService.PREFIXES = {
OUTPUTS: 'outs',
SPENTS: 'sp'
OUTPUTS: new Buffer('32', 'hex'),
SPENTS: new Buffer('33', 'hex')
};
AddressService.SPACER_MIN = new Buffer('00', 'hex');
AddressService.SPACER_MAX = new Buffer('ff', 'hex');
AddressService.prototype.getAPIMethods = function() {
return [
['getBalance', this, this.getBalance, 2],
@ -153,6 +157,7 @@ AddressService.prototype._extractAddressInfoFromScript = function(script) {
AddressService.prototype.blockHandler = function(block, addOutput, callback) {
var txs = block.transactions;
var height = block.__height;
var action = 'put';
if (!addOutput) {
@ -188,43 +193,28 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
continue;
}
addressInfo.hashHex = addressInfo.hashBuffer.toString('hex');
// We need to use the height for indexes (and not the timestamp) because the
// the timestamp has unreliable sequential ordering. The next block
// can have a time that is previous to the previous block (however not
// less than the mean of the 11 previous blocks) and not greater than 2
// hours in the future.
var height = block.__height;
var scriptHex = output._scriptBuffer.toString('hex');
// To lookup outputs by address and height
var key = [
AddressService.PREFIXES.OUTPUTS,
addressInfo.hashHex,
height,
txid,
outputIndex
].join('-');
// TODO use buffers directly to save on disk storage
var value = [output.satoshis, scriptHex].join(':');
var key = this._encodeOutputKey(addressInfo.hashBuffer, height, txid, outputIndex);
var value = this._encodeOutputValue(output.satoshis, output._scriptBuffer);
operations.push({
type: action,
key: key,
value: value
});
addressInfo.hashHex = addressInfo.hashBuffer.toString('hex');
// Collect data for subscribers
if (txmessages[addressInfo.hashHex]) {
txmessages[addressInfo.hashHex].outputIndexes.push(outputIndex);
} else {
txmessages[addressInfo.hashHex] = {
tx: tx,
height: block.__height,
height: height,
outputIndexes: [outputIndex],
addressInfo: addressInfo,
timestamp: block.header.timestamp
@ -247,30 +237,19 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
for(var inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
var input = inputs[inputIndex];
var inputHashBuffer;
var inputHash;
if (input.script.isPublicKeyHashIn()) {
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[1].buf);
inputHash = Hash.sha256ripemd160(input.script.chunks[1].buf);
} else if (input.script.isScriptHashIn()) {
inputHashBuffer = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf);
inputHash = Hash.sha256ripemd160(input.script.chunks[input.script.chunks.length - 1].buf);
} else {
continue;
}
// 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(':');
var inputKey = this._encodeInputKey(inputHash, height, input.prevTxId, input.outputIndex);
var inputValue = this._encodeInputValue(txid, inputIndex);
operations.push({
type: action,
@ -285,6 +264,104 @@ AddressService.prototype.blockHandler = function(block, addOutput, callback) {
});
};
AddressService.prototype._encodeOutputKey = function(hashBuffer, height, txid, outputIndex) {
var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height);
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex);
var key = Buffer.concat([
AddressService.PREFIXES.OUTPUTS,
hashBuffer,
AddressService.SPACER_MIN,
heightBuffer,
new Buffer(txid, 'hex'), //TODO get buffer directly from tx
outputIndexBuffer
]);
return key;
};
AddressService.prototype._decodeOutputKey = function(buffer) {
var reader = new BufferReader(buffer);
var prefix = reader.read(1);
var hashBuffer = reader.read(20);
var spacer = reader.read(1);
var height = reader.readUInt32BE();
var txid = reader.read(32);
var outputIndex = reader.readUInt32BE();
return {
prefix: prefix,
hashBuffer: hashBuffer,
height: height,
txid: txid,
outputIndex: outputIndex
};
};
AddressService.prototype._encodeOutputValue = function(satoshis, scriptBuffer) {
var satoshisBuffer = new Buffer(8);
satoshisBuffer.writeDoubleBE(satoshis);
return Buffer.concat([satoshisBuffer, scriptBuffer]);
};
AddressService.prototype._decodeOutputValue = function(buffer) {
var satoshis = buffer.readDoubleBE(0);
var scriptBuffer = buffer.slice(8, buffer.length);
return {
satoshis: satoshis,
scriptBuffer: scriptBuffer
};
};
AddressService.prototype._encodeInputKey = function(hashBuffer, height, prevTxIdBuffer, outputIndex) {
var heightBuffer = new Buffer(4);
heightBuffer.writeUInt32BE(height);
var outputIndexBuffer = new Buffer(4);
outputIndexBuffer.writeUInt32BE(outputIndex);
return Buffer.concat([
AddressService.PREFIXES.SPENTS,
hashBuffer,
AddressService.SPACER_MIN,
heightBuffer,
prevTxIdBuffer,
outputIndexBuffer
]);
};
AddressService.prototype._decodeInputKey = function(buffer) {
var reader = new BufferReader(buffer);
var prefix = reader.read(1);
var hashBuffer = reader.read(20);
var spacer = reader.read(1);
var height = reader.readUInt32BE();
var prevTxId = reader.read(32);
var outputIndex = reader.readUInt32BE();
return {
prefix: prefix,
hashBuffer: hashBuffer,
height: height,
prevTxId: prevTxId,
outputIndex: outputIndex
};
};
AddressService.prototype._encodeInputValue = function(txid, inputIndex) {
var inputIndexBuffer = new Buffer(4);
inputIndexBuffer.writeUInt32BE(inputIndex);
return Buffer.concat([
new Buffer(txid, 'hex'),
inputIndexBuffer
]);
};
AddressService.prototype._decodeInputValue = function(buffer) {
var txid = buffer.slice(0, 32);
var inputIndex = buffer.readUInt32BE(32);
return {
txid: txid,
inputIndex: inputIndex
};
};
/**
* @param {Object} obj
* @param {Transaction} obj.tx - The transaction
@ -424,45 +501,56 @@ AddressService.prototype.getInputs = function(addressStr, options, callback) {
var inputs = [];
var stream;
var hashHex = bitcore.Address(addressStr).hashBuffer.toString('hex');
var hashBuffer = bitcore.Address(addressStr).hashBuffer;
if (options.start && options.end) {
// 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
// so that it's included in the results.
var endBuffer = new Buffer(4);
endBuffer.writeUInt32BE(options.end);
var endKey = [AddressService.PREFIXES.SPENTS, hashHex, options.start + 1].join('-');
var startKey = [AddressService.PREFIXES.SPENTS, hashHex, options.end].join('-');
var startBuffer = new Buffer(4);
startBuffer.writeUInt32BE(options.start + 1);
stream = this.node.services.db.store.createReadStream({
start: startKey,
end: endKey
gte: Buffer.concat([
AddressService.PREFIXES.SPENTS,
hashBuffer,
AddressService.SPACER_MIN,
endBuffer
]),
lte: Buffer.concat([
AddressService.PREFIXES.SPENTS,
hashBuffer,
AddressService.SPACER_MIN,
startBuffer
]),
valueEncoding: 'binary',
keyEncoding: 'binary'
});
} else {
var allKey = [AddressService.PREFIXES.SPENTS, hashHex].join('-');
var allKey = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer]);
stream = this.node.services.db.store.createReadStream({
start: allKey,
end: allKey + '~'
gte: Buffer.concat([allKey, AddressService.SPACER_MIN]),
lte: Buffer.concat([allKey, AddressService.SPACER_MAX]),
valueEncoding: 'binary',
keyEncoding: 'binary'
});
}
stream.on('data', function(data) {
var key = data.key.split('-');
var value = data.value.split(':');
var key = self._decodeInputKey(data.key);
var value = self._decodeInputValue(data.value);
var blockHeight = Number(key[2]);
var output = {
var input = {
address: addressStr,
txid: value[0],
inputIndex: Number(value[1]),
height: blockHeight,
confirmations: self.node.services.db.tip.__height - blockHeight + 1
txid: value.txid.toString('hex'),
inputIndex: value.inputIndex,
height: key.height,
confirmations: self.node.services.db.tip.__height - key.height + 1
};
inputs.push(output);
inputs.push(input);
});
@ -502,44 +590,57 @@ AddressService.prototype.getOutputs = function(addressStr, options, callback) {
$.checkArgument(_.isObject(options), 'Second argument is expected to be an options object.');
$.checkArgument(_.isFunction(callback), 'Third argument is expected to be a callback function.');
var hashHex = bitcore.Address(addressStr).hashBuffer.toString('hex');
var hashBuffer = bitcore.Address(addressStr).hashBuffer;
var outputs = [];
var stream;
if (options.start && options.end) {
// 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
// so that it's included in the results.
var endKey = [AddressService.PREFIXES.OUTPUTS, hashHex, options.start + 1].join('-');
var startKey = [AddressService.PREFIXES.OUTPUTS, hashHex, options.end].join('-');
var startBuffer = new Buffer(4);
startBuffer.writeUInt32BE(options.start + 1);
var endBuffer = new Buffer(4);
endBuffer.writeUInt32BE(options.end);
stream = this.node.services.db.store.createReadStream({
start: startKey,
end: endKey
gte: Buffer.concat([
AddressService.PREFIXES.OUTPUTS,
hashBuffer,
AddressService.SPACER_MIN,
endBuffer
]),
lte: Buffer.concat([
AddressService.PREFIXES.OUTPUTS,
hashBuffer,
AddressService.SPACER_MIN,
startBuffer
]),
valueEncoding: 'binary',
keyEncoding: 'binary'
});
} else {
var allKey = [AddressService.PREFIXES.OUTPUTS, hashHex].join('-');
var allKey = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer]);
stream = this.node.services.db.store.createReadStream({
start: allKey,
end: allKey + '~'
gte: Buffer.concat([allKey, AddressService.SPACER_MIN]),
lte: Buffer.concat([allKey, AddressService.SPACER_MAX]),
valueEncoding: 'binary',
keyEncoding: 'binary'
});
}
stream.on('data', function(data) {
var key = data.key.split('-');
var value = data.value.split(':');
var key = self._decodeOutputKey(data.key);
var value = self._decodeOutputValue(data.value);
var output = {
address: addressStr,
txid: key[3],
outputIndex: Number(key[4]),
height: Number(key[2]),
satoshis: Number(value[0]),
script: value[1],
confirmations: self.node.services.db.tip.__height - Number(key[2]) + 1
txid: key.txid.toString('hex'),
outputIndex: key.outputIndex,
height: key.height,
satoshis: value.satoshis,
script: value.scriptBuffer.toString('hex'),
confirmations: self.node.services.db.tip.__height - key.height + 1
};
outputs.push(output);

View File

@ -108,53 +108,6 @@ describe('Address Service', function() {
var am;
var testBlock = bitcore.Block.fromString(blockData);
var data = [
{
key: {
hashHex: bitcore.Address('1F1MAvhTKg2VG29w8cXsiSN2PJ8gSsrJw').hashBuffer.toString('hex'),
height: 345003,
txid: 'fdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e16923',
outputIndex: 0
},
value: {
satoshis: 2502227470,
script: '76a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac',
blockHeight: 345003
}
},
{
key: {
hashHex: bitcore.Address('1Q8ec8kG7c7HqgK7uSzQyWsX9tzepRcKEL').hashBuffer.toString('hex'),
height: 345003,
prevTxId: '3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a9',
prevOutputIndex: 32
},
value: {
txid: '5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca',
inputIndex: 0
}
},
{
key: {
hashHex: bitcore.Address('1Ep5LA4T6Y7zaBPiwruUJurjGFvCJHzJhm').hashBuffer.toString('hex'),
height: 345003,
txid: 'e66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d',
outputIndex: 1
},
value: {
satoshis: 3100000,
script: '76a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac',
blockHeight: 345003
}
}
];
var key0 = data[0].key;
var value0 = data[0].value;
var key3 = data[1].key;
var value3 = data[1].value;
var key64 = data[2].key;
var value64 = data[2].value;
before(function() {
am = new AddressService({node: mocknode});
am.node.network = Networks.livenet;
@ -173,17 +126,14 @@ describe('Address Service', function() {
should.not.exist(err);
operations.length.should.equal(81);
operations[0].type.should.equal('put');
var expected0 = ['outs', key0.hashHex, key0.height, key0.txid, key0.outputIndex].join('-');
operations[0].key.should.equal(expected0);
operations[0].value.should.equal([value0.satoshis, value0.script].join(':'));
operations[0].key.toString('hex').should.equal('3202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
operations[3].type.should.equal('put');
var expected3 = ['sp', key3.hashHex, key3.height, key3.prevTxId, key3.prevOutputIndex].join('-');
operations[3].key.should.equal(expected3);
operations[3].value.should.equal([value3.txid, value3.inputIndex].join(':'));
operations[3].key.toString('hex').should.equal('33fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
operations[64].type.should.equal('put');
var expected64 = ['outs', key64.hashHex, key64.height, key64.txid, key64.outputIndex].join('-');
operations[64].key.should.equal(expected64);
operations[64].value.should.equal([value64.satoshis, value64.script].join(':'));
operations[64].key.toString('hex').should.equal('329780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
done();
});
});
@ -199,14 +149,14 @@ describe('Address Service', function() {
should.not.exist(err);
operations.length.should.equal(81);
operations[0].type.should.equal('del');
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].key.toString('hex').should.equal('3202a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b00000543abfdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e1692300000000');
operations[0].value.toString('hex').should.equal('41e2a49ec1c0000076a91402a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b88ac');
operations[3].type.should.equal('del');
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].key.toString('hex').should.equal('33fdbd324b28ea69e49c998816407dc055fb81d06e00000543ab3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a900000020');
operations[3].value.toString('hex').should.equal('5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca00000000');
operations[64].type.should.equal('del');
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].key.toString('hex').should.equal('329780ccd5356e2acc0ee439ee04e0fe69426c752800000543abe66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d00000001');
operations[64].value.toString('hex').should.equal('4147a6b00000000076a9149780ccd5356e2acc0ee439ee04e0fe69426c752888ac');
done();
});
});
@ -442,7 +392,7 @@ describe('Address Service', function() {
describe('#getInputs', function() {
var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashHex = bitcore.Address(address).hashBuffer.toString('hex');
var hashBuffer = bitcore.Address(address).hashBuffer;
var db = {
tip: {
__height: 1
@ -470,8 +420,10 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0;
am.node.services.db.store = {
createReadStream: function(ops) {
ops.start.should.equal([AddressService.PREFIXES.SPENTS, hashHex, 12].join('-'));
ops.end.should.equal([AddressService.PREFIXES.SPENTS, hashHex, 16].join('-'));
var gte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('000000000c', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('0000000010', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
}
@ -490,8 +442,8 @@ describe('Address Service', function() {
});
createReadStreamCallCount.should.equal(1);
var data = {
key: ['sp', address, '15', '125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87', '1'].join('-'),
value: ['3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', '0'].join(':')
key: new Buffer('33038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000000', 'hex')
};
testStream.emit('data', data);
testStream.emit('close');
@ -504,8 +456,10 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0;
am.node.services.db.store = {
createReadStream: function(ops) {
ops.start.should.equal([AddressService.PREFIXES.SPENTS, hashHex].join('-'));
ops.end.should.equal([AddressService.PREFIXES.SPENTS, hashHex].join('-') + '~');
var gte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('00', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.SPENTS, hashBuffer, new Buffer('ff', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
}
@ -524,8 +478,8 @@ describe('Address Service', function() {
});
createReadStreamCallCount.should.equal(1);
var data = {
key: ['sp', address, '15', '125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87', '1'].join('-'),
value: ['3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', '0'].join(':')
key: new Buffer('33038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000000', 'hex')
};
testStream.emit('data', data);
testStream.emit('close');
@ -553,7 +507,7 @@ describe('Address Service', function() {
describe('#getOutputs', function() {
var am;
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
var hashHex = bitcore.Address('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W').hashBuffer.toString('hex');
var hashBuffer = bitcore.Address('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W').hashBuffer;
var db = {
tip: {
__height: 1
@ -585,8 +539,10 @@ describe('Address Service', function() {
var createReadStreamCallCount = 0;
am.node.services.db.store = {
createReadStream: function(ops) {
ops.start.should.equal([AddressService.PREFIXES.OUTPUTS, hashHex, 12].join('-'));
ops.end.should.equal([AddressService.PREFIXES.OUTPUTS, hashHex, 16].join('-'));
var gte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, new Buffer('000000000c', 'hex')]);
ops.gte.toString('hex').should.equal(gte.toString('hex'));
var lte = Buffer.concat([AddressService.PREFIXES.OUTPUTS, hashBuffer, new Buffer('0000000010', 'hex')]);
ops.lte.toString('hex').should.equal(lte.toString('hex'));
createReadStreamCallCount++;
return testStream;
}
@ -607,8 +563,8 @@ describe('Address Service', function() {
});
createReadStreamCallCount.should.equal(1);
var data = {
key: ['outs', address, '15', '125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87', '1'].join('-'),
value: ['4527773864', '76a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac'].join(':')
key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b687000000000f125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
};
testStream.emit('data', data);
testStream.emit('close');
@ -655,13 +611,13 @@ describe('Address Service', function() {
});
var data1 = {
key: ['outs', address, 345000, '125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87', '1'].join('-'),
value: ['4527773864', '76a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac'].join(':')
key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b68700000543a8125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf8700000001', 'hex'),
value: new Buffer('41f0de058a80000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
};
var data2 = {
key: ['outs', address, 345004, '3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', '2'].join('-'),
value: ['10000', '76a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac'].join(':')
key: new Buffer('32038a213afdfc551fc658e9a2a58a86e98d69b68700000543ac3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae700000002', 'hex'),
value: new Buffer('40c388000000000076a914038a213afdfc551fc658e9a2a58a86e98d69b68788ac', 'hex')
};
readStream1.emit('data', data1);