diff --git a/Gruntfile.js b/Gruntfile.js index d0d2afa8..7b04ccef 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -35,8 +35,8 @@ module.exports = function(grunt) { livereload: true, }, }, - js2: { - files: ['public/src/**/*.js'], + assets: { + files: ['public/src/**/*.js', 'public/**/*.css'], tasks: ['compile'], options: { livereload: true, diff --git a/app/controllers/blocks.js b/app/controllers/blocks.js index 8b0d3d4e..0b471cee 100644 --- a/app/controllers/blocks.js +++ b/app/controllers/blocks.js @@ -104,24 +104,20 @@ exports.list = function(req, res) { bdb.getBlocksByDate(gte, lte, function(err, blocks) { if (err) { res.status(500).send(err); - } else { - var blockshashList = []; - var limit = parseInt(req.query.limit || blocks.length); - if (blocks.length < limit) { - limit = blocks.length; - } - for (var i = 0; i < limit; i++) { - blockshashList.push(blocks[i].hash); - } - async.mapSeries(blockshashList, - function(hash, cb) { - getBlock(hash, function(err, info) { - if (err) return cb(err); - return cb(err, { + } + else { + var l = blocks.length; + var limit = parseInt(req.query.limit || l); + if (l < limit) limit = l; + + async.mapSeries(blocks, + function(b, cb) { + getBlock(b.hash, function(err, info) { + return cb(err,{ height: info.height, size: info.size, - hash: info.hash, - time: info.time, + hash: b.hash, + time: b.ts || info.time, txlength: info.tx.length, }); }); diff --git a/app/controllers/socket.js b/app/controllers/socket.js index e0017a7c..5175ab59 100644 --- a/app/controllers/socket.js +++ b/app/controllers/socket.js @@ -44,5 +44,8 @@ module.exports.broadcastAddressTx = function(address, tx) { }; module.exports.broadcastSyncInfo = function(historicSync) { - if (ios) ios.sockets.in('sync').emit('status', historicSync); + + if (ios) { + ios.sockets.in('sync').emit('status', historicSync); + } }; diff --git a/app/models/Address.js b/app/models/Address.js index cdb820b8..3bd9611a 100644 --- a/app/models/Address.js +++ b/app/models/Address.js @@ -13,7 +13,11 @@ function spec() { this.balanceSat = 0; this.totalReceivedSat = 0; this.totalSentSat = 0; - this.txApperances = 0; + + this.unconfirmedBalanceSat = 0; + + this.txApperances = 0; + this.unconfirmedTxApperances= 0; // TODO store only txids? +index? +all? this.transactions = []; @@ -51,12 +55,25 @@ function spec() { }, enumerable: 1, }); + + + Object.defineProperty(this, 'unconfirmedBalance', { + get: function() { + return parseFloat(this.unconfirmedBalanceSat) / parseFloat(BitcoreUtil.COIN); + }, + set: function(i) { + this.unconfirmedBalanceSat = i * BitcoreUtil.COIN; + }, + enumerable: 1, + }); + } Address.prototype.update = function(next) { var self = this; if (!self.addrStr) return next(); + var txs = []; var db = new TransactionDb(); async.series([ function (cb) { @@ -64,27 +81,52 @@ function spec() { if (err) return cb(err); txOut.forEach(function(txItem){ + var v = txItem.value_sat; + + txs.push({txid: txItem.txid, ts: txItem.ts}); + + if (txItem.spendTxId) { + txs.push({txid: txItem.spendTxId, ts: txItem.spendTs}); + } + if (txItem.isConfirmed) { - var v = txItem.value_sat; + self.txApperances += 1; self.totalReceivedSat += v; - self.transactions.push(txItem.txid); - if (! txItem.spendTxId || !txItem.spendIsConfirmed) { + if (! txItem.spendTxId ) { + //unspend + self.balanceSat += v; + } + else if(!txItem.spendIsConfirmed) { // unspent self.balanceSat += v; - self.txApperances +=1; + self.unconfirmedBalanceSat -= v; + self.unconfirmedTxApperances += 1; } else { // spent self.totalSentSat += v; - self.transactions.push(txItem.spendTxId); - self.txApperances +=2; + self.txApperances += 1; } } + else { + self.unconfirmedBalanceSat += v; + self.unconfirmedTxApperances += 1; + } }); return cb(); }); }, ], function (err) { + + // sort input and outputs togheter + txs.sort( + function compare(a,b) { + if (a.ts < b.ts) return 1; + if (a.ts > b.ts) return -1; + return 0; + }); + + self.transactions = txs.map(function(i) { return i.txid; } ); return next(err); }); }; diff --git a/dev-util/get_block.js b/dev-util/get_block.js index 23c5f457..fe3e4163 100755 --- a/dev-util/get_block.js +++ b/dev-util/get_block.js @@ -17,7 +17,7 @@ hash = 'e2253359458db3e732c82a43fc62f56979ff59928f25a2df34dfa443e9a41160'; var rpc = new RpcClient(config.bitcoind); -rpc.getRawTransaction( hash, 1, function(err, ret) { +rpc.getBlockCount( function(err, ret) { console.log('Err:'); console.log(err); diff --git a/lib/BlockDb.js b/lib/BlockDb.js index c38d2598..a80b5410 100644 --- a/lib/BlockDb.js +++ b/lib/BlockDb.js @@ -5,11 +5,11 @@ require('classtool'); function spec(b) { - var TIMESTAMP_PREFIX = 'b-ts-'; // b-ts- => - var PREV_PREFIX = 'b-prev-'; // b-prev- => - var NEXT_PREFIX = 'b-next-'; // b-next- => - var MAIN_PREFIX = 'b-main-'; // b-main- => 1/0 - var TIP = 'b-tip-'; // last block on the chain + var TIMESTAMP_PREFIX = 'bts-'; // b-ts- => + var PREV_PREFIX = 'bpr-'; // b-prev- => + var NEXT_PREFIX = 'bne-'; // b-next- => + var MAIN_PREFIX = 'bma-'; // b-main- => 1/0 + var TIP = 'bti-'; // last block on the chain /** @@ -125,14 +125,11 @@ function spec(b) { BlockDb.prototype.has = function(hash, cb) { var k = PREV_PREFIX + hash; db.get(k, function (err,val) { - var ret; + var ret = true; if (err && err.notFound) { err = null; ret = false; } - if (typeof val !== 'undefined') { - ret = true; - } return cb(err, ret); }); }; @@ -170,8 +167,9 @@ function spec(b) { fillCache: true }) .on('data', function (data) { + var k = data.key.split('-'); list.push({ - ts: data.key.replace(TIMESTAMP_PREFIX, ''), + ts: k[1], hash: data.value, }); }) diff --git a/lib/HistoricSync.js b/lib/HistoricSync.js index 40706e30..753e2de0 100644 --- a/lib/HistoricSync.js +++ b/lib/HistoricSync.js @@ -7,8 +7,6 @@ require('classtool'); function spec() { var util = require('util'); var RpcClient = require('bitcore/RpcClient').class(); - var bitutil = require('bitcore/util/util'); - var Address = require('bitcore/Address').class(); var Script = require('bitcore/Script').class(); var networks = require('bitcore/networks'); var async = require('async'); @@ -35,7 +33,6 @@ function spec() { this.syncPercentage = 0; this.syncedBlocks = 0; - this.skippedBlocks = 0; this.orphanBlocks = 0; this.type =''; } @@ -95,7 +92,6 @@ function spec() { status: this.status, blockChainHeight: this.blockChainHeight, syncPercentage: this.syncPercentage, - skippedBlocks: this.skippedBlocks, syncedBlocks: this.syncedBlocks, orphanBlocks: this.orphanBlocks, syncTipHash: this.sync.tip, @@ -105,7 +101,7 @@ function spec() { }; HistoricSync.prototype.updatePercentage = function() { - var r = (this.syncedBlocks + this.skippedBlocks) / this.blockChainHeight; + var r = this.syncedBlocks / this.blockChainHeight; this.syncPercentage = parseFloat(100 * r).toFixed(3); if (this.syncPercentage > 100) this.syncPercentage = 100; }; @@ -113,13 +109,15 @@ function spec() { HistoricSync.prototype.showProgress = function() { var self = this; - if ( ( self.syncedBlocks + self.skippedBlocks) % self.step !== 1) return; + if ( self.status ==='syncing' && + ( self.syncedBlocks ) % self.step !== 1) return; if (self.error) { p('ERROR: ' + self.error); } else { - p(util.format('status: [%d%%] skipped: %d ', self.syncPercentage, self.skippedBlocks)); + self.updatePercentage(); + p(util.format('status: [%d%%]', self.syncPercentage)); } if (self.opts.shouldBroadcastSync) { sockets.broadcastSyncInfo(self.info()); @@ -194,22 +192,21 @@ function spec() { self.status = 'syncing'; } - if ( (scanOpts.upToExisting && existed && - self.syncedBlocks >= self.blockChainHeight) || - (blockEnd && blockEnd === blockHash)) { + if ( blockEnd && blockEnd === blockHash) { + p('blockEnd found!:' + blockEnd); + self.found=1; + } + + if ( self.found && self.syncedBlocks >= self.blockChainHeight ) { self.status = 'finished'; - p('DONE. Found block: ', blockHash); - self.showProgress(); + p('DONE. Height: ' , self.syncedBlocks); return cb(err); } // Continue if (blockInfo) { - if (existed) - self.skippedBlocks++; - else - self.syncedBlocks++; + self.syncedBlocks++; // recursion if (scanOpts.prev && blockInfo.previousblockhash) @@ -223,42 +220,6 @@ function spec() { }; - // TODO. replace with - // Script.prototype.getAddrStrs if that one get merged in bitcore - HistoricSync.prototype.getAddrStr = function(s) { - var self = this; - - var addrStrs = []; - var type = s.classify(); - var addr; - - switch(type) { - case Script.TX_PUBKEY: - var chunk = s.captureOne(); - addr = new Address(self.network.addressPubkey, bitutil.sha256ripe160(chunk)); - addrStrs = [ addr.toString() ]; - break; - case Script.TX_PUBKEYHASH: - addr = new Address(self.network.addressPubkey, s.captureOne()); - addrStrs = [ addr.toString() ]; - break; - case Script.TX_SCRIPTHASH: - addr = new Address(self.network.addressScript, s.captureOne()); - addrStrs = [ addr.toString() ]; - break; - case Script.TX_MULTISIG: - var chunks = s.capture(); - chunks.forEach(function(chunk) { - var a = new Address(self.network.addressPubkey, bitutil.sha256ripe160(chunk)); - addrStrs.push(a.toString()); - }); - break; - case Script.TX_UNKNOWN: - break; - } - - return addrStrs; - }; HistoricSync.prototype.getBlockFromFile = function(cb) { var self = this; @@ -289,7 +250,7 @@ function spec() { var s = new Script(o.s); - var addrs = self.getAddrStr(s); + var addrs = self.sync.getAddrStr(s); // support only for p2pubkey p2pubkeyhash and p2sh if (addrs.length === 1) { @@ -308,7 +269,6 @@ function spec() { var self = this; self.showProgress(); - self.getBlockFromFile(function(err, blockInfo) { if (err) { self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', @@ -363,6 +323,7 @@ function spec() { var self = this; var lastBlock; + var tip; async.series([ function(cb) { @@ -376,17 +337,25 @@ function spec() { function (cb) { return self.getBlockCount(cb); }, function(cb) { if (!scanOpts.reverse) return cb(); - self.rpc.getBlockHash(self.blockChainHeight, function(err, res) { if (err) return cb(err); lastBlock = res.result; - return cb(); }); }, function(cb) { - if (scanOpts.upToExisting) { - // should be isOrphan = true or null to be more accurate. + if (!scanOpts.reverse) return cb(); + self.sync.bDb.getTip(function(err, res) { + if (err) return cb(err); + tip = res; + + console.log('Old Tip:', tip); + return cb(); + }); + }, + + function(cb) { + if (scanOpts.reverse) { self.countNotOrphan(function(err, count) { if (err) return cb(err); @@ -421,7 +390,7 @@ function spec() { if (scanOpts.reverse) { start = lastBlock; - end = self.genesis; + end = tip || self.genesis; scanOpts.prev = true; } else { @@ -448,12 +417,14 @@ function spec() { }); }); }, function(err) { + self.showProgress(); return next(err); }); } else { self.type = 'from RPC calls'; self.getPrevNextBlock(start, end, scanOpts, function(err) { + self.showProgress(); return next(err); }); } @@ -490,10 +461,9 @@ function spec() { } } else { - p('Genesis block found. Syncing upto known blocks.'); + p('Genesis block found. Syncing upto old TIP.'); p('Got ' + count + ' out of ' + self.blockChainHeight + ' blocks'); scanOpts.reverse = true; - scanOpts.upToExisting = true; } return self.importHistory(scanOpts, next); }); diff --git a/lib/PeerSync.js b/lib/PeerSync.js index ae9a850e..ce1bf1a4 100644 --- a/lib/PeerSync.js +++ b/lib/PeerSync.js @@ -6,6 +6,7 @@ function spec() { var CoinConst = require('bitcore/const'); var coinUtil = require('bitcore/util/util'); var Sync = require('./Sync').class(); + var Script = require('bitcore/Script').class(); var Peer = require('bitcore/Peer').class(); var config = require('../config/config'); var networks = require('bitcore/networks'); @@ -57,8 +58,23 @@ function spec() { }; PeerSync.prototype.handleTx = function(info) { + var self =this; var tx = info.message.tx.getStandardizedObject(); console.log('[p2p_sync] Handle tx: ' + tx.hash); + tx.time = tx.time || Math.round(new Date().getTime() / 1000); + + var to=0; + info.message.tx.outs.forEach( function(o) { + var s = new Script(o.s); + var addrs = self.sync.getAddrStr(s); + + // support only for p2pubkey p2pubkeyhash and p2sh + if (addrs.length === 1) { + tx.out[to].addrStr = addrs[0]; + tx.out[to].n = to; + } + to++; + }); this.sync.storeTxs([tx], function(err) { if (err) { diff --git a/lib/Sync.js b/lib/Sync.js index d69bc120..d343fd0d 100644 --- a/lib/Sync.js +++ b/lib/Sync.js @@ -6,7 +6,12 @@ require('classtool'); function spec() { var sockets = require('../app/controllers/socket.js'); var BlockDb = require('./BlockDb').class(); + var bitutil = require('bitcore/util/util'); + var Address = require('bitcore/Address').class(); var TransactionDb = require('./TransactionDb').class(); + var config = require('../config/config'); + var networks = require('bitcore/networks'); + var Script = require('bitcore/Script').class(); var async = require('async'); @@ -18,6 +23,7 @@ function spec() { self.opts = opts; this.bDb = new BlockDb(opts); this.txDb = new TransactionDb(opts); + this.network = config.network === 'testnet' ? networks.testnet: networks.livenet; return cb(); }; @@ -276,6 +282,44 @@ function spec() { return cb(err); }); }; + + + // TODO. replace with + // Script.prototype.getAddrStrs if that one get merged in bitcore + Sync.prototype.getAddrStr = function(s) { + var self = this; + + var addrStrs = []; + var type = s.classify(); + var addr; + + switch(type) { + case Script.TX_PUBKEY: + var chunk = s.captureOne(); + addr = new Address(self.network.addressPubkey, bitutil.sha256ripe160(chunk)); + addrStrs = [ addr.toString() ]; + break; + case Script.TX_PUBKEYHASH: + addr = new Address(self.network.addressPubkey, s.captureOne()); + addrStrs = [ addr.toString() ]; + break; + case Script.TX_SCRIPTHASH: + addr = new Address(self.network.addressScript, s.captureOne()); + addrStrs = [ addr.toString() ]; + break; + case Script.TX_MULTISIG: + var chunks = s.capture(); + chunks.forEach(function(chunk) { + var a = new Address(self.network.addressPubkey, bitutil.sha256ripe160(chunk)); + addrStrs.push(a.toString()); + }); + break; + case Script.TX_UNKNOWN: + break; + } + + return addrStrs; + }; return Sync; } module.defineClass(spec); diff --git a/lib/TransactionDb.js b/lib/TransactionDb.js index 05b3e46f..607d10c9 100644 --- a/lib/TransactionDb.js +++ b/lib/TransactionDb.js @@ -6,17 +6,17 @@ require('classtool'); function spec(b) { // blockHash -> txid mapping - var IN_BLK_PREFIX = 'tx-b-'; //tx-b-- => 1/0 (connected or not) + var IN_BLK_PREFIX = 'txb-'; //txb-- => 1/0 (connected or not) // Only for orphan blocks var FROM_BLK_PREFIX = 'tx-'; //tx-- => 1 // to show tx outs - var OUTS_PREFIX = 'txouts-'; //txouts-- => [addr, btc_sat] + var OUTS_PREFIX = 'txo-'; //txo-- => [addr, btc_sat] + var SPEND_PREFIX = 'txs-'; //txs---- = ts - // to sum up addr balance - var ADDR_PREFIX = 'txouts-addr-'; //txouts-addr---- => + btc_sat - var SPEND_PREFIX = 'txouts-spend-';//txouts-spend---- = ts + // to sum up addr balance (only outs, spends are gotten later) + var ADDR_PREFIX = 'txa-'; //txa--- => + btc_sat:ts // TODO: use bitcore networks module var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'; @@ -69,7 +69,7 @@ function spec(b) { }); }; - TransactionDb.prototype._addSpendInfo = function(r, txid, index) { + TransactionDb.prototype._addSpendInfo = function(r, txid, index, ts) { if (r.spendTxId) { if (!r.multipleSpendAttempts) { r.multipleSpendAttempts = [{ @@ -85,6 +85,7 @@ function spec(b) { else { r.spendTxId = txid; r.spendIndex = parseInt(index); + r.spendTs = parseInt(ts); } }; @@ -118,12 +119,12 @@ function spec(b) { db.createReadStream({start: k, end: k + '~'}) .on('data', function (data) { var k = data.key.split('-'); - var j = idx[parseInt(k[3])]; + var j = idx[parseInt(k[2])]; assert(typeof j !== 'undefined','Spent could not be stored: tx ' + txid + - 'spend in TX:' + k[2] + ',' + k[3]+ ' j:' + j); + 'spend in TX:' + k[1] + ',' + k[2]+ ' j:' + j); - self._addSpendInfo(ret[j], k[4], k[5]); + self._addSpendInfo(ret[j], k[3], k[4], data.value); }) .on('error', function (err) { return cb(err); @@ -144,7 +145,7 @@ function spec(b) { db.createReadStream({start: k, end: k + '~'}) .on('data', function (data) { var k = data.key.split('-'); - self._addSpendInfo(info.vout[k[3]], k[4], k[5]); + self._addSpendInfo(info.vout[k[2]], k[3], k[4], data.value); }) .on('error', function (err) { return cb(err); @@ -173,6 +174,7 @@ function spec(b) { return c_in(); // error not scalated } + info.firstSeenTs = ret.spendTs; i.unconfirmedInput = i.unconfirmedInput; i.addr = ret.addr; i.valueSat = ret.valueSat; @@ -264,7 +266,7 @@ function spec(b) { db.createReadStream({start: k, end: k + '~'}) .on('data', function (data) { var k = data.key.split('-'); - self._addSpendInfo(ret, k[4], k[5]); + self._addSpendInfo(ret, k[3], k[4], data.value); }) .on('error', function (error) { return cb(error); @@ -320,10 +322,10 @@ function spec(b) { var k = data.key.split('-'); var v = data.value.split(':'); ret.push({ + txid: k[2], + index: parseInt(k[3]), value_sat: parseInt(v[0]), - ts: parseInt(k[3]), - txid: k[4], - index: parseInt(k[5]), + ts: parseInt(v[1]), }); }) .on('error', function (err) { @@ -336,7 +338,7 @@ function spec(b) { db.createReadStream({start: k, end: k + '~'}) .on('data', function (data) { var k = data.key.split('-'); - self._addSpendInfo(o, k[4], k[5]); + self._addSpendInfo(o, k[3], k[4], data.value); }) .on('error', function (err) { return e_c(err); @@ -438,17 +440,12 @@ function spec(b) { async.forEachLimit(tx.vin, CONCURRENCY, function(i, next_out) { db.batch() - .put( SPEND_PREFIX + i.txid + '-' + i.vout + '-' + tx.txid + '-' + i.n, ts || 0) + .put( SPEND_PREFIX + i.txid + '-' + i.vout + '-' + tx.txid + '-' + i.n, + ts || 0) .write(next_out); }, function (err) { - if (err) { - if (!err.message.match(/E11000/)) { - console.log('ERR at TX %s: %s', tx.txid, err); - return cb(err); - } - } - return p_c(); + return p_c(err); }); }, // Parse Outputs @@ -467,12 +464,20 @@ function spec(b) { var addr = o.scriptPubKey.addresses[0]; var sat = Math.round(o.value * util.COIN); - db.batch() - .put( OUTS_PREFIX + tx.txid + '-' + o.n, addr + ':' + sat) - .put( ADDR_PREFIX + addr + '-' + ts + '-' + tx.txid + - '-' + o.n, sat) - .write(next_out); + // existed? + var k = OUTS_PREFIX + tx.txid + '-' + o.n; + db.get(k, function(err) { + if (err && err.notFound) { + db.batch() + .put( k, addr + ':' + sat) + .put( ADDR_PREFIX + addr + '-' + tx.txid + '-' + o.n, sat+':'+ts) + .write(next_out); + } + else { + return next_out(); + } + }); } else { //console.log ('WARN in TX: %s could not parse OUTPUT %d', tx.txid, o.n); diff --git a/public/src/css/common.css b/public/src/css/common.css index 6c0febf0..dddd70cc 100644 --- a/public/src/css/common.css +++ b/public/src/css/common.css @@ -578,3 +578,24 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { width: 20%; } +@keyframes rotateThis { + from { transform: scale( 1 ) rotate( 0deg ); } + to { transform: scale( 1 ) rotate( 360deg ); } +} + +@-webkit-keyframes rotateThis { + from { -webkit-transform: scale( 1 ) rotate( 0deg ); } + to { -webkit-transform: scale( 1 ) rotate( 360deg ); } +} + +.icon-rotate { + animation-name: rotateThis; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: linear; + -webkit-animation-name: rotateThis; + -webkit-animation-duration: 2s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: linear; +} + diff --git a/public/src/js/services/socket.js b/public/src/js/services/socket.js index 9a2d459e..a2bf1e48 100644 --- a/public/src/js/services/socket.js +++ b/public/src/js/services/socket.js @@ -48,7 +48,10 @@ ScopedSocket.prototype.emit = function(event, data, callback) { angular.module('insight.socket').factory('getSocket', function($rootScope) { - var socket = io.connect(); + var socket = io.connect(null, { + 'reconnect': true, + 'reconnection delay': 500, +}); return function(scope) { var scopedSocket = new ScopedSocket(socket, $rootScope); scope.$on('$routeChangeStart', function() { diff --git a/public/views/address.html b/public/views/address.html index 4c5e9df6..d2f050ac 100644 --- a/public/views/address.html +++ b/public/views/address.html @@ -12,6 +12,7 @@

Summary

+
Confirmed
@@ -30,8 +31,25 @@ - +
No. Transactions {{address.txApperances}}
+
+
Unconfirmed
+ + + + + + + + + + + + +
Unconfirmed Txs Balance{{$root.currency.getConvertion(address.unconfirmedBalance)}}
No. Transactions{{address.unconfirmedTxApperances}}
+
+
diff --git a/public/views/includes/header.html b/public/views/includes/header.html index 0e2d7dc6..53dc0b38 100644 --- a/public/views/includes/header.html +++ b/public/views/includes/header.html @@ -24,11 +24,18 @@