Merge pull request #385 from matiu/feature/unspent-outputs

add scriptpubkey to utxo
This commit is contained in:
Mario Colque 2014-02-20 19:55:34 -02:00
commit adfd363c76
5 changed files with 99 additions and 26 deletions

View File

@ -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);
});
};

View File

@ -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);
});
};

View File

@ -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);

View File

@ -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();
});
});
}
});
});

View File

@ -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
}
]