diff --git a/lib/services/utxo/encoding.js b/lib/services/utxo/encoding.js index 2343b1fd..2c11b798 100644 --- a/lib/services/utxo/encoding.js +++ b/lib/services/utxo/encoding.js @@ -1,15 +1,17 @@ 'use strict'; +var assert = require('assert'); var bitcore = require('bitcore-lib'); var BufferReader = bitcore.encoding.BufferReader; function Encoding(servicePrefix) { this.servicePrefix = servicePrefix; + this.nonP2PKPrefix = new Buffer('00', 'hex'); + this.P2PKPrefix = new Buffer('01', 'hex'); } Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) { - var prefix = new Buffer('01', 'hex'); - var buffers = [this.servicePrefix, prefix]; + var buffers = [this.servicePrefix, this.nonP2PKPrefix]; var addressSizeBuffer = new Buffer(1); addressSizeBuffer.writeUInt8(address.length); @@ -63,5 +65,56 @@ Encoding.prototype.decodeUtxoIndexValue = function(buffer) { }; }; +Encoding.prototype.encodeP2PKUtxoIndexKey = function(txid, outputIndex) { + var buffers = [this.servicePrefix, this.P2PKPrefix]; + + assert(txid && txid.length === 64, 'Txid required'); + assert(outputIndex >= 0, 'outputIndex required'); + + var txidBuffer = new Buffer(txid); + buffers.push(txidBuffer); + + var outputIndexBuffer = new Buffer(4); + outputIndexBuffer.writeUInt32BE(outputIndex); + buffers.push(outputIndexBuffer); + + return Buffer.concat(buffers); +}; + +Encoding.prototype.decodeP2PKUtxoIndexKey = 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.encodeP2PKUtxoIndexValue = 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.decodeP2PKUtxoIndexValue = 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 08863e79..7d9bdf05 100644 --- a/lib/services/utxo/index.js +++ b/lib/services/utxo/index.js @@ -3,8 +3,9 @@ var BaseService = require('../../service'); var inherits = require('util').inherits; var Encoding = require('./encoding'); -var LRU = require('lru-cache'); var utils = require('../../utils'); +var index = require('../../'); +var log = index.log; function UtxoService(options) { BaseService.call(this, options); @@ -79,7 +80,7 @@ UtxoService.prototype.getUtxosForAddress = function(address, callback) { address: address, height: value.height, satoshis: value.satoshis, - script: value.scriptBuffer + script: value.script }); }); @@ -92,17 +93,32 @@ UtxoService.prototype._processInputs = function(tx, inputs, connect) { var operations = []; for(var i = 0; i < inputs.length; i++) { - var input = inputs[i]; - var key = input.prevHash + input.outputIndex; + var input = inputs[i]; if (input.prevTxId === tx.hash) { this._exlusionIndexes.push(i); continue; } - var ops = this._moveOutput(key, connect); - operations = operations.concat(ops); + var key = this._getOperationsKey({ + script: input.script, + txid: input.prevTxId.toString('hex'), + index: input.outputIndex + }); + + if (key) { + var operation = connect ? { + type: 'del', + key: key + } : { + type: 'put', + key: key, + value: new Buffer('00', 'hex') + }; + + operations.push(operation); + } } return operations; @@ -115,42 +131,70 @@ UtxoService.prototype._processOutputs = function(tx, outputs, block, connect) { for(var i = 0; i < outputs.length; i++) { var output = outputs[i]; - var key = tx.hash + i; if (this._exclusionIndexes.indexOf(i) > -1) { continue; } - var action = connect ? 'put' : 'del'; - operations = operations.concat( - { action: action, - key: this._getOperationsKey(key, output), - value: this._getOperationsValue({ - height: block.__height, - satoshis: output.satoshis, - script: output.script - }) - } - ); + var key = this._getOperationsKey({ + script: output.script, + txid: tx.id, + index: i + }); + + var value = this._getOperationsValue({ + height: block.__height, + satoshis: output.satoshis, + script: output.script + }); + + if (key && value) { + var operation = connect ? { + type: 'put', + key: key, + value: value + } : { + type: 'del', + key: key, + value: value + }; + + operations.push(operation); + } + + } return operations; }; -UtxoService.prototype._moveOutput = function(key, connect) { +UtxoService.prototype._getOperationsKey = function(io) { - if (connect) { - return { action: 'del', key: key }; + var address = utils.getAddressStringFromScript(io.script, this.node.network); + + if (!address) { + var key = this._tryP2PKOperation(io); + if (key) { + return key; + } } - return { action: 'put', key: key, value: null }; + if (!address) { + log.debug('could not determine address for script: ' + io.script.toString()); + return; + } + return this.encoding.encodeUtxoIndexKey(address, io.txid, io.index); }; -UtxoService.prototype._getOperationsKey = function(key, output) { - var address = utils.getAddressStringFromScript(output.script, this.node.network); - return this.encoding.encodeUtxoIndexKey(address, key.slice(0, 64), parseInt(key.slice(64))); +UtxoService.prototype._tryP2PKOperation = function(io) { + + // checking for a scriptSig that has one signature + var sig = io.script.chunks[0]; + if (sig && (sig.len > 69 && sig.len < 75)) { + return this.encoding.encodeP2PKUtxoIndexKey(io.txid, io.index); + } }; UtxoService.prototype._getOperationsValue = function(value) { diff --git a/regtest/utxo.js b/regtest/utxo.js index 388185ec..b73598be 100644 --- a/regtest/utxo.js +++ b/regtest/utxo.js @@ -121,6 +121,7 @@ describe('Utxo Operations', function() { utils.waitForBitcoinReady.bind(utils, self.opts), utils.unlockWallet.bind(utils, self.opts), utils.setupInitialTxs.bind(utils, self.opts), + utils.sendTxs.bind(utils, self.opts), utils.startBitcoreNode.bind(utils, self.opts), utils.waitForBitcoreNode.bind(utils, self.opts) ], done); @@ -140,6 +141,8 @@ describe('Utxo Operations', function() { res = JSON.parse(res); expect(res.address).to.equal(address); + expect(res.utxos.length).equal(1); + expect(Object.keys(res.utxos[0])).to.deep.equal([ 'txid', 'outputIndex', 'address', 'height', 'satoshis', 'script' ]); next(null, res.utxos); }); }, function(err, utxos) { @@ -147,7 +150,6 @@ describe('Utxo Operations', function() { if(err) { return done(err); } -console.log('done'); done();