diff --git a/lib/service.js b/lib/service.js index 59a36d13..1ca4bcba 100644 --- a/lib/service.js +++ b/lib/service.js @@ -91,6 +91,10 @@ Service.prototype._createConcurrencyCache = function(opts) { this._concurrencyCache = LRU(opts || 500); }; +Service.prototype._createCache = function(opts) { + this._cache = LRU(opts || 500); +}; + Service.prototype._retrieveCachedItems = function(key, valueItem, prevKey, fn) { var self = this; diff --git a/lib/services/address/encoding.js b/lib/services/address/encoding.js index b22fcf58..e4a76908 100644 --- a/lib/services/address/encoding.js +++ b/lib/services/address/encoding.js @@ -64,40 +64,5 @@ Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) { return Buffer.concat(buffers); }; -Encoding.prototype.decodeUtxoIndexKey = function(buffer) { - var reader = new BufferReader(buffer); - reader.read(3); - - var addressSize = reader.readUInt8(); - var address = reader.read(addressSize).toString('utf8'); - var txid = reader.read(32).toString('hex'); - var outputIndex = reader.readUInt32BE(4); - - return { - address: address, - txid: txid, - outputIndex: outputIndex - }; -}; - -Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, scriptBuffer) { - var heightBuffer = new Buffer(4); - heightBuffer.writeUInt32BE(height); - var satoshisBuffer = new Buffer(8); - satoshisBuffer.writeDoubleBE(satoshis); - return Buffer.concat([heightBuffer, satoshisBuffer, scriptBuffer]); -}; - -Encoding.prototype.decodeUtxoIndexValue = function(buffer) { - var height = buffer.readUInt32BE(); - var satoshis = buffer.readDoubleBE(4); - var scriptBuffer = buffer.slice(12); - return { - height: height, - satoshis: satoshis, - script: scriptBuffer - }; -}; - module.exports = Encoding; diff --git a/lib/services/utxo/encoding.js b/lib/services/utxo/encoding.js new file mode 100644 index 00000000..d2b418ac --- /dev/null +++ b/lib/services/utxo/encoding.js @@ -0,0 +1,66 @@ +'use strict'; + +var BufferReader = bitcore.encoding.BufferReader; + +function Encoding(servicePrefix) { + this.servicePrefix = servicePrefix; +} + +Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) { + var prefix = new Buffer('01', 'hex'); + var buffers = [this.servicePrefix, prefix]; + + var addressSizeBuffer = new Buffer(1); + addressSizeBuffer.writeUInt8(address.length); + var addressBuffer = new Buffer(address, 'utf8'); + + buffers.push(addressSizeBuffer); + buffers.push(addressBuffer); + + var txidBuffer = new Buffer(txid || new Array(65).join('0'), 'hex'); + buffers.push(txidBuffer); + + var outputIndexBuffer = new Buffer(4); + outputIndexBuffer.writeUInt32BE(outputIndex || 0); + buffers.push(outputIndexBuffer); + + return Buffer.concat(buffers); +}; + +Encoding.prototype.decodeUtxoIndexKey = function(buffer) { + var reader = new BufferReader(buffer); + reader.read(3); + + var addressSize = reader.readUInt8(); + var address = reader.read(addressSize).toString('utf8'); + var txid = reader.read(32).toString('hex'); + var outputIndex = reader.readUInt32BE(4); + + return { + address: address, + txid: txid, + outputIndex: outputIndex + }; +}; + +Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, scriptBuffer) { + var heightBuffer = new Buffer(4); + heightBuffer.writeUInt32BE(height); + var satoshisBuffer = new Buffer(8); + satoshisBuffer.writeDoubleBE(satoshis); + return Buffer.concat([heightBuffer, satoshisBuffer, scriptBuffer]); +}; + +Encoding.prototype.decodeUtxoIndexValue = function(buffer) { + var height = buffer.readUInt32BE(); + var satoshis = buffer.readDoubleBE(4); + var scriptBuffer = buffer.slice(12); + return { + height: height, + satoshis: satoshis, + script: scriptBuffer + }; +}; + +module.exports = Encoding; + diff --git a/lib/services/utxo/index.js b/lib/services/utxo/index.js index 47b24164..d8e66ca0 100644 --- a/lib/services/utxo/index.js +++ b/lib/services/utxo/index.js @@ -3,11 +3,11 @@ var BaseService = require('../../service'); var inherits = require('util').inherits; var Encoding = require('./encoding'); +var LRU = require('lru-cache'); function UtxoService(options) { BaseService.call(this, options); - this._createConcurrencyCache({ max: 500000, dispose: this._getUtxoOperations.bind(this) }); - this._operations = []; + this._createCache({ max: 500000, dispose: this._getUtxoOperations.bind(this) }); } inherits(UtxoService, BaseService); @@ -35,59 +35,118 @@ UtxoService.prototype.stop = function(callback) { } }; -UtxoService.prototype.concurrencyBlockHandler = function(block, connect, callback) { +UtxoService.prototype._processInputs = function(inputs, block, connect) { + + var ret = []; + + for(var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + + var key = input.prevHash + input.outputIndex; + + if (input.prevTxId === tx.hash) { + ret.push(i); + continue; + } + + if (connect) { + self._removeSpentOutput(key); + } else { + self._readdUnspentOutput(key); + } + } + + return ret; +}; + +UtxoService.prototype._processOutputs = function(outputs, exclusions, block, connect) { + + for(var i = 0; i < outputs.length; i++) { + + var output = outputs[i]; + var key = tx.hash + i; + + if (exclusions.indexOf(i) > -1) { + continue; + } + + self._setCache(key, block, output); + + } + +}; + +UtxoService.prototype._setCache = function(key, block, output) { + + self._cache.set(key, { + output: output, + height: block.__height, + hash: block.hash, + }); // key = 36 bytes, value = (8 + 25ish) + 36 = 69 bytes + +}; + +UtxoService.prototype._removeSpentOutput = function(key) { + + var output = self._cache.peek(key); + + // we don't want nuke out our + if (!output) { + return { action: 'del', key: key }; + } + +}; + +UtxoService.prototype._readdSpentOutput = function(key) { + + var output = self._cache.get(key); + + if (!output) { + } +}; +/* +connect: + 1. for all txs, for each input in the tx, remove the output that this input is spending + +*/ +UtxoService.prototype.blockHandler = function(block, connect) { var self = this; - var reverseAction = connect ? 'del' : 'put'; - var action = connect ? 'put' : 'del'; + var operations = []; for(var i = 0; i < block.transactions.length; i++) { var tx = block.transactions[i]; var inputs = tx.inputs; var outputs = tx.outputs; - var skipOutput = []; + var inputOperations = {}; - for(var j = 0; j < inputs.length; j++) { - var input = inputs[j]; - - if (tx.isCoinbase()) { - continue; - } - - if (input.prevHash === tx.hash) { - skipOutput.push(input.outputIndex); - continue; - } - - self._concurrencyCache.del(input.prevHash + input.outputTndex); + if (!tx.isCoinbase()) { + inputOperations = self._processInputs(inputs, block, connect); } - for(var k = 0; k < inputs.length; k++) { - - if (skipOutput.indexOf(k) !== -1) { - continue; - } - - var output = outputs[k]; - self._concurrencyCache.set(tx.hash + k, { - output: output, - height: block.__height, - hash: block.hash - }); // key = 36 bytes, value = (8 + 25ish) + 36 = 69 bytes - } + var outputRes = self._processOutputs(outputs, inputRes.exclusions || [], block, connect); + operations = operations.concat(inputRes.operations || []).concat(outputRes.operations); } - setImmediate(callback); - + return operations; }; UtxoService.prototype._getUtxoOperations = function(key, value) { this._operations.push({ action: 'put', - key: this._getKey(key), - value: this._getValue(value) + key: this._getOperationsKey(key, value), + value: this._getOperationsValue(value) }); }; +UtxoService.prototype._getOperationsKey = function(key, value) { + var address = utils.getAddressFromScript(value.output.script); + return self.encoding.encodeUtxoIndexKey(address, key.slice(0, 32), parseInt(key.slice(32))); +}; + +UtxoService.prototype._getOperationsValue = function(value) { + return self.encoding.encodeUtxoIndexValue(value.height, value.output.satoshis, value.output.script); +}; + module.exports = UtxoService;