From 3fb4383287bf5c092d30b188f772ead899ab0cf4 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 9 Jan 2014 20:21:16 -0300 Subject: [PATCH 01/19] test for Addr API. Go TDD! --- test/model/addr.js | 109 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 test/model/addr.js diff --git a/test/model/addr.js b/test/model/addr.js new file mode 100644 index 0000000..815f7bc --- /dev/null +++ b/test/model/addr.js @@ -0,0 +1,109 @@ +#!/usr/bin/env node + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + + +var addrs = + [ + { addr: 'mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H', + balance: 46413.0, + totalReceived: 357130.17644359, + totalSent: 310717.17644359, + }, + { addr: 'mgKY35SXqxFpcKK3Dq9mW9919N7wYXvcFM', + balance: 0.01979459, + totalReceived: 0.01979459, + totalSent: 0, + transactions: [ '91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5' ], + }, + { addr: 'mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5', + balance: 10580.50027254, + totalReceived: 12157.65075053, + totalSent: 1577.15047799, + transactions: [ + '91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5', + 'f6e80d4fd1a2377406856c67d0cee5ac7e5120993ff97e617ca9aac33b4c6b1e', + 'bc27f31caae86750b126d9b09e969362b85b7c15f41421387d682064544bf7e7', + '2cd6a1cb26880276fbc9851396f1bd8081cb2b9107ff6921e8fd65ed2df3df79', + '8bea41f573bccb7b648bc0b1bbfeba8a96da05b1d819ff4a33d39fbcd334ecfd', + 'cb0d55c37acc57f759255193673e13858b5ab3d8fdfa7ee8b25f9964bdaa11e3', + '7b007aeace2299d27b6bb6c24d0a8040d6a87e4c2601216c34d226462b75f915', + 'a9f40fbaecd2b28a05405e28b95566d7b3bd8ac38a2853debd72517f2994c6fc', + '4123255b7678e37c168b9e929927760bc5d9363b0c78ec61a7b4a78b2a07adab', + 'cb3760529c2684c32047a2fddf0e2534c9241e5d72011aac4a8982e0c7b46df3', + 'e8d00d8cc744381233dbc95e2d657345084dfb6df785b81285183f4c89b678d4', + '7a748364255c5b64979d9d3da35ea0fbef0114e0d7f96fccd5bea76f6d19f06b', + 'd0b7e087040f67ef9bd9f21ccf53d1b5410400351d949cabf127caf28a6e7add', + '209f97873265652b83922921148cad92d7e048c6822e4864e984753e04181470', + '3a4af7755d3061ecced2f3707c2623534104f08aa73a52ca243d7ddecf5fe86d', + '4a4b5c8d464a77814ed35a37e2a28e821d467a803761427c057f67823309b725', + 'd85f5265618fb694c3ea3ca6f73eba93df8a644bc1c7286cec2fbc2fbf7d895e', + '0d2c778ed9976b52792c941cac126bda37d3b1453082022d5e36ac401be3b249', + 'daf03d666047ca0b5340b4a0027f8562b7c5bac87dca3727093b5393176a541a', + 'a0dc03a870e589ea51e3d3a8aed0d34f4f1ae6844acad26dae48fe523b26e764', + '3df1a50e2e5d8525f04bd21a66bad824364a975449fa24fd5c2537d0f713919b', + '7bc26c1f3b4ab5ca57677593d28d13bff468a658f4d5efc379c1612554cf668e', + 'ded4cbc9c52fd5599b6a93f89a79cde9aeb5a7f8f56732bb67ae9554325b3666', + '91224a219196a3f6e6f40ad2137b13fe54109e57aaed7527ea34aa903e6b8313', + 'ee899a182bbb75e98ef14d83489e631dd66a8c5059dc8255692dd8ca9efba01f', + '0a61590c7548bd4f6a0df1575b268057e5e3e295a44eaeeb1dfbd01332c585ed', + 'd56c22950ad2924f404b5b0baa6e49b0df1aaf09d1947842aed9d0178958eb9d', + 'c6b5368c5a256141894972fbd02377b3894aa0df7c35fab5e0eca90de064fdc1', + '158e1f9c3f8ec44e88052cadef74e8eb99fbad5697d0b005ba48c933f7d96816', + '7f6191c0f4e3040901ef0d5d6e76af4f16423061ca1347524c86205e35d904d9', + '2c2e20f976b98a0ca76c57eca3653699b60c1bd9503cc9cc2fb755164a679a26', + '59bc81733ff0eaf2b106a70a655e22d2cdeff80ada27b937693993bf0c22e9ea', + '7da38b66fb5e8582c8be85abecfd744a6de89e738dd5f3aaa0270b218ec424eb', + '393d51119cdfbf0a308c0bbde2d4c63546c0961022bad1503c4bbaed0638c837', + '4518868741817ae6757fd98de27693b51fad100e89e5206b9bbf798aeebb804c', + 'c58bce14de1e3016504babd8bbe8175207d75074134a2548a71743fa3e56c58d', + '6e69ec4a97515a8fd424f123a5fc1fdfd3c3adcd741292cbc09c09a2cc433bea', + '0e15f2498362050e5ceb6157d0fbf820fdcaf936e447207d433ee7701d7b99c2', + 'a3789e113041db907a1217ddb5c3aaf0eff905cc3d913e68d977e1ab4d19acea', + '80b460922faf0ad1e8b8a55533654c9a9f3039bfff0fff2bcf8536b8adf95939', + ] + }, + ]; + +var + mongoose= require('mongoose'), + assert = require('assert'), + config = require('../../config/config'), + Address = require('../../app/models/Address'); + + +mongoose.connection.on('error', function(err) { console.log(err); }); + +describe('Address fromAddrWithInfo', function(){ + + before(function(done) { + mongoose.connect(config.db); + done(); + }); + + after(function(done) { + mongoose.connection.close(); + done(); + }); + + + addrs.forEach( function(t) { + it('should retrieve the correct info for' + t.addr, function(done) { + Addr.fromHashWithInfo(t.addr, function(err, a) { + if (err) done(err); + + assert.equal(t.balance, a.balance, "balance"); + assert.equal(t.totalReceived, a.totalReceived, "totalReceived"); + assert.equal(t.totalSent, a.totalSent, "totalSent"); + if (t.transactions) { + t.transactions.forEach( function(tx) { + assert(tx in a.inTransactions); + }); + } + done(); + }); + }); + }); + +}); + From 0700cd1cd98e6b97d7f0199e9aa8014f487b52c7 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 9 Jan 2014 20:24:14 -0300 Subject: [PATCH 02/19] simple Address model class created --- app/models/Address.js | 94 +++++++++++++++++++++++++++++++++++++++++++ test/model/addr.js | 2 +- 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 app/models/Address.js diff --git a/app/models/Address.js b/app/models/Address.js new file mode 100644 index 0000000..68a2979 --- /dev/null +++ b/app/models/Address.js @@ -0,0 +1,94 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + RpcClient = require('bitcore/RpcClient').class(), + config = require('../../config/config') + ; + +/** + * Block Schema + */ +var AddressSchema = new Schema({ + + // For now we keep this as short as possible + // More fields will be propably added as we move + // forward with the UX + addr: { + type: String, + index: true, + unique: true, + }, + balance: Number, + totalReceived: Number, + totalSent: Number, + inTransactions: [String], +}); + + +/** + * Validations + */ + +/* +AddressSchema.path('title').validate(function(title) { + return title.length; +},'Title cannot be blank'); +*/ + +/** + * Statics + */ + +AddressSchema.statics.load = function(id, cb) { + this.findOne({ + _id: id + }).exec(cb); +}; + + +AddressSchema.statics.fromAddr = function(hash, cb) { + this.findOne({ + hash: hash, + }).exec(cb); +}; + + +AddressSchema.statics.fromAddrWithInfo = function(hash, cb) { + this.fromHash(hash, function(err, block) { + if (err) return cb(err); + if (!block) { return cb(new Error('Block not found')); } + + block.getInfo(function(err) { return cb(err,block); } ); + }); +}; + + + +// TODO: Can we store the rpc instance in the Block object? +AddressSchema.methods.getInfo = function (next) { + + var that = this; + var rpc = new RpcClient(config.bitcoind); + + rpc.getBlock(this.hash, function(err, blockInfo) { + if (err) return next(err); + + /* + * Not sure this is the right way to do it. + * Any other way to lazy load a property in a mongoose object? + */ + + that.info = blockInfo.result; + + //console.log("THAT", that); + return next(null, that.info); + }); +}; + + + +module.exports = mongoose.model('Address', AddressSchema); diff --git a/test/model/addr.js b/test/model/addr.js index 815f7bc..ccb560e 100644 --- a/test/model/addr.js +++ b/test/model/addr.js @@ -89,7 +89,7 @@ describe('Address fromAddrWithInfo', function(){ addrs.forEach( function(t) { it('should retrieve the correct info for' + t.addr, function(done) { - Addr.fromHashWithInfo(t.addr, function(err, a) { + Address.fromHashWithInfo(t.addr, function(err, a) { if (err) done(err); assert.equal(t.balance, a.balance, "balance"); From 7cde88ddce7d2f50b23ad24ef230c7a0a3f7aabb Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 10 Jan 2014 16:57:27 -0300 Subject: [PATCH 03/19] ignore /lib in grunt/nodemon --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 32cf055..a386491 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -66,7 +66,7 @@ module.exports = function(grunt) { options: { file: 'server.js', args: [], - ignoredFiles: ['public/**', 'test/**','util/**'], + ignoredFiles: ['public/**', 'test/**','util/**','lib/**'], watchedExtensions: ['js'], // nodeArgs: ['--debug'], delayTime: 1, From 742adf61865599d0e3e4c707f1d5aef35987428e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 10 Jan 2014 16:59:20 -0300 Subject: [PATCH 04/19] fix test env variables --- app/models/Transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/Transaction.js b/app/models/Transaction.js index 61c0605..7f6014e 100644 --- a/app/models/Transaction.js +++ b/app/models/Transaction.js @@ -46,10 +46,10 @@ TransactionSchema.statics.fromId = function(txid, cb) { }).exec(cb); }; + TransactionSchema.statics.fromIdWithInfo = function(txid, cb) { // TODO Should we go to mongoDB first? Now, no extra information is stored at mongo. - this.fromId(txid, function(err, tx) { if (err) return cb(err); From 63063f906b681f2ae404126446530ce7921f4515 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 10 Jan 2014 19:55:16 -0300 Subject: [PATCH 05/19] correction in README --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a85ed9d..d1f8411 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,6 @@ $ npm install -g bower http://localhost:3000 -## API - -A REST API is provided at /api. The entry points are: - ### Prerequisites Get bitcore from github repository: @@ -73,6 +69,12 @@ A REST API is provided at /api. The entry points are: Check utils/sync.js --help for options. + +## API + +A REST API is provided at /api. The entry points are: + + ### Blocks ``` /api/block/[:hash] From b5234cc2d0a21ca2816907b37d8c2bf80a6e2267 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 10 Jan 2014 19:55:35 -0300 Subject: [PATCH 06/19] test for addr --- test/model/addr.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/model/addr.js b/test/model/addr.js index ccb560e..55ae06b 100644 --- a/test/model/addr.js +++ b/test/model/addr.js @@ -5,6 +5,9 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development'; var addrs = [ + { addr: 'mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H', + }, + /* { addr: 'mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H', balance: 46413.0, totalReceived: 357130.17644359, @@ -63,6 +66,7 @@ var addrs = '80b460922faf0ad1e8b8a55533654c9a9f3039bfff0fff2bcf8536b8adf95939', ] }, + */ ]; var @@ -92,9 +96,10 @@ describe('Address fromAddrWithInfo', function(){ Address.fromHashWithInfo(t.addr, function(err, a) { if (err) done(err); - assert.equal(t.balance, a.balance, "balance"); - assert.equal(t.totalReceived, a.totalReceived, "totalReceived"); - assert.equal(t.totalSent, a.totalSent, "totalSent"); + assert.equal(t.addr, a.addr); + if (t.balance) assert.equal(t.balance, a.balance, "balance"); + if (t.totalReceived) assert.equal(t.totalReceived, a.totalReceived, "totalReceived"); + if (t.totalSent) assert.equal(t.totalSent, a.totalSent, "totalSent"); if (t.transactions) { t.transactions.forEach( function(tx) { assert(tx in a.inTransactions); From 9a3feeb10b3e738389903f980de2ba86e05b67cf Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 10 Jan 2014 19:58:12 -0300 Subject: [PATCH 07/19] correction in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1f8411..9843a23 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ There is a bitcoind configuration sample at: If you want to use a external bitcoind server set BITCOIND_HOST / BITCOIND_PORT enviroment variables. Make sure that bitcoind is configured to accept incomming connections using 'rpcallowip' decribed in https://en.bitcoin.it/wiki/Running_Bitcoin. -### Environmental Settings +### Environment Variables Settings There are three environments provided by default, __development__, __test__, and __production__. Each of these environments has the following configuration options: * __db__ - This is the name of the MongoDB database to use, and is set by default to __mystery-dev__ for the development environment. From e93befe6f7f1bd2ff67da5ce24bbf72adf38eaff Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 10 Jan 2014 21:42:39 -0300 Subject: [PATCH 08/19] sync with TX items!!! --- app/models/Address.js | 63 ++++------------ app/models/Transaction.js | 38 ++++++---- app/models/TransactionItem.js | 35 +++++++++ lib/Sync.js | 136 ++++++++++++++++++++++++++++++---- 4 files changed, 195 insertions(+), 77 deletions(-) create mode 100644 app/models/TransactionItem.js diff --git a/app/models/Address.js b/app/models/Address.js index 68a2979..f4dad6b 100644 --- a/app/models/Address.js +++ b/app/models/Address.js @@ -4,13 +4,11 @@ * Module dependencies. */ var mongoose = require('mongoose'), - Schema = mongoose.Schema, - RpcClient = require('bitcore/RpcClient').class(), - config = require('../../config/config') + Schema = mongoose.Schema ; /** - * Block Schema + * Addr Schema */ var AddressSchema = new Schema({ @@ -22,26 +20,17 @@ var AddressSchema = new Schema({ index: true, unique: true, }, - balance: Number, - totalReceived: Number, - totalSent: Number, - inTransactions: [String], + inputs: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'TransactionItem' //Edit: I'd put the schema. Silly me. + }], + output: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'TransactionItem' //Edit: I'd put the schema. Silly me. + }], }); -/** - * Validations - */ - -/* -AddressSchema.path('title').validate(function(title) { - return title.length; -},'Title cannot be blank'); -*/ - -/** - * Statics - */ AddressSchema.statics.load = function(id, cb) { this.findOne({ @@ -58,37 +47,13 @@ AddressSchema.statics.fromAddr = function(hash, cb) { AddressSchema.statics.fromAddrWithInfo = function(hash, cb) { - this.fromHash(hash, function(err, block) { + this.fromHash(hash, function(err, addr) { if (err) return cb(err); - if (!block) { return cb(new Error('Block not found')); } - - block.getInfo(function(err) { return cb(err,block); } ); + if (!addr) { return cb(new Error('Addr not found')); } +// TODO +// addr.getInfo(function(err) { return cb(err,addr); } ); }); }; - -// TODO: Can we store the rpc instance in the Block object? -AddressSchema.methods.getInfo = function (next) { - - var that = this; - var rpc = new RpcClient(config.bitcoind); - - rpc.getBlock(this.hash, function(err, blockInfo) { - if (err) return next(err); - - /* - * Not sure this is the right way to do it. - * Any other way to lazy load a property in a mongoose object? - */ - - that.info = blockInfo.result; - - //console.log("THAT", that); - return next(null, that.info); - }); -}; - - - module.exports = mongoose.model('Address', AddressSchema); diff --git a/app/models/Transaction.js b/app/models/Transaction.js index 7f6014e..b813187 100644 --- a/app/models/Transaction.js +++ b/app/models/Transaction.js @@ -27,6 +27,14 @@ var TransactionSchema = new Schema({ index: true, unique: true, }, + processed: { + type: Boolean, + default: false, + }, + orphaned: { + type: Boolean, + default: false, + }, }); /** @@ -154,22 +162,26 @@ TransactionSchema.methods.queryInfo = function (next) { } else { tx.ins.forEach(function(i) { + + if (i.value) { + that.info.vin[c].value = util.formatValue(i.value); + var n = util.valueToBigInt(i.value).toNumber(); + valueIn = valueIn.add( n ); - that.info.vin[c].value = util.formatValue(i.value); - var n = util.valueToBigInt(i.value).toNumber(); - valueIn = valueIn.add( n ); + var scriptSig = i.getScript(); + var pubKey = scriptSig.simpleInPubKey(); - var scriptSig = i.getScript(); - var pubKey = scriptSig.simpleInPubKey(); - - // We check for pubKey in case a broken / strange TX. - if (pubKey) { - var pubKeyHash = util.sha256ripe160(pubKey); - var addr = new Address(network.addressPubkey, pubKeyHash); - var addrStr = addr.toString(); - that.info.vin[c].addr = addrStr; + // We check for pubKey in case a broken / strange TX. + if (pubKey) { + var pubKeyHash = util.sha256ripe160(pubKey); + var addr = new Address(network.addressPubkey, pubKeyHash); + var addrStr = addr.toString(); + that.info.vin[c].addr = addrStr; + } + } + else { + console.log("TX could not be parsed: %s,%d",txInfo.result.txid, c); } - c++; }); } diff --git a/app/models/TransactionItem.js b/app/models/TransactionItem.js new file mode 100644 index 0000000..db06927 --- /dev/null +++ b/app/models/TransactionItem.js @@ -0,0 +1,35 @@ +'use strict'; + +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + +var TransactionItemSchema = new Schema({ + txid: String, + index: Number, + addr: { + type: String, + index: true, + }, + // >0 is Input <0 is Output + value: Number, +}); + + + +TransactionItemSchema.statics.load = function(id, cb) { + this.findOne({ + _id: id + }).exec(cb); +}; + + +TransactionItemSchema.statics.fromAddr = function(addr, cb) { + this.find({ + addr: addr, + }).exec(cb); +}; + +module.exports = mongoose.model('TransactionItem', TransactionItemSchema); diff --git a/lib/Sync.js b/lib/Sync.js index 0fcdc8f..1dd4a47 100644 --- a/lib/Sync.js +++ b/lib/Sync.js @@ -2,21 +2,17 @@ require('classtool'); -/* We dont sync any contents from TXs, only their IDs are stored */ - -var isSyncTxEnabled = 0; function spec() { - var mongoose = require('mongoose'); - var util = require('util'); - - var RpcClient = require('bitcore/RpcClient').class(); - var networks = require('bitcore/networks'); - var async = require('async'); - - var config = require('../config/config'); - var Block = require('../app/models/Block'); - var Transaction = require('../app/models/Transaction'); + var mongoose = require('mongoose'); + var util = require('util'); + var RpcClient = require('bitcore/RpcClient').class(); + var networks = require('bitcore/networks'); + var async = require('async'); + var config = require('../config/config'); + var Block = require('../app/models/Block'); + var Transaction = require('../app/models/Transaction'); + var TransactionItem = require('../app/models/TransactionItem'); function Sync(config) { this.network = config.networkName === 'testnet' ? networks.testnet: networks.livenet; @@ -151,6 +147,105 @@ function spec() { }); }; + + Sync.prototype.processTXs = function(reindex, cb) { + + var that = this; + + console.log('Syncing TXs...'); + + var filter = reindex ? {} : { processed: false } ; + + Transaction.find(filter, + function(err, txs) { + if (err) return cb(err); + + var read = 0, + pull = 0, + write = 0, + total = txs.length; + + console.log('\tneed to pull %d txs', total); + + if (!total) return cb(); + + async.each(txs, function(tx, next) { + if (read++ % 1000 === 0) progress_bar('read', read, total); + + if (!tx.txid) { + console.log('NO TXID skipping...', tx); + return next(); + } + + + // This will trigger an RPC call + Transaction.fromIdWithInfo( tx.txid, function(err,t) { + if (pull++ % 1000 === 0) progress_bar('\tpull', pull, total); + + if (!err && t) { + var index = 0; + + async.each(t.info.vin, function(i, next_in) { + + /* + * TODO Support multisigs??? + * how?? + */ + + if (i.addr && i.value) { + TransactionItem.create({ + txid : t.txid, + value : -1 * i.value, + addr : i.addr, + index : i.n, + }, next_in); + } + else { + if ( !i.coinbase ) + console.log ("TX: %s seems to be multisig IN. Skipping... ", t.txid); + return next_in(); + } + }, + function (err) { + if (err) console.log (err); + index = 0; + async.each(t.info.vout, function(o, next_out) { + + /* + * TODO Support multisigs + */ + if (o.value && o.scriptPubKey + && o.scriptPubKey.addresses + && o.scriptPubKey.addresses[0] + ) { + TransactionItem.create({ + txid : t.txid, + value : o.value, + addr : o.scriptPubKey.addresses[0], + index : o.n, + }, next_out); + } + else { + console.log ("TX: %s,%d seems to be multisig OUT. Skipping... ", t.txid, o.n); + return next_out(); + } + }, + function (err) { + if (err) console.log (err); + if (write++ % 1000 === 0) progress_bar('\t\twrite', write, total); + return next(); + }); + }); + } + else return next(); + }); + }, + function(err) { + return cb(err); + }); + }); + }; + Sync.prototype.init = function(opts) { if (!(opts && opts.skip_db_connection)) { mongoose.connect(config.db); @@ -191,13 +286,24 @@ function spec() { } }, function(cb) { - if (isSyncTxEnabled && ! opts.skip_txs) { + if (! opts.skip_txs) { + that.processTXs(opts.reindex, cb); + } + else { + return cb(); + } + } +/* We dont sync any contents from TXs, only their IDs are stored + function(cb) { + if (! opts.skip_txs) { that.syncTXs(opts.reindex, cb); } else { return cb(); } - }], function(err) { + } +*/ + ], function(err) { return next(err); }); }); From 518b69a94cb0d14d02602422364d8e5d9f742b96 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sat, 11 Jan 2014 02:12:05 -0300 Subject: [PATCH 09/19] add proper ctags file --- .ctags | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .ctags diff --git a/.ctags b/.ctags new file mode 100644 index 0000000..fa51e9e --- /dev/null +++ b/.ctags @@ -0,0 +1,11 @@ +--extra=+f +--exclude=*jquery* +--exclude=node_modules/a* +--exclude=node_modules/[c-z]* +--exclude=*grunt* +--exclude=*bower* +--exclude=.swp +--exclude=public +--links=yes +--totals=yes + From 7bf54aadf60e6af25af7cef3813b8ed01e634a69 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sat, 11 Jan 2014 22:57:33 -0300 Subject: [PATCH 10/19] transactionitem multikey index --- app/models/TransactionItem.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/TransactionItem.js b/app/models/TransactionItem.js index db06927..66a825f 100644 --- a/app/models/TransactionItem.js +++ b/app/models/TransactionItem.js @@ -18,6 +18,10 @@ var TransactionItemSchema = new Schema({ }); +// Compound index +TransactionItemSchema.index({txid: 1, index: 1, value: 1}, {unique: true, dropDups: true}); + + TransactionItemSchema.statics.load = function(id, cb) { this.findOne({ From 28e082b53bfeabbc51fdbf7f15805b659e3599bc Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sat, 11 Jan 2014 22:58:00 -0300 Subject: [PATCH 11/19] move txitem creation to transaction model --- app/models/Transaction.js | 63 +++++++++++++++++++++++++++- lib/Sync.js | 88 +++++++-------------------------------- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/app/models/Transaction.js b/app/models/Transaction.js index b813187..87667c1 100644 --- a/app/models/Transaction.js +++ b/app/models/Transaction.js @@ -13,7 +13,8 @@ var mongoose = require('mongoose'), networks = require('bitcore/networks'), util = require('bitcore/util/util'), bignum = require('bignum'), - config = require('../../config/config'); + config = require('../../config/config'), + TransactionItem = require('./TransactionItem'); /** @@ -30,6 +31,7 @@ var TransactionSchema = new Schema({ processed: { type: Boolean, default: false, + index: true, }, orphaned: { type: Boolean, @@ -67,6 +69,8 @@ TransactionSchema.statics.fromIdWithInfo = function(txid, cb) { }); }; + + TransactionSchema.statics.createFromArray = function(txs, next) { var that = this; if (!txs) return next(); @@ -87,6 +91,63 @@ TransactionSchema.statics.createFromArray = function(txs, next) { }; +TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { + + this.fromIdWithInfo(txid, function(err, t) { + if (err || !t) return cb(err); + + async.each(t.info.vin, function(i, next_in) { + + /* + * TODO Support multisigs??? + */ + + if (i.addr && i.value) { + TransactionItem.create({ + txid : t.txid, + value : -1 * i.value, + addr : i.addr, + index : i.n, + }, next_in); + } + else { + if ( !i.coinbase ) + console.log ("TX: %s seems to be multisig IN. Skipping... ", t.txid); + return next_in(); + } + }, + function (err) { + if (err) console.log (err); + async.each(t.info.vout, function(o, next_out) { + + /* + * TODO Support multisigs + */ + if (o.value && o.scriptPubKey + && o.scriptPubKey.addresses + && o.scriptPubKey.addresses[0] + ) { + TransactionItem.create({ + txid : t.txid, + value : o.value, + addr : o.scriptPubKey.addresses[0], + index : o.n, + }, next_out); + } + else { + console.log ("TX: %s,%d seems to be multisig OUT. Skipping... ", t.txid, o.n); + return next_out(); + } + }, + function (err) { + return cb(err); + }); + }); + }); +}; + + + TransactionSchema.methods.fillInputValues = function (tx, next) { if (tx.isCoinBase()) return next(); diff --git a/lib/Sync.js b/lib/Sync.js index 1dd4a47..4474540 100644 --- a/lib/Sync.js +++ b/lib/Sync.js @@ -156,20 +156,19 @@ function spec() { var filter = reindex ? {} : { processed: false } ; - Transaction.find(filter, - function(err, txs) { - if (err) return cb(err); + Transaction.find(filter, function(err, txs) { + if (err) return cb(err); - var read = 0, - pull = 0, - write = 0, - total = txs.length; + var read = 0, + pull = 0, + proc = 0, + total = txs.length; - console.log('\tneed to pull %d txs', total); + console.log('\tneed to pull %d txs', total); - if (!total) return cb(); + if (!total) return cb(); - async.each(txs, function(tx, next) { + async.each(txs, function(tx, next) { if (read++ % 1000 === 0) progress_bar('read', read, total); if (!tx.txid) { @@ -177,73 +176,14 @@ function spec() { return next(); } - // This will trigger an RPC call - Transaction.fromIdWithInfo( tx.txid, function(err,t) { - if (pull++ % 1000 === 0) progress_bar('\tpull', pull, total); - - if (!err && t) { - var index = 0; - - async.each(t.info.vin, function(i, next_in) { - - /* - * TODO Support multisigs??? - * how?? - */ - - if (i.addr && i.value) { - TransactionItem.create({ - txid : t.txid, - value : -1 * i.value, - addr : i.addr, - index : i.n, - }, next_in); - } - else { - if ( !i.coinbase ) - console.log ("TX: %s seems to be multisig IN. Skipping... ", t.txid); - return next_in(); - } - }, - function (err) { - if (err) console.log (err); - index = 0; - async.each(t.info.vout, function(o, next_out) { - - /* - * TODO Support multisigs - */ - if (o.value && o.scriptPubKey - && o.scriptPubKey.addresses - && o.scriptPubKey.addresses[0] - ) { - TransactionItem.create({ - txid : t.txid, - value : o.value, - addr : o.scriptPubKey.addresses[0], - index : o.n, - }, next_out); - } - else { - console.log ("TX: %s,%d seems to be multisig OUT. Skipping... ", t.txid, o.n); - return next_out(); - } - }, - function (err) { - if (err) console.log (err); - if (write++ % 1000 === 0) progress_bar('\t\twrite', write, total); - return next(); - }); - }); - } - else return next(); + Transaction.explodeTransactionItems( tx.txid, function(err) { + if (proc++ % 1000 === 0) progress_bar('\tproc', pull, total); + next(err); }); }, - function(err) { - return cb(err); - }); - }); + cb); + }); }; Sync.prototype.init = function(opts) { From 9193ae174600c6b15a9517da84f83782fd8c4fa7 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 12 Jan 2014 00:44:37 -0300 Subject: [PATCH 12/19] test for txitem explode --- test/model/transaction.js | 68 ++++++++++++++++++++++++++++----------- test/model/txitems.json | 22 +++++++++++++ 2 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 test/model/txitems.json diff --git a/test/model/transaction.js b/test/model/transaction.js index 0b3e18d..6a50547 100644 --- a/test/model/transaction.js +++ b/test/model/transaction.js @@ -8,12 +8,15 @@ var mongoose= require('mongoose'), assert = require('assert'), config = require('../../config/config'), - Transaction = require('../../app/models/Transaction'); + Transaction = require('../../app/models/Transaction'), + TransactionItem = require('../../app/models/TransactionItem'), + fs = require('fs'); +var txItemsValid = JSON.parse(fs.readFileSync('test/model/txitems.json')); mongoose.connection.on('error', function(err) { console.log(err); }); -describe('Transaction fromIdWithInfo', function(){ +describe('Transaction', function(){ before(function(done) { mongoose.connect(config.db); @@ -26,20 +29,20 @@ describe('Transaction fromIdWithInfo', function(){ }); it('should pool tx\'s object from mongoose', function(done) { - var test_txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237'; - Transaction.fromIdWithInfo(test_txid, function(err, tx) { + var txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237'; + Transaction.fromIdWithInfo(txid, function(err, tx) { if (err) done(err); - assert.equal(tx.txid, test_txid); + assert.equal(tx.txid, txid); assert(!tx.info.isCoinBase); done(); }); }); it('should pool tx\'s info from bitcoind', function(done) { - var test_txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237'; - Transaction.fromIdWithInfo(test_txid, function(err, tx) { + var txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237'; + Transaction.fromIdWithInfo(txid, function(err, tx) { if (err) done(err); - assert.equal(tx.info.txid, test_txid); + assert.equal(tx.info.txid, txid); assert.equal(tx.info.blockhash, '000000000185678d3d7ecc9962c96418174431f93fe20bf216d5565272423f74'); assert.equal(tx.info.valueOut, 1.66174); assert.equal(tx.info.feeds, 0.0005 ); @@ -49,28 +52,57 @@ describe('Transaction fromIdWithInfo', function(){ }); }); - it('test a coinbase TX 2a104bab1782e9b6445583296d4a0ecc8af304e4769ceb64b890e8219c562399', function(done) { - var test_txid2 = '2a104bab1782e9b6445583296d4a0ecc8af304e4769ceb64b890e8219c562399'; - Transaction.fromIdWithInfo(test_txid2, function(err, tx) { + var txid1 = '2a104bab1782e9b6445583296d4a0ecc8af304e4769ceb64b890e8219c562399'; + it('test a coinbase TX ' + txid1, function(done) { + Transaction.fromIdWithInfo(txid1, function(err, tx) { if (err) done(err); assert(tx.info.isCoinBase); - assert.equal(tx.info.txid, test_txid2); + assert.equal(tx.info.txid, txid1); assert(!tx.info.feeds); done(); }); }); - it('test a broken TX 64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608', function(done) { - var test_txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608'; - Transaction.fromIdWithInfo(test_txid2, function(err, tx) { + var txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608'; + it('test a broken TX ' + txid2, function(done) { + Transaction.fromIdWithInfo(txid2, function(err, tx) { if (err) done(err); - assert.equal(tx.info.txid, test_txid2); + assert.equal(tx.info.txid, txid2); assert.equal(tx.info.vin[0].addr, null); done(); }); }); - - + + + txItemsValid.forEach( function(v) { + it('test a exploding TX ' + v.txid, function(done) { + + // Remove first + TransactionItem.remove({txid: v.txid}, function(err) { + + Transaction.explodeTransactionItems(v.txid, function(err, tx) { + if (err) done(err); + + TransactionItem.find({txid: v.txid}).sort({ index:1 }).exec(function(err, readItems) { + + var match=0; + v.items.forEach(function(validItem){ + readItems.forEach(function(readItem){ + if ( readItem.addr === validItem.addr && + parseInt(readItem.index) === parseInt(validItem.index) && + parseFloat(readItem.value) === parseFloat(validItem.value) ) { + } + match=1; + }); + }); + var all = v.items.toString(); + assert(match, "Testing..." + readItems + "vs." + all); + done(); + }); + }); + }); + }); + }); }); diff --git a/test/model/txitems.json b/test/model/txitems.json new file mode 100644 index 0000000..bd0ffb4 --- /dev/null +++ b/test/model/txitems.json @@ -0,0 +1,22 @@ +[ + { + "txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237", + "items": [ + { + "addr": "mwcFwXv2Yquy4vJA4nnNLAbHVjrPdC8Q1Z", + "value": 1.66224, + "index": 0 + }, + { + "addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA", + "value": -1.34574, + "index": 0 + }, + { + "addr": "n28wb1cRGxPtfmsenYKFfsvnZ6kRapx3jF", + "value": -0.316, + "index": 1 + } + ] + } +] From 5e68c8c7ed6a57aa87be1d4cf750b220b6e3fb06 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 12 Jan 2014 00:46:38 -0300 Subject: [PATCH 13/19] test for txitem explode --- test/model/txitems.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/model/txitems.json b/test/model/txitems.json index bd0ffb4..4ebd188 100644 --- a/test/model/txitems.json +++ b/test/model/txitems.json @@ -18,5 +18,25 @@ "index": 1 } ] + }, + { + "txid": "b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee", + "items": [ + { + "addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA", + "value": 0.40790667, + "index": 0 + }, + { + "addr": "mhfQJUSissP6nLM5pz6DxHfctukrrLct2T", + "value": -0.193, + "index": 0 + }, + { + "addr": "mzcDhbL877ES3MGftWnc3EuTSXs3WXDDML", + "value": -0.21440667, + "index": 1 + } + ] } ] From c1d52966181fdeb91f31afe002f1f1bdae9b8d5e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 12 Jan 2014 01:16:22 -0300 Subject: [PATCH 14/19] better txitem test --- app/models/Transaction.js | 5 +++-- app/models/TransactionItem.js | 2 +- test/model/transaction.js | 20 ++++++++++++-------- test/model/txitems.json | 16 ++++++++-------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/app/models/Transaction.js b/app/models/Transaction.js index 87667c1..c986cf2 100644 --- a/app/models/Transaction.js +++ b/app/models/Transaction.js @@ -96,6 +96,7 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { this.fromIdWithInfo(txid, function(err, t) { if (err || !t) return cb(err); + var index=0; async.each(t.info.vin, function(i, next_in) { /* @@ -107,7 +108,7 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { txid : t.txid, value : -1 * i.value, addr : i.addr, - index : i.n, + index : index++, }, next_in); } else { @@ -130,7 +131,7 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { TransactionItem.create({ txid : t.txid, value : o.value, - addr : o.scriptPubKey.addresses[0], + addr : o.scriptPubKey.addresses[0], index : o.n, }, next_out); } diff --git a/app/models/TransactionItem.js b/app/models/TransactionItem.js index 66a825f..f6ba9e9 100644 --- a/app/models/TransactionItem.js +++ b/app/models/TransactionItem.js @@ -13,7 +13,7 @@ var TransactionItemSchema = new Schema({ type: String, index: true, }, - // >0 is Input <0 is Output + // <0 is Input >0 is Output value: Number, }); diff --git a/test/model/transaction.js b/test/model/transaction.js index 6a50547..39daa20 100644 --- a/test/model/transaction.js +++ b/test/model/transaction.js @@ -10,7 +10,8 @@ var config = require('../../config/config'), Transaction = require('../../app/models/Transaction'), TransactionItem = require('../../app/models/TransactionItem'), - fs = require('fs'); + fs = require('fs'), + util = require('util'); var txItemsValid = JSON.parse(fs.readFileSync('test/model/txitems.json')); @@ -86,18 +87,21 @@ describe('Transaction', function(){ TransactionItem.find({txid: v.txid}).sort({ index:1 }).exec(function(err, readItems) { - var match=0; + var unmatch={}; + + v.items.forEach(function(validItem){ + unmatch[validItem.addr] =1; + }); v.items.forEach(function(validItem){ readItems.forEach(function(readItem){ if ( readItem.addr === validItem.addr && - parseInt(readItem.index) === parseInt(validItem.index) && - parseFloat(readItem.value) === parseFloat(validItem.value) ) { - } - match=1; + parseInt(readItem.index) == parseInt(validItem.index) && + parseFloat(readItem.value) == parseFloat(validItem.value) ) + delete unmatch[validItem.addr]; }); }); - var all = v.items.toString(); - assert(match, "Testing..." + readItems + "vs." + all); + var valid = util.inspect(v.items, { depth: null }); + assert(!Object.keys(unmatch).length, '\n\tmatched:' + Object.keys(unmatch) + "\n\n" +valid + '\nvs.\n' + readItems); done(); }); }); diff --git a/test/model/txitems.json b/test/model/txitems.json index 4ebd188..d900ded 100644 --- a/test/model/txitems.json +++ b/test/model/txitems.json @@ -3,18 +3,18 @@ "txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237", "items": [ { - "addr": "mwcFwXv2Yquy4vJA4nnNLAbHVjrPdC8Q1Z", - "value": 1.66224, - "index": 0 + "addr": "mwcFwXv2Yquy4vJA4nnNLAbHVjrPdC8Q1Z", + "value": -1.66224, + "index": 0 }, { "addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA", - "value": -1.34574, + "value": 1.34574, "index": 0 }, { "addr": "n28wb1cRGxPtfmsenYKFfsvnZ6kRapx3jF", - "value": -0.316, + "value": 0.316, "index": 1 } ] @@ -24,17 +24,17 @@ "items": [ { "addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA", - "value": 0.40790667, + "value": -0.40790667, "index": 0 }, { "addr": "mhfQJUSissP6nLM5pz6DxHfctukrrLct2T", - "value": -0.193, + "value": 0.193, "index": 0 }, { "addr": "mzcDhbL877ES3MGftWnc3EuTSXs3WXDDML", - "value": -0.21440667, + "value": 0.21440667, "index": 1 } ] From 33b6404a14b6c74d6ebe61fa44a1a802d3b1e34e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 12 Jan 2014 01:29:25 -0300 Subject: [PATCH 15/19] . --- app/models/TransactionItem.js | 1 - lib/Sync.js | 15 +++- p2p.js | 131 ++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 3 deletions(-) create mode 100755 p2p.js diff --git a/app/models/TransactionItem.js b/app/models/TransactionItem.js index f6ba9e9..8f5ca97 100644 --- a/app/models/TransactionItem.js +++ b/app/models/TransactionItem.js @@ -22,7 +22,6 @@ var TransactionItemSchema = new Schema({ TransactionItemSchema.index({txid: 1, index: 1, value: 1}, {unique: true, dropDups: true}); - TransactionItemSchema.statics.load = function(id, cb) { this.findOne({ _id: id diff --git a/lib/Sync.js b/lib/Sync.js index 4474540..16d7c94 100644 --- a/lib/Sync.js +++ b/lib/Sync.js @@ -58,8 +58,19 @@ function spec() { }); }; - Sync.prototype.storeTxs = function(txs, cb) { - Transaction.createFromArray(txs, cb); + Sync.prototype.storeTxs = function(txids, cb) { + Transaction.createFromArray(txids, function(err) { + if (err) return cb(err); + + async.each(txids, function(txid, next) { + + // This will trigger an RPC call + Transaction.explodeTransactionItems( txid, function(err) { + next(err); + }); + }, + cb); + }); }; Sync.prototype.syncBlocks = function(reindex, cb) { diff --git a/p2p.js b/p2p.js new file mode 100755 index 0000000..aa0a08d --- /dev/null +++ b/p2p.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node +<<<<<<< HEAD +======= + +>>>>>>> 71e1c718ac8f5eb89acedb4f91f2207ec463808b +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var fs = require('fs'); +var HeaderDB = require('./HeaderDB').class(); +var Block = require('bitcore/Block').class(); +var CoinConst = require('bitcore/const'); +var coinUtil = require('bitcore/util/util'); +var networks = require('bitcore/networks'); +var Parser = require('bitcore/util/BinaryParser').class(); +var Sync = require('./lib/Sync').class(); +var Peer = require('bitcore/Peer').class(); + +var peerdb_fn = 'peerdb.json'; +var peerdb = undefined; + +var PROGRAM_VERSION = '0.1'; +var program = require('commander'); + +program + .version(PROGRAM_VERSION) + .option('-N --network [testnet]', 'Set bitcoin network [testnet]', 'testnet') + .parse(process.argv); + +var sync = new Sync({ + networkName: program.network +}); +sync.init(); + +var PeerManager = require('bitcore/PeerManager').createClass({ + config: { + network: program.network + } +}); + +function peerdb_load() { + try { + peerdb = JSON.parse(fs.readFileSync(peerdb_fn)); + } catch(d) { + console.warn('Unable to read peer db', peerdb_fn, 'creating new one.'); + peerdb = [{ + ipv4: '127.0.0.1', + port: 18333 + }, + ]; + + fs.writeFileSync(peerdb_fn, JSON.stringify(peerdb)); + } +} + +function handle_inv(info) { + // TODO: should limit the invs to objects we haven't seen yet + var invs = info.message.invs; + invs.forEach(function(inv) { + console.log('Handle inv for a ' + CoinConst.MSG.to_str(inv.type)); + }); + // this is not needed right now, but it's left in case + // we need to store more info in the future + info.conn.sendGetData(invs); +} + +function handle_tx(info) { + var tx = info.message.tx.getStandardizedObject(); + console.log('Handle tx: ' + tx.hash); + sync.storeTxs([tx.hash], function(err) { + if (err) { + console.log('Error in handle TX: ' + err); + } + }); +} + +function handle_block(info) { + var block = info.message.block; + var now = Math.round(new Date().getTime() / 1000); + var blockHash = coinUtil.formatHashFull(block.calcHash()); + console.log('Handle block: ' + blockHash); + sync.storeBlock({ + 'hash': blockHash, + 'time': now + }, + function(err) { + if (err) { + console.log('Error in handle Block: ' + err); + } else { + // if no errors importing block, import the transactions + var hashes = block.txs.map(function(tx) { + return coinUtil.formatHashFull(tx.hash); + }); + sync.storeTxs(hashes, function() {}); + } + }); + +} + +function handle_connected(data) { + var peerman = data.pm; + var peers_n = peerman.peers.length; + console.log('p2psync: Connected to ' + peers_n + ' peer' + (peers_n !== 1 ? 's': '')); +} + +function p2psync() { + var peerman = new PeerManager(); + + peerdb.forEach(function(datum) { + var peer = new Peer(datum.ipv4, datum.port); + peerman.addPeer(peer); + }); + + peerman.on('connection', function(conn) { + conn.on('inv', handle_inv); + conn.on('block', handle_block); + conn.on('tx', handle_tx); + }); + peerman.on('connect', handle_connected); + + peerman.start(); +} + +function main() { + peerdb_load(); + p2psync(); +} + +main(); + From fa52ce878cd09a553122e73b89a423e3431b7660 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 12 Jan 2014 03:06:14 -0300 Subject: [PATCH 16/19] fix pay-to-pubkey transaction inputs --- app/models/Transaction.js | 30 +++++++++++++++++++++++------- lib/Sync.js | 21 +++++++++++++++++++-- test/model/transaction.js | 17 +++++++++++++++++ test/model/txitems.json | 26 ++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 9 deletions(-) diff --git a/app/models/Transaction.js b/app/models/Transaction.js index c986cf2..f60653f 100644 --- a/app/models/Transaction.js +++ b/app/models/Transaction.js @@ -112,8 +112,9 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { }, next_in); } else { - if ( !i.coinbase ) + if ( !i.coinbase ) { console.log ("TX: %s seems to be multisig IN. Skipping... ", t.txid); + } return next_in(); } }, @@ -154,6 +155,7 @@ TransactionSchema.methods.fillInputValues = function (tx, next) { if (tx.isCoinBase()) return next(); if (! this.rpc) this.rpc = new RpcClient(config.bitcoind); + var network = ( config.network === 'testnet') ? networks.testnet : networks.livenet ; var that = this; async.each(tx.ins, function(i, cb) { @@ -165,21 +167,32 @@ TransactionSchema.methods.fillInputValues = function (tx, next) { var c=0; that.rpc.getRawTransaction(outHashBase64, function(err, txdata) { var txin = new Transaction(); - if (err || ! txdata.result) return cb( new Error('Input TX '+outHashBase64+' not found')); var b = new Buffer(txdata.result,'hex'); txin.parse(b); - - if ( txin.isCoinBase() ) { - return cb(); - } + /* + *We have to parse it anyways. It will have outputs even it is a coinbase tx + if ( txin.isCoinBase() ) { + return cb(); + } + */ txin.outs.forEach( function(j) { // console.log( c + ': ' + util.formatValue(j.v) ); if (c === outIndex) { i.value = j.v; + + // This is used for pay-to-pubkey transaction in which + // the pubkey is not provided on the input + var scriptPubKey = j.getScript(); + var txType = scriptPubKey.classify(); + var hash = scriptPubKey.simpleOutHash(); + if (hash) { + var addr = new Address(network.addressPubkey, hash); + i.addrFromOutput = addr.toString(); + } } c++; }); @@ -224,7 +237,6 @@ TransactionSchema.methods.queryInfo = function (next) { } else { tx.ins.forEach(function(i) { - if (i.value) { that.info.vin[c].value = util.formatValue(i.value); var n = util.valueToBigInt(i.value).toNumber(); @@ -240,6 +252,10 @@ TransactionSchema.methods.queryInfo = function (next) { var addrStr = addr.toString(); that.info.vin[c].addr = addrStr; } + else { + if (i.addrFromOutput) + that.info.vin[c].addr = i.addrFromOutput; + } } else { console.log("TX could not be parsed: %s,%d",txInfo.result.txid, c); diff --git a/lib/Sync.js b/lib/Sync.js index 16d7c94..b3dfc4a 100644 --- a/lib/Sync.js +++ b/lib/Sync.js @@ -15,6 +15,7 @@ function spec() { var TransactionItem = require('../app/models/TransactionItem'); function Sync(config) { + this.tx_count =0; this.network = config.networkName === 'testnet' ? networks.testnet: networks.livenet; } @@ -32,7 +33,7 @@ function spec() { if (blockInfo.result.height % 1000 === 0) { var h = blockInfo.result.height, d = blockInfo.result.confirmations; - progress_bar('height', h, h + d); + progress_bar(util.format('Height [txs:%d]',that.tx_count), h, h + d); } that.storeBlock(blockInfo.result, function(err) { @@ -59,6 +60,7 @@ function spec() { }; Sync.prototype.storeTxs = function(txids, cb) { + var that=this; Transaction.createFromArray(txids, function(err) { if (err) return cb(err); @@ -66,10 +68,13 @@ function spec() { // This will trigger an RPC call Transaction.explodeTransactionItems( txid, function(err) { + that.tx_count++; next(err); }); }, - cb); + function(err) { + return cb(); + }); }); }; @@ -159,6 +164,7 @@ function spec() { }; + // Not used Sync.prototype.processTXs = function(reindex, cb) { var that = this; @@ -229,6 +235,15 @@ function spec() { cb(); } }, + function(cb) { + if (opts.destroy) { + console.log('Deleting TXItems...'); + that.db.collections.transactionitems.drop(cb); + } else { + cb(); + } + }, + function(cb) { if (!opts.skip_blocks) { that.syncBlocks(opts.reindex, cb); @@ -236,6 +251,7 @@ function spec() { cb(); } }, +/* Exploding happens on block insertion function(cb) { if (! opts.skip_txs) { that.processTXs(opts.reindex, cb); @@ -244,6 +260,7 @@ function spec() { return cb(); } } +*/ /* We dont sync any contents from TXs, only their IDs are stored function(cb) { if (! opts.skip_txs) { diff --git a/test/model/transaction.js b/test/model/transaction.js index 39daa20..801ed8b 100644 --- a/test/model/transaction.js +++ b/test/model/transaction.js @@ -29,6 +29,23 @@ describe('Transaction', function(){ done(); }); + + + it('should pool tx\'s object from mongoose', function(done) { + var txid = '7e621eeb02874ab039a8566fd36f4591e65eca65313875221842c53de6907d6c'; + Transaction.fromIdWithInfo(txid, function(err, tx) { + if (err) done(err); + assert.equal(tx.txid, txid); + assert(!tx.info.isCoinBase); + + for(var i=0; i<20; i++) + assert(parseFloat(tx.info.vin[i].value) === parseFloat(50)); + assert(tx.info.vin[0].addr === 'msGKGCy2i8wbKS5Fo1LbWUTJnf1GoFFG59'); + assert(tx.info.vin[1].addr === 'mfye7oHsdrHbydtj4coPXCasKad2eYSv5P'); + done(); + }); + }); + it('should pool tx\'s object from mongoose', function(done) { var txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237'; Transaction.fromIdWithInfo(txid, function(err, tx) { diff --git a/test/model/txitems.json b/test/model/txitems.json index d900ded..3441bb6 100644 --- a/test/model/txitems.json +++ b/test/model/txitems.json @@ -38,5 +38,31 @@ "index": 1 } ] + }, + { + "txid": "ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b", + "items": [ + { + "addr": "mzeiUi4opeheWYveXqp8ebqHyVwYGA2s3x", + "value": -0.01225871, + "index": 0 + }, + { + "addr": "mtMLijHAbG8CsgBbQGajsqav9p9wKUYad5", + "value": -0.01201823, + "index": 1 + }, + { + "addr": "mhqyL1nDQDo1WLH9qH8sjRjx2WwrnmAaXE", + "value": 0.01327746, + "index": 0 + }, + { + "addr": "mkGrySSnxcqRbtPCisApj3zXCQVmUUWbf1", + "value": 0.01049948, + "index": 1 + } + ] } + ] From 1324d8c183e2a54596a3fea8f2347d49ca5780a0 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 12 Jan 2014 23:37:36 -0300 Subject: [PATCH 17/19] Address class working from some address :) --- app/models/Address.js | 72 +++++++++++++++++++---- test/model/addr.js | 117 ++++++++++---------------------------- test/model/addr.json | 89 +++++++++++++++++++++++++++++ test/model/transaction.js | 5 +- 4 files changed, 185 insertions(+), 98 deletions(-) create mode 100644 test/model/addr.json diff --git a/app/models/Address.js b/app/models/Address.js index f4dad6b..23d7089 100644 --- a/app/models/Address.js +++ b/app/models/Address.js @@ -1,15 +1,67 @@ 'use strict'; -/** - * Module dependencies. - */ -var mongoose = require('mongoose'), - Schema = mongoose.Schema - ; +require('classtool'); + + +function spec() { + var util = require('util'); + var RpcClient = require('bitcore/RpcClient').class(); + var networks = require('bitcore/networks'); + var async = require('async'); + var Transaction = require('./Transaction'); + var TransactionItem = require('./TransactionItem'); + var config = require('../../config/config'); + + function Address(addrStr,cb) { + this.addrStr = addrStr; + this.balance = null; + this.totalReceived = null; + this.totalSent = null; + this.txApperances = 0; + + // TODO store only txids? +index? +all? + this.transactions = []; + } + + Address.prototype.update = function(next) { + + var that = this; + async.series([ + // TODO TXout! + //T + function (cb) { + TransactionItem.find({addr:that.addrStr}, function(err,txItems){ + if (err) return cb(err); + + txItems.forEach(function(txItem){ + + that.txApperances +=1; + // TESTING + that.balance += txItem.value + 0.1; + + that.transactions.push(txItem.txid); + + if (txItem.value > 0) + that.totalSent += txItem.value; + else + that.totalReceived += Math.abs(txItem.value); + }); + return cb(); + }) + } + ], function (err) { + return next(err); + }); + } + + return Address; +} +module.defineClass(spec); + /** - * Addr Schema - */ + * Addr Schema Idea for moogose. Not used now. + * var AddressSchema = new Schema({ // For now we keep this as short as possible @@ -31,7 +83,6 @@ var AddressSchema = new Schema({ }); - AddressSchema.statics.load = function(id, cb) { this.findOne({ _id: id @@ -55,5 +106,6 @@ AddressSchema.statics.fromAddrWithInfo = function(hash, cb) { }); }; - module.exports = mongoose.model('Address', AddressSchema); +*/ + diff --git a/test/model/addr.js b/test/model/addr.js index 55ae06b..0b683a5 100644 --- a/test/model/addr.js +++ b/test/model/addr.js @@ -2,83 +2,17 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - -var addrs = - [ - { addr: 'mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H', - }, - /* - { addr: 'mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H', - balance: 46413.0, - totalReceived: 357130.17644359, - totalSent: 310717.17644359, - }, - { addr: 'mgKY35SXqxFpcKK3Dq9mW9919N7wYXvcFM', - balance: 0.01979459, - totalReceived: 0.01979459, - totalSent: 0, - transactions: [ '91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5' ], - }, - { addr: 'mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5', - balance: 10580.50027254, - totalReceived: 12157.65075053, - totalSent: 1577.15047799, - transactions: [ - '91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5', - 'f6e80d4fd1a2377406856c67d0cee5ac7e5120993ff97e617ca9aac33b4c6b1e', - 'bc27f31caae86750b126d9b09e969362b85b7c15f41421387d682064544bf7e7', - '2cd6a1cb26880276fbc9851396f1bd8081cb2b9107ff6921e8fd65ed2df3df79', - '8bea41f573bccb7b648bc0b1bbfeba8a96da05b1d819ff4a33d39fbcd334ecfd', - 'cb0d55c37acc57f759255193673e13858b5ab3d8fdfa7ee8b25f9964bdaa11e3', - '7b007aeace2299d27b6bb6c24d0a8040d6a87e4c2601216c34d226462b75f915', - 'a9f40fbaecd2b28a05405e28b95566d7b3bd8ac38a2853debd72517f2994c6fc', - '4123255b7678e37c168b9e929927760bc5d9363b0c78ec61a7b4a78b2a07adab', - 'cb3760529c2684c32047a2fddf0e2534c9241e5d72011aac4a8982e0c7b46df3', - 'e8d00d8cc744381233dbc95e2d657345084dfb6df785b81285183f4c89b678d4', - '7a748364255c5b64979d9d3da35ea0fbef0114e0d7f96fccd5bea76f6d19f06b', - 'd0b7e087040f67ef9bd9f21ccf53d1b5410400351d949cabf127caf28a6e7add', - '209f97873265652b83922921148cad92d7e048c6822e4864e984753e04181470', - '3a4af7755d3061ecced2f3707c2623534104f08aa73a52ca243d7ddecf5fe86d', - '4a4b5c8d464a77814ed35a37e2a28e821d467a803761427c057f67823309b725', - 'd85f5265618fb694c3ea3ca6f73eba93df8a644bc1c7286cec2fbc2fbf7d895e', - '0d2c778ed9976b52792c941cac126bda37d3b1453082022d5e36ac401be3b249', - 'daf03d666047ca0b5340b4a0027f8562b7c5bac87dca3727093b5393176a541a', - 'a0dc03a870e589ea51e3d3a8aed0d34f4f1ae6844acad26dae48fe523b26e764', - '3df1a50e2e5d8525f04bd21a66bad824364a975449fa24fd5c2537d0f713919b', - '7bc26c1f3b4ab5ca57677593d28d13bff468a658f4d5efc379c1612554cf668e', - 'ded4cbc9c52fd5599b6a93f89a79cde9aeb5a7f8f56732bb67ae9554325b3666', - '91224a219196a3f6e6f40ad2137b13fe54109e57aaed7527ea34aa903e6b8313', - 'ee899a182bbb75e98ef14d83489e631dd66a8c5059dc8255692dd8ca9efba01f', - '0a61590c7548bd4f6a0df1575b268057e5e3e295a44eaeeb1dfbd01332c585ed', - 'd56c22950ad2924f404b5b0baa6e49b0df1aaf09d1947842aed9d0178958eb9d', - 'c6b5368c5a256141894972fbd02377b3894aa0df7c35fab5e0eca90de064fdc1', - '158e1f9c3f8ec44e88052cadef74e8eb99fbad5697d0b005ba48c933f7d96816', - '7f6191c0f4e3040901ef0d5d6e76af4f16423061ca1347524c86205e35d904d9', - '2c2e20f976b98a0ca76c57eca3653699b60c1bd9503cc9cc2fb755164a679a26', - '59bc81733ff0eaf2b106a70a655e22d2cdeff80ada27b937693993bf0c22e9ea', - '7da38b66fb5e8582c8be85abecfd744a6de89e738dd5f3aaa0270b218ec424eb', - '393d51119cdfbf0a308c0bbde2d4c63546c0961022bad1503c4bbaed0638c837', - '4518868741817ae6757fd98de27693b51fad100e89e5206b9bbf798aeebb804c', - 'c58bce14de1e3016504babd8bbe8175207d75074134a2548a71743fa3e56c58d', - '6e69ec4a97515a8fd424f123a5fc1fdfd3c3adcd741292cbc09c09a2cc433bea', - '0e15f2498362050e5ceb6157d0fbf820fdcaf936e447207d433ee7701d7b99c2', - 'a3789e113041db907a1217ddb5c3aaf0eff905cc3d913e68d977e1ab4d19acea', - '80b460922faf0ad1e8b8a55533654c9a9f3039bfff0fff2bcf8536b8adf95939', - ] - }, - */ - ]; - var - mongoose= require('mongoose'), assert = require('assert'), + fs = require('fs'), config = require('../../config/config'), Address = require('../../app/models/Address'); + mongoose= require('mongoose'), + config = require('../../config/config'); +var addrValid = JSON.parse(fs.readFileSync('test/model/addr.json')); -mongoose.connection.on('error', function(err) { console.log(err); }); - -describe('Address fromAddrWithInfo', function(){ +describe('Address update', function(){ before(function(done) { mongoose.connect(config.db); @@ -90,24 +24,33 @@ describe('Address fromAddrWithInfo', function(){ done(); }); + addrValid.forEach( function(v) { + if (v.disabled) { + console.log(v.addr + " => disabled in JSON"); + } + else { + it('should retrieve the correct info for:' + v.addr, function(done) { + this.timeout(5000); + + var a = Address.new(v.addr); - addrs.forEach( function(t) { - it('should retrieve the correct info for' + t.addr, function(done) { - Address.fromHashWithInfo(t.addr, function(err, a) { - if (err) done(err); - assert.equal(t.addr, a.addr); - if (t.balance) assert.equal(t.balance, a.balance, "balance"); - if (t.totalReceived) assert.equal(t.totalReceived, a.totalReceived, "totalReceived"); - if (t.totalSent) assert.equal(t.totalSent, a.totalSent, "totalSent"); - if (t.transactions) { - t.transactions.forEach( function(tx) { - assert(tx in a.inTransactions); - }); - } - done(); - }); - }); + a.update(function(err) { + if (err) done(err); + + assert.equal(v.addr, a.addrStr); + if (v.balance) assert.equal(v.balance, a.balance); + if (v.totalReceived) assert.equal(v.totalReceived, a.totalReceived); + if (v.totalSent) assert.equal(v.totalSent, a.totalSent); + if (v.transactions) { + v.transactions.forEach( function(tx) { + assert(tx in a.inTransactions); + }); + } + done(); + }); + }); + } }); }); diff --git a/test/model/addr.json b/test/model/addr.json new file mode 100644 index 0000000..c82aa7d --- /dev/null +++ b/test/model/addr.json @@ -0,0 +1,89 @@ +[ + { + "addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H" + }, + { + "addr": "mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS", + "balance": 0, + "totalReceived": 50, + "totalSent": 50.0 + } + , + + { + "addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29", + "balance": 910.39522682, + "totalReceived": 910.39522682, + "totalSent": 0 + } + , + + + + { +"disabled":1, + "addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H", + "balance": 46413.0, + "totalReceived": 357130.17644359, + "totalSent": 310717.17644359 + }, + { +"disabled":1, + "addr": "mgKY35SXqxFpcKK3Dq9mW9919N7wYXvcFM", + "balance": 0.01979459, + "totalReceived": 0.01979459, + "totalSent": 0, + "transactions": [ "91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5" ] + }, + { +"disabled":1, + "addr": "mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5", + "balance": 10580.50027254, + "totalReceived": 12157.65075053, + "totalSent": 1577.15047799, + "transactions": [ + "91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5", + "f6e80d4fd1a2377406856c67d0cee5ac7e5120993ff97e617ca9aac33b4c6b1e", + "bc27f31caae86750b126d9b09e969362b85b7c15f41421387d682064544bf7e7", + "2cd6a1cb26880276fbc9851396f1bd8081cb2b9107ff6921e8fd65ed2df3df79", + "8bea41f573bccb7b648bc0b1bbfeba8a96da05b1d819ff4a33d39fbcd334ecfd", + "cb0d55c37acc57f759255193673e13858b5ab3d8fdfa7ee8b25f9964bdaa11e3", + "7b007aeace2299d27b6bb6c24d0a8040d6a87e4c2601216c34d226462b75f915", + "a9f40fbaecd2b28a05405e28b95566d7b3bd8ac38a2853debd72517f2994c6fc", + "4123255b7678e37c168b9e929927760bc5d9363b0c78ec61a7b4a78b2a07adab", + "cb3760529c2684c32047a2fddf0e2534c9241e5d72011aac4a8982e0c7b46df3", + "e8d00d8cc744381233dbc95e2d657345084dfb6df785b81285183f4c89b678d4", + "7a748364255c5b64979d9d3da35ea0fbef0114e0d7f96fccd5bea76f6d19f06b", + "d0b7e087040f67ef9bd9f21ccf53d1b5410400351d949cabf127caf28a6e7add", + "209f97873265652b83922921148cad92d7e048c6822e4864e984753e04181470", + "3a4af7755d3061ecced2f3707c2623534104f08aa73a52ca243d7ddecf5fe86d", + "4a4b5c8d464a77814ed35a37e2a28e821d467a803761427c057f67823309b725", + "d85f5265618fb694c3ea3ca6f73eba93df8a644bc1c7286cec2fbc2fbf7d895e", + "0d2c778ed9976b52792c941cac126bda37d3b1453082022d5e36ac401be3b249", + "daf03d666047ca0b5340b4a0027f8562b7c5bac87dca3727093b5393176a541a", + "a0dc03a870e589ea51e3d3a8aed0d34f4f1ae6844acad26dae48fe523b26e764", + "3df1a50e2e5d8525f04bd21a66bad824364a975449fa24fd5c2537d0f713919b", + "7bc26c1f3b4ab5ca57677593d28d13bff468a658f4d5efc379c1612554cf668e", + "ded4cbc9c52fd5599b6a93f89a79cde9aeb5a7f8f56732bb67ae9554325b3666", + "91224a219196a3f6e6f40ad2137b13fe54109e57aaed7527ea34aa903e6b8313", + "ee899a182bbb75e98ef14d83489e631dd66a8c5059dc8255692dd8ca9efba01f", + "0a61590c7548bd4f6a0df1575b268057e5e3e295a44eaeeb1dfbd01332c585ed", + "d56c22950ad2924f404b5b0baa6e49b0df1aaf09d1947842aed9d0178958eb9d", + "c6b5368c5a256141894972fbd02377b3894aa0df7c35fab5e0eca90de064fdc1", + "158e1f9c3f8ec44e88052cadef74e8eb99fbad5697d0b005ba48c933f7d96816", + "7f6191c0f4e3040901ef0d5d6e76af4f16423061ca1347524c86205e35d904d9", + "2c2e20f976b98a0ca76c57eca3653699b60c1bd9503cc9cc2fb755164a679a26", + "59bc81733ff0eaf2b106a70a655e22d2cdeff80ada27b937693993bf0c22e9ea", + "7da38b66fb5e8582c8be85abecfd744a6de89e738dd5f3aaa0270b218ec424eb", + "393d51119cdfbf0a308c0bbde2d4c63546c0961022bad1503c4bbaed0638c837", + "4518868741817ae6757fd98de27693b51fad100e89e5206b9bbf798aeebb804c", + "c58bce14de1e3016504babd8bbe8175207d75074134a2548a71743fa3e56c58d", + "6e69ec4a97515a8fd424f123a5fc1fdfd3c3adcd741292cbc09c09a2cc433bea", + "0e15f2498362050e5ceb6157d0fbf820fdcaf936e447207d433ee7701d7b99c2", + "a3789e113041db907a1217ddb5c3aaf0eff905cc3d913e68d977e1ab4d19acea", + "80b460922faf0ad1e8b8a55533654c9a9f3039bfff0fff2bcf8536b8adf95939" + ] + } +] + + diff --git a/test/model/transaction.js b/test/model/transaction.js index 801ed8b..03c4a4d 100644 --- a/test/model/transaction.js +++ b/test/model/transaction.js @@ -87,7 +87,10 @@ describe('Transaction', function(){ Transaction.fromIdWithInfo(txid2, function(err, tx) { if (err) done(err); assert.equal(tx.info.txid, txid2); - assert.equal(tx.info.vin[0].addr, null); + assert.equal(tx.info.vin[0].addr, 'n1JagbRWBDi6VMvG7HfZmXX74dB9eiHJzU'); + + // TODO output -> multisig! + // https://www.biteasy.com/testnet/transactions/64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608 done(); }); }); From 5a97c96d6e08af68229a8159c9929d78e362fbe9 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 13 Jan 2014 15:29:19 -0300 Subject: [PATCH 18/19] test working with values stored in satoshis --- app/models/Address.js | 16 +++++------ app/models/Transaction.js | 50 ++++++++++++++++++++++++++--------- app/models/TransactionItem.js | 26 ++++++++++++++---- config/env/development.js | 6 ++--- test/model/addr.js | 4 +-- test/model/addr.json | 7 ++++- test/model/transaction.js | 49 +++++++++++++++++++++++----------- test/model/txitems.json | 24 ++++++++++------- 8 files changed, 124 insertions(+), 58 deletions(-) diff --git a/app/models/Address.js b/app/models/Address.js index 23d7089..ef8f319 100644 --- a/app/models/Address.js +++ b/app/models/Address.js @@ -14,9 +14,9 @@ function spec() { function Address(addrStr,cb) { this.addrStr = addrStr; - this.balance = null; - this.totalReceived = null; - this.totalSent = null; + this.balanceSat = 0; + this.totalReceivedSat = 0; + this.totalSentSat = 0; this.txApperances = 0; // TODO store only txids? +index? +all? @@ -35,16 +35,16 @@ function spec() { txItems.forEach(function(txItem){ +console.log(txItem.txid + ' : ' + txItem.value_sat); that.txApperances +=1; - // TESTING - that.balance += txItem.value + 0.1; + that.balanceSat += txItem.value_sat; that.transactions.push(txItem.txid); - if (txItem.value > 0) - that.totalSent += txItem.value; + if (txItem.value_sat > 0) + that.totalSentSat += txItem.value_sat; else - that.totalReceived += Math.abs(txItem.value); + that.totalReceivedSat += Math.abs(txItem.value_sat); }); return cb(); }) diff --git a/app/models/Transaction.js b/app/models/Transaction.js index f60653f..7858b35 100644 --- a/app/models/Transaction.js +++ b/app/models/Transaction.js @@ -58,19 +58,36 @@ TransactionSchema.statics.fromId = function(txid, cb) { TransactionSchema.statics.fromIdWithInfo = function(txid, cb) { + var that = this; - // TODO Should we go to mongoDB first? Now, no extra information is stored at mongo. this.fromId(txid, function(err, tx) { if (err) return cb(err); - if (!tx) { return cb(new Error('TX not found')); } + if (!tx) { + + return cb(new Error('TX not found')); + + // No in mongo...but maybe in bitcoind... lets query it +/* var tx = new that(); + + tx.txid = txid; + + tx.queryInfo(function(err, txInfo) { + + if (!txInfo) return cb(new Error('TX not found')); + + tx.save(function(err) { +console.log('asdadsads'); + return cb(err,tx); + }); + }); +*/ } tx.queryInfo(function(err) { return cb(err,tx); } ); }); }; - TransactionSchema.statics.createFromArray = function(txs, next) { var that = this; if (!txs) return next(); @@ -97,23 +114,23 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { if (err || !t) return cb(err); var index=0; + t.info.vin.forEach(function(i){ i.n = index++}); + async.each(t.info.vin, function(i, next_in) { - - /* - * TODO Support multisigs??? - */ - if (i.addr && i.value) { + +//console.log("Creating IN %s %d", i.addr, i.valueSat); TransactionItem.create({ txid : t.txid, - value : -1 * i.value, + value_sat : -1 * i.valueSat, addr : i.addr, - index : index++, + index : i.n, + ts : t.info.time, }, next_in); } else { if ( !i.coinbase ) { - console.log ("TX: %s seems to be multisig IN. Skipping... ", t.txid); + console.log ("TX: %s,%d could not parse INPUT", t.txid, i.n); } return next_in(); } @@ -129,15 +146,17 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { && o.scriptPubKey.addresses && o.scriptPubKey.addresses[0] ) { +//console.log("Creating OUT %s %d", o.scriptPubKey.addresses[0], o.valueSat); TransactionItem.create({ txid : t.txid, - value : o.value, + value_sat : o.valueSat, addr : o.scriptPubKey.addresses[0], index : o.n, + ts : t.info.time, }, next_out); } else { - console.log ("TX: %s,%d seems to be multisig OUT. Skipping... ", t.txid, o.n); + console.log ("TX: %s,%d could not parse OUTPUT. Skipping... ", t.txid, o.n); return next_out(); } }, @@ -240,6 +259,7 @@ TransactionSchema.methods.queryInfo = function (next) { if (i.value) { that.info.vin[c].value = util.formatValue(i.value); var n = util.valueToBigInt(i.value).toNumber(); + that.info.vin[c].valueSat = n; valueIn = valueIn.add( n ); var scriptSig = i.getScript(); @@ -264,9 +284,13 @@ TransactionSchema.methods.queryInfo = function (next) { }); } + var c = 0; tx.outs.forEach( function(i) { var n = util.valueToBigInt(i.v).toNumber(); valueOut = valueOut.add(n); + + that.info.vout[c].valueSat = n; + c++; }); that.info.valueOut = valueOut / util.COIN; diff --git a/app/models/TransactionItem.js b/app/models/TransactionItem.js index 8f5ca97..bb7c4b9 100644 --- a/app/models/TransactionItem.js +++ b/app/models/TransactionItem.js @@ -13,13 +13,15 @@ var TransactionItemSchema = new Schema({ type: String, index: true, }, + // OJO: mongoose doesnt accept camelcase for field names // <0 is Input >0 is Output - value: Number, + value_sat: Number, + ts: Number, }); // Compound index -TransactionItemSchema.index({txid: 1, index: 1, value: 1}, {unique: true, dropDups: true}); +TransactionItemSchema.index({txid: 1, index: 1, value_sat: 1}, {unique: true, dropDups: true}); TransactionItemSchema.statics.load = function(id, cb) { @@ -29,10 +31,24 @@ TransactionItemSchema.statics.load = function(id, cb) { }; -TransactionItemSchema.statics.fromAddr = function(addr, cb) { +TransactionItemSchema.statics.fromTxId = function(txid, cb) { this.find({ - addr: addr, - }).exec(cb); + txid: txid, + }).exec(function (err,items) { + + // sort by 1) value sign 2) index + return cb(err,items.sort(function(a,b){ + var sa= a.value_sat < 0 ? -1 : 1; + var sb= b.value_sat < 0 ? -1 : 1; + + if (sa != sb) { + return sa-sb; + } + else { + return a.index - b.index; + } + })); + }); }; module.exports = mongoose.model('TransactionItem', TransactionItemSchema); diff --git a/config/env/development.js b/config/env/development.js index f01f9fb..479553e 100755 --- a/config/env/development.js +++ b/config/env/development.js @@ -6,9 +6,9 @@ module.exports = { name: "Mystery - Development" }, bitcoind: { - user: 'mystery', - pass: 'real_mystery', - protocol: 'http', + protocol: process.env.BITCOIND_USER || 'http', + user: process.env.BITCOIND_USER || 'mystery', + pass: process.env.BITCOIND_PASS || 'real_mystery', host: process.env.BITCOIND_HOST || '127.0.0.1', port: process.env.BITCOIND_PORT || '18332', }, diff --git a/test/model/addr.js b/test/model/addr.js index 0b683a5..b999200 100644 --- a/test/model/addr.js +++ b/test/model/addr.js @@ -8,9 +8,7 @@ var config = require('../../config/config'), Address = require('../../app/models/Address'); mongoose= require('mongoose'), - config = require('../../config/config'); - -var addrValid = JSON.parse(fs.readFileSync('test/model/addr.json')); + addrValid = JSON.parse(fs.readFileSync('test/model/addr.json')); describe('Address update', function(){ diff --git a/test/model/addr.json b/test/model/addr.json index c82aa7d..716a62b 100644 --- a/test/model/addr.json +++ b/test/model/addr.json @@ -1,15 +1,20 @@ [ { +"disabled":1, "addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H" }, { +"disabled":1, "addr": "mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS", "balance": 0, "totalReceived": 50, "totalSent": 50.0 } , - + { + "addr": "mgqvRGJMwR9JU5VhJ3x9uX9MTkzTsmmDgQ", + "balance": 43.1 + }, { "addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29", "balance": 910.39522682, diff --git a/test/model/transaction.js b/test/model/transaction.js index 03c4a4d..d42c172 100644 --- a/test/model/transaction.js +++ b/test/model/transaction.js @@ -28,9 +28,6 @@ describe('Transaction', function(){ mongoose.connection.close(); done(); }); - - - it('should pool tx\'s object from mongoose', function(done) { var txid = '7e621eeb02874ab039a8566fd36f4591e65eca65313875221842c53de6907d6c'; Transaction.fromIdWithInfo(txid, function(err, tx) { @@ -80,23 +77,42 @@ describe('Transaction', function(){ done(); }); }); + var txid22 = '666'; + it('test unexisting TX ' + txid22, function(done) { + Transaction.fromIdWithInfo(txid22, function(err, tx) { + if (err && err.toString().match(/TX.not.found/)) { + return done(); + } + else { + return done(err); + } + }); + }); + var txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608'; + it('create TX on the fly ' + txid2, function(done) { + TransactionItem.remove({txid: txid2}, function(err) { + Transaction.fromIdWithInfo(txid2, function(err, tx) { + if (err) return done(err); + assert.equal(tx.info.txid, txid2); + done(); + }); + }); + }); var txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608'; it('test a broken TX ' + txid2, function(done) { Transaction.fromIdWithInfo(txid2, function(err, tx) { - if (err) done(err); + if (err) return done(err); assert.equal(tx.info.txid, txid2); assert.equal(tx.info.vin[0].addr, 'n1JagbRWBDi6VMvG7HfZmXX74dB9eiHJzU'); - - // TODO output -> multisig! - // https://www.biteasy.com/testnet/transactions/64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608 done(); }); }); txItemsValid.forEach( function(v) { + if (v.disabled) return; it('test a exploding TX ' + v.txid, function(done) { // Remove first @@ -105,7 +121,8 @@ describe('Transaction', function(){ Transaction.explodeTransactionItems(v.txid, function(err, tx) { if (err) done(err); - TransactionItem.find({txid: v.txid}).sort({ index:1 }).exec(function(err, readItems) { + TransactionItem + .fromTxId( v.txid, function(err, readItems) { var unmatch={}; @@ -113,20 +130,22 @@ describe('Transaction', function(){ unmatch[validItem.addr] =1; }); v.items.forEach(function(validItem){ - readItems.forEach(function(readItem){ - if ( readItem.addr === validItem.addr && - parseInt(readItem.index) == parseInt(validItem.index) && - parseFloat(readItem.value) == parseFloat(validItem.value) ) - delete unmatch[validItem.addr]; - }); + var readItem = readItems.shift(); + assert.equal(readItem.addr,validItem.addr); + assert.equal(readItem.value_sat,validItem.value_sat); + assert.equal(readItem.index,validItem.index); + delete unmatch[validItem.addr]; }); + var valid = util.inspect(v.items, { depth: null }); - assert(!Object.keys(unmatch).length, '\n\tmatched:' + Object.keys(unmatch) + "\n\n" +valid + '\nvs.\n' + readItems); + assert(!Object.keys(unmatch).length, + '\n\tUnmatchs:' + Object.keys(unmatch) + "\n\n" +valid + '\nvs.\n' + readItems); done(); }); }); }); }); }); + }); diff --git a/test/model/txitems.json b/test/model/txitems.json index 3441bb6..9e92a17 100644 --- a/test/model/txitems.json +++ b/test/model/txitems.json @@ -1,20 +1,24 @@ [ + { + "disabled": 1, + "txid": "75c5ffe6dc2eb0f6bd011a08c041ef115380ccd637d859b379506a0dca4c26fc" + }, { "txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237", "items": [ { "addr": "mwcFwXv2Yquy4vJA4nnNLAbHVjrPdC8Q1Z", - "value": -1.66224, + "value_sat": -166224000, "index": 0 }, { "addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA", - "value": 1.34574, + "value_sat": 134574000, "index": 0 }, { "addr": "n28wb1cRGxPtfmsenYKFfsvnZ6kRapx3jF", - "value": 0.316, + "value_sat": 31600000, "index": 1 } ] @@ -24,17 +28,17 @@ "items": [ { "addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA", - "value": -0.40790667, + "value_sat": -40790667, "index": 0 }, { "addr": "mhfQJUSissP6nLM5pz6DxHfctukrrLct2T", - "value": 0.193, + "value_sat": 19300000, "index": 0 }, { "addr": "mzcDhbL877ES3MGftWnc3EuTSXs3WXDDML", - "value": 0.21440667, + "value_sat": 21440667, "index": 1 } ] @@ -44,22 +48,22 @@ "items": [ { "addr": "mzeiUi4opeheWYveXqp8ebqHyVwYGA2s3x", - "value": -0.01225871, + "value_sat": -1225871, "index": 0 }, { "addr": "mtMLijHAbG8CsgBbQGajsqav9p9wKUYad5", - "value": -0.01201823, + "value_sat": -1201823, "index": 1 }, { "addr": "mhqyL1nDQDo1WLH9qH8sjRjx2WwrnmAaXE", - "value": 0.01327746, + "value_sat": 1327746, "index": 0 }, { "addr": "mkGrySSnxcqRbtPCisApj3zXCQVmUUWbf1", - "value": 0.01049948, + "value_sat": 1049948, "index": 1 } ] From 868f22cf2b4045016fd3e50c9a7240f1b05d3225 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 13 Jan 2014 17:21:42 -0300 Subject: [PATCH 19/19] api working WIP --- Gruntfile.js | 8 ++--- app/controllers/addresses.js | 37 +++++++++++++++++++++ app/controllers/blocks.js | 14 +++++--- app/models/Address.js | 60 ++++++++++++++++++++++------------- app/models/Transaction.js | 55 +++++++++++++++----------------- app/models/TransactionItem.js | 2 +- config/env/development.js | 2 +- config/env/test.js | 6 ++-- config/routes.js | 4 +++ test/model/addr.js | 5 ++- test/model/addr.json | 3 +- 11 files changed, 127 insertions(+), 69 deletions(-) create mode 100644 app/controllers/addresses.js diff --git a/Gruntfile.js b/Gruntfile.js index a386491..715c647 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -40,11 +40,11 @@ module.exports = function(grunt) { livereload: true } }, - test: { // we monitor only app/models/* because we have test for models only now - files: ['test/**/*.js', 'test/*.js','app/models/*.js'], - tasks: ['test'], - } +// test: { +// files: ['test/**/*.js', 'test/*.js','app/models/*.js'], +// tasks: ['test'], +// } }, jshint: { all: { diff --git a/app/controllers/addresses.js b/app/controllers/addresses.js new file mode 100644 index 0000000..e13000c --- /dev/null +++ b/app/controllers/addresses.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var Address = require('../models/Address'); + + +/** + * Find block by hash ... + */ +exports.address = function(req, res, next, addr) { + var a = Address.new(addr); + + a.update(function(err) { + if (err && !a.totalReceivedSat) { + console.log(err); + res.status(404).send('Invalid address'); + return next(); + } + + req.address = a; + return next(); + }); +}; + + +/** + * Show block + */ +exports.show = function(req, res) { + if (req.address) { + res.jsonp(req.address); + } +}; + diff --git a/app/controllers/blocks.js b/app/controllers/blocks.js index 5381ec0..21f82d0 100644 --- a/app/controllers/blocks.js +++ b/app/controllers/blocks.js @@ -14,10 +14,14 @@ var mongoose = require('mongoose'), */ exports.block = function(req, res, next, hash) { Block.fromHashWithInfo(hash, function(err, block) { - if (err) return next(err); - if (!block) return next(new Error('Failed to load block ' + hash)); + if (err && !block) { + console.log(err); + res.status(404).send('Not found'); + return next(); + } + req.block = block.info; - next(); + return next(); }); }; @@ -26,7 +30,9 @@ exports.block = function(req, res, next, hash) { * Show block */ exports.show = function(req, res) { - res.jsonp(req.block); + if (req.block) { + res.jsonp(req.block); + } }; /** diff --git a/app/models/Address.js b/app/models/Address.js index ef8f319..0541488 100644 --- a/app/models/Address.js +++ b/app/models/Address.js @@ -4,16 +4,12 @@ require('classtool'); function spec() { - var util = require('util'); - var RpcClient = require('bitcore/RpcClient').class(); - var networks = require('bitcore/networks'); var async = require('async'); - var Transaction = require('./Transaction'); var TransactionItem = require('./TransactionItem'); - var config = require('../../config/config'); + var BitcoreAddress = require('bitcore/Address').class(); + var BitcoreUtil = require('bitcore/util/util'); - function Address(addrStr,cb) { - this.addrStr = addrStr; + function Address(addrStr) { this.balanceSat = 0; this.totalReceivedSat = 0; this.totalSentSat = 0; @@ -21,38 +17,58 @@ function spec() { // TODO store only txids? +index? +all? this.transactions = []; + + var a = new BitcoreAddress(addrStr); + try { + a.validate(); + this.addrStr = addrStr; + } catch(e){ + } } + + Address.prototype.__defineGetter__('balance', function(){ + +console.log('#################### '+this.balanceSat); + + + return this.balanceSat / BitcoreUtil.COIN; + }); + Address.prototype.update = function(next) { + if (! this.addrStr) { + return next(new Error('Invalid or undefined address string')); + } + var that = this; async.series([ // TODO TXout! //T function (cb) { - TransactionItem.find({addr:that.addrStr}, function(err,txItems){ - if (err) return cb(err); + TransactionItem.find({addr:that.addrStr}, function(err,txItems){ + if (err) return cb(err); - txItems.forEach(function(txItem){ + txItems.forEach(function(txItem){ -console.log(txItem.txid + ' : ' + txItem.value_sat); - that.txApperances +=1; - that.balanceSat += txItem.value_sat; + // console.log(txItem.txid + ' : ' + txItem.value_sat); + that.txApperances +=1; + that.balanceSat += txItem.value_sat; - that.transactions.push(txItem.txid); + that.transactions.push(txItem.txid); - if (txItem.value_sat > 0) - that.totalSentSat += txItem.value_sat; - else - that.totalReceivedSat += Math.abs(txItem.value_sat); + if (txItem.value_sat > 0) + that.totalSentSat += txItem.value_sat; + else + that.totalReceivedSat += Math.abs(txItem.value_sat); + }); + return cb(); }); - return cb(); - }) - } + } ], function (err) { return next(err); }); - } + }; return Address; } diff --git a/app/models/Transaction.js b/app/models/Transaction.js index 7858b35..29b0499 100644 --- a/app/models/Transaction.js +++ b/app/models/Transaction.js @@ -58,32 +58,31 @@ TransactionSchema.statics.fromId = function(txid, cb) { TransactionSchema.statics.fromIdWithInfo = function(txid, cb) { - var that = this; + var That = this; this.fromId(txid, function(err, tx) { if (err) return cb(err); - if (!tx) { - - return cb(new Error('TX not found')); - + if (!tx) { // No in mongo...but maybe in bitcoind... lets query it -/* var tx = new that(); + tx = new That(); tx.txid = txid; - - tx.queryInfo(function(err, txInfo) { + tx.queryInfo(function(err, txInfo) { - if (!txInfo) return cb(new Error('TX not found')); + if (!txInfo) + return cb(new Error('TX not found1')); - tx.save(function(err) { -console.log('asdadsads'); - return cb(err,tx); + tx.save(function(err) { + return cb(err,tx); }); }); -*/ } - - tx.queryInfo(function(err) { return cb(err,tx); } ); + } + else { + tx.queryInfo(function(err) { + return cb(err,tx); + }); + } }); }; @@ -113,8 +112,10 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { this.fromIdWithInfo(txid, function(err, t) { if (err || !t) return cb(err); - var index=0; - t.info.vin.forEach(function(i){ i.n = index++}); + var index = 0; + t.info.vin.forEach( function(i){ + i.n = index++; + }); async.each(t.info.vin, function(i, next_in) { if (i.addr && i.value) { @@ -130,9 +131,9 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { } else { if ( !i.coinbase ) { - console.log ("TX: %s,%d could not parse INPUT", t.txid, i.n); + console.log ('TX: %s,%d could not parse INPUT', t.txid, i.n); } - return next_in(); + return next_in(); } }, function (err) { @@ -142,10 +143,7 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { /* * TODO Support multisigs */ - if (o.value && o.scriptPubKey - && o.scriptPubKey.addresses - && o.scriptPubKey.addresses[0] - ) { + if (o.value && o.scriptPubKey && o.scriptPubKey.addresses && o.scriptPubKey.addresses[0]) { //console.log("Creating OUT %s %d", o.scriptPubKey.addresses[0], o.valueSat); TransactionItem.create({ txid : t.txid, @@ -156,8 +154,8 @@ TransactionSchema.statics.explodeTransactionItems = function(txid, cb) { }, next_out); } else { - console.log ("TX: %s,%d could not parse OUTPUT. Skipping... ", t.txid, o.n); - return next_out(); + console.log ('TX: %s,%d could not parse OUTPUT', t.txid, o.n); + return next_out(); } }, function (err) { @@ -206,7 +204,6 @@ TransactionSchema.methods.fillInputValues = function (tx, next) { // This is used for pay-to-pubkey transaction in which // the pubkey is not provided on the input var scriptPubKey = j.getScript(); - var txType = scriptPubKey.classify(); var hash = scriptPubKey.simpleOutHash(); if (hash) { var addr = new Address(network.addressPubkey, hash); @@ -273,18 +270,18 @@ TransactionSchema.methods.queryInfo = function (next) { that.info.vin[c].addr = addrStr; } else { - if (i.addrFromOutput) + if (i.addrFromOutput) that.info.vin[c].addr = i.addrFromOutput; } } else { - console.log("TX could not be parsed: %s,%d",txInfo.result.txid, c); + console.log('TX could not be parsed: %s,%d' ,txInfo.result.txid, c); } c++; }); } - var c = 0; + c=0; tx.outs.forEach( function(i) { var n = util.valueToBigInt(i.v).toNumber(); valueOut = valueOut.add(n); diff --git a/app/models/TransactionItem.js b/app/models/TransactionItem.js index bb7c4b9..5a04f84 100644 --- a/app/models/TransactionItem.js +++ b/app/models/TransactionItem.js @@ -41,7 +41,7 @@ TransactionItemSchema.statics.fromTxId = function(txid, cb) { var sa= a.value_sat < 0 ? -1 : 1; var sb= b.value_sat < 0 ? -1 : 1; - if (sa != sb) { + if (sa !== sb) { return sa-sb; } else { diff --git a/config/env/development.js b/config/env/development.js index 479553e..3439a60 100755 --- a/config/env/development.js +++ b/config/env/development.js @@ -6,7 +6,7 @@ module.exports = { name: "Mystery - Development" }, bitcoind: { - protocol: process.env.BITCOIND_USER || 'http', + protocol: process.env.BITCOIND_PROTO || 'http', user: process.env.BITCOIND_USER || 'mystery', pass: process.env.BITCOIND_PASS || 'real_mystery', host: process.env.BITCOIND_HOST || '127.0.0.1', diff --git a/config/env/test.js b/config/env/test.js index f668acf..dd7a70e 100755 --- a/config/env/test.js +++ b/config/env/test.js @@ -7,9 +7,9 @@ module.exports = { }, port: '3301', bitcoind: { - user: 'mystery', - pass: 'real_mystery', - protocol: 'http', + protocol: process.env.BITCOIND_PROTO || 'http', + user: process.env.BITCOIND_USER || 'mystery', + pass: process.env.BITCOIND_PASS || 'real_mystery', host: process.env.BITCOIND_HOST || '127.0.0.1', port: process.env.BITCOIND_PORT || '18332', }, diff --git a/config/routes.js b/config/routes.js index 83c7eb3..45bc4ad 100644 --- a/config/routes.js +++ b/config/routes.js @@ -19,4 +19,8 @@ module.exports = function(app) { app.param('txid', transactions.transaction); + var addresses = require('../app/controllers/addresses'); + app.get('/api/addr/:addr', addresses.show); + app.param('addr', addresses.address); + }; diff --git a/test/model/addr.js b/test/model/addr.js index b999200..2a59f61 100644 --- a/test/model/addr.js +++ b/test/model/addr.js @@ -6,7 +6,7 @@ var assert = require('assert'), fs = require('fs'), config = require('../../config/config'), - Address = require('../../app/models/Address'); + Address = require('../../app/models/Address').class(); mongoose= require('mongoose'), addrValid = JSON.parse(fs.readFileSync('test/model/addr.json')); @@ -30,8 +30,7 @@ describe('Address update', function(){ it('should retrieve the correct info for:' + v.addr, function(done) { this.timeout(5000); - var a = Address.new(v.addr); - + var a = new Address(v.addr); a.update(function(err) { if (err) done(err); diff --git a/test/model/addr.json b/test/model/addr.json index 716a62b..22f4255 100644 --- a/test/model/addr.json +++ b/test/model/addr.json @@ -1,10 +1,8 @@ [ { -"disabled":1, "addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H" }, { -"disabled":1, "addr": "mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS", "balance": 0, "totalReceived": 50, @@ -16,6 +14,7 @@ "balance": 43.1 }, { +"disabled":1, "addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29", "balance": 910.39522682, "totalReceived": 910.39522682,