From dbc2fe5c3728c07978ac8528ce03d27bf2be2f5f Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 20 Feb 2014 16:10:10 -0300 Subject: [PATCH] add scriptpubkey to utxo --- app/models/Address.js | 56 ++++++++++++++++++++++++++------------ lib/Rpc.js | 13 +++++---- lib/TransactionDb.js | 11 ++++++-- test/integration/addr.js | 29 +++++++++++++++++++- test/integration/utxo.json | 16 +++++++++++ 5 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 test/integration/utxo.json diff --git a/app/models/Address.js b/app/models/Address.js index 45995d9d..aef9c255 100644 --- a/app/models/Address.js +++ b/app/models/Address.js @@ -4,10 +4,14 @@ require('classtool'); function spec() { - var async = require('async'); - var BitcoreAddress = require('bitcore/Address').class(); - var BitcoreUtil = require('bitcore/util/util'); - var TransactionDb = require('../../lib/TransactionDb').class(); + var async = require('async'); + var BitcoreAddress = require('bitcore/Address').class(); + var BitcoreUtil = require('bitcore/util/util'); + var TransactionDb = require('../../lib/TransactionDb').class(); + var BitcoreTransaction = require('bitcore/Transaction').class(); + var Parser = require('bitcore/util/BinaryParser').class(); + var Buffer = require('buffer').Buffer; + var CONCURRENCY = 5; function Address(addrStr) { this.balanceSat = 0; @@ -69,6 +73,14 @@ function spec() { } + Address.prototype._getScriptPubKey = function(hex,n) { + // ScriptPubKey is not provided by bitcoind RPC, so we parse it from tx hex. + + var parser = new Parser(new Buffer(hex,'hex')); + var tx = new BitcoreTransaction(); + tx.parse(parser); + return (tx.outs[n].s.toString('hex')); + }; Address.prototype.getUtxo = function(next) { var self = this; @@ -80,22 +92,30 @@ function spec() { db.fromAddr(self.addrStr, function(err,txOut){ if (err) return next(err); - txOut.forEach(function(txItem){ + // Complete utxo info + async.eachLimit(txOut,CONCURRENCY,function (txItem, a_c) { + db.fromIdInfoSimple(txItem.txid, function(err, info) { - // we are filtering out even unconfirmed spents! - // add || !txItem.spentIsConfirmed - if (!txItem.spentTxId) { - ret.push({ - address: self.addrStr, - txid: txItem.txid, - vout: txItem.index, - ts: txItem.ts, - amount: txItem.value_sat / BitcoreUtil.COIN, - confirmations: txItem.isConfirmed ? 1 : 0, // TODO => actually is 1+ - }); - } + var scriptPubKey = self._getScriptPubKey(info.hex, txItem.index); + + // we are filtering out even unconfirmed spents! + // add || !txItem.spentIsConfirmed + if (!txItem.spentTxId) { + ret.push({ + address: self.addrStr, + txid: txItem.txid, + vout: txItem.index, + ts: txItem.ts, + scriptPubKey: scriptPubKey, + amount: txItem.value_sat / BitcoreUtil.COIN, + confirmations: txItem.isConfirmed ? info.confirmations : 0, + }); + } + return a_c(err); + }); + }, function(err) { + return next(err,ret); }); - return next(err,ret); }); }; diff --git a/lib/Rpc.js b/lib/Rpc.js index 3025bdd0..428f619f 100644 --- a/lib/Rpc.js +++ b/lib/Rpc.js @@ -18,7 +18,7 @@ function spec(b) { Rpc._parseTxResult = function(info) { var b = new Buffer(info.hex,'hex'); - // remove fields we dont need, to speed, and adapt the information + // remove fields we dont need, to speed and adapt the information delete info['hex']; // Inputs => add index + coinBase flag @@ -53,17 +53,20 @@ function spec(b) { return e; }; - Rpc.getTxInfo = function(txid, cb) { + Rpc.getTxInfo = function(txid, doNotParse, cb) { var self = this; - bitcoreRpc.getRawTransaction(txid, 1, function(err, txInfo) { + if (typeof doNotParse === 'function') { + cb = doNotParse; + doNotParse = false; + } + bitcoreRpc.getRawTransaction(txid, 1, function(err, txInfo) { // Not found? if (err && err.code === -5) return cb(); if (err) return cb(self.errMsg(err)); - var info = self._parseTxResult(txInfo.result); - + var info = doNotParse ? txInfo.result : self._parseTxResult(txInfo.result); return cb(null,info); }); }; diff --git a/lib/TransactionDb.js b/lib/TransactionDb.js index 00c9faeb..03415402 100644 --- a/lib/TransactionDb.js +++ b/lib/TransactionDb.js @@ -267,13 +267,21 @@ isspent }; + // Simplified / faster Info version: No spent / outpoints info. + TransactionDb.prototype.fromIdInfoSimple = function(txid, cb) { + Rpc.getTxInfo(txid, true, function(err, info) { + if (err) return cb(err); + if (!info) return cb(); + return cb(err, info); + }); + }; + TransactionDb.prototype.fromIdWithInfo = function(txid, cb) { var self = this; self._getInfo(txid, function(err, info) { if (err) return cb(err); if (!info) return cb(); -//console.log('[TransactionDb.js.278:info:]',info); //TODO return cb(err, { txid: txid, info: info @@ -720,7 +728,6 @@ isspent return self.createFromArray(b.tx, b.hash, next); }; - return TransactionDb; } module.defineClass(spec); diff --git a/test/integration/addr.js b/test/integration/addr.js index 89db2b4d..fecc7360 100644 --- a/test/integration/addr.js +++ b/test/integration/addr.js @@ -8,7 +8,8 @@ var assert = require('assert'), fs = require('fs'), Address = require('../../app/models/Address').class(), TransactionDb = require('../../lib/TransactionDb').class(), - addrValid = JSON.parse(fs.readFileSync('test/integration/addr.json')); + addrValid = JSON.parse(fs.readFileSync('test/integration/addr.json')), + utxoValid = JSON.parse(fs.readFileSync('test/integration/utxo.json')); var txDb; describe('Address balances', function() { @@ -52,3 +53,29 @@ describe('Address balances', function() { }); }); + + +describe('Address utxo', function() { + utxoValid.forEach(function(v) { + if (v.disabled) { + console.log(v.addr + ' => disabled in JSON'); + } else { + it('Address utxo for: ' + v.addr, function(done) { + this.timeout(50000); + + var a = new Address(v.addr, txDb); + a.getUtxo(function(err, utxo) { + if (err) done(err); + assert.equal(v.addr, a.addrStr); + if (v.length) assert.equal(v.length, utxo.length, 'length: ' + utxo.length); + if (v.tx0id) assert.equal(v.tx0id, utxo[0].txid, 'have tx: ' + utxo[0].txid); + if (v.tx0scriptPubKey) + assert.equal(v.tx0scriptPubKey, utxo[0].scriptPubKey, 'have tx: ' + utxo[0].scriptPubKey); + if (v.tx0amount) + assert.equal(v.tx0amount, utxo[0].amount, 'amount: ' + utxo[0].amount); + done(); + }); + }); + } + }); +}); diff --git a/test/integration/utxo.json b/test/integration/utxo.json new file mode 100644 index 00000000..b38e4f00 --- /dev/null +++ b/test/integration/utxo.json @@ -0,0 +1,16 @@ +[ + { + "addr": "muyg1K5WsHkfMVCkUXU2y7Xp5ZD6RGzCeH", + "length": 1, + "tx0id": "eeabc70063d3f266e190e8735bc4599c811d3a79d138da1364e88502069b029c", + "tx0scriptPubKey": "76a9149e9f6515c70db535abdbbc983c7d8d1bff6c20cd88ac", + "tx0amount": 0.38571339 + }, + { + "addr": "mgKY35SXqxFpcKK3Dq9mW9919N7wYXvcFM", + "length": 1, + "tx0id": "91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5", + "tx0scriptPubKey": "76a91408cf4ceb2b7278043fcc7f545e6e6e73ef9a644f88ac", + "tx0amount": 0.01979459 + } +]