Merge pull request #280 from matiu/bug/p2p-txs

Bug/p2p txs
This commit is contained in:
Matias Alejo Garcia 2014-02-13 01:29:03 -02:00
commit 3e1e94de62
22 changed files with 313 additions and 176 deletions

View File

@ -35,8 +35,8 @@ module.exports = function(grunt) {
livereload: true, livereload: true,
}, },
}, },
js2: { assets: {
files: ['public/src/**/*.js'], files: ['public/src/**/*.js', 'public/**/*.css'],
tasks: ['compile'], tasks: ['compile'],
options: { options: {
livereload: true, livereload: true,

View File

@ -104,24 +104,20 @@ exports.list = function(req, res) {
bdb.getBlocksByDate(gte, lte, function(err, blocks) { bdb.getBlocksByDate(gte, lte, function(err, blocks) {
if (err) { if (err) {
res.status(500).send(err); res.status(500).send(err);
} else { }
var blockshashList = []; else {
var limit = parseInt(req.query.limit || blocks.length); var l = blocks.length;
if (blocks.length < limit) { var limit = parseInt(req.query.limit || l);
limit = blocks.length; if (l < limit) limit = l;
}
for (var i = 0; i < limit; i++) { async.mapSeries(blocks,
blockshashList.push(blocks[i].hash); function(b, cb) {
} getBlock(b.hash, function(err, info) {
async.mapSeries(blockshashList, return cb(err,{
function(hash, cb) {
getBlock(hash, function(err, info) {
if (err) return cb(err);
return cb(err, {
height: info.height, height: info.height,
size: info.size, size: info.size,
hash: info.hash, hash: b.hash,
time: info.time, time: b.ts || info.time,
txlength: info.tx.length, txlength: info.tx.length,
}); });
}); });

View File

@ -44,5 +44,8 @@ module.exports.broadcastAddressTx = function(address, tx) {
}; };
module.exports.broadcastSyncInfo = function(historicSync) { module.exports.broadcastSyncInfo = function(historicSync) {
if (ios) ios.sockets.in('sync').emit('status', historicSync);
if (ios) {
ios.sockets.in('sync').emit('status', historicSync);
}
}; };

View File

@ -13,7 +13,11 @@ function spec() {
this.balanceSat = 0; this.balanceSat = 0;
this.totalReceivedSat = 0; this.totalReceivedSat = 0;
this.totalSentSat = 0; this.totalSentSat = 0;
this.txApperances = 0;
this.unconfirmedBalanceSat = 0;
this.txApperances = 0;
this.unconfirmedTxApperances= 0;
// TODO store only txids? +index? +all? // TODO store only txids? +index? +all?
this.transactions = []; this.transactions = [];
@ -51,12 +55,25 @@ function spec() {
}, },
enumerable: 1, 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) { Address.prototype.update = function(next) {
var self = this; var self = this;
if (!self.addrStr) return next(); if (!self.addrStr) return next();
var txs = [];
var db = new TransactionDb(); var db = new TransactionDb();
async.series([ async.series([
function (cb) { function (cb) {
@ -64,27 +81,52 @@ function spec() {
if (err) return cb(err); if (err) return cb(err);
txOut.forEach(function(txItem){ 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) { if (txItem.isConfirmed) {
var v = txItem.value_sat; self.txApperances += 1;
self.totalReceivedSat += v; self.totalReceivedSat += v;
self.transactions.push(txItem.txid); if (! txItem.spendTxId ) {
if (! txItem.spendTxId || !txItem.spendIsConfirmed) { //unspend
self.balanceSat += v;
}
else if(!txItem.spendIsConfirmed) {
// unspent // unspent
self.balanceSat += v; self.balanceSat += v;
self.txApperances +=1; self.unconfirmedBalanceSat -= v;
self.unconfirmedTxApperances += 1;
} }
else { else {
// spent // spent
self.totalSentSat += v; self.totalSentSat += v;
self.transactions.push(txItem.spendTxId); self.txApperances += 1;
self.txApperances +=2;
} }
} }
else {
self.unconfirmedBalanceSat += v;
self.unconfirmedTxApperances += 1;
}
}); });
return cb(); return cb();
}); });
}, },
], function (err) { ], 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); return next(err);
}); });
}; };

View File

@ -17,7 +17,7 @@ hash = 'e2253359458db3e732c82a43fc62f56979ff59928f25a2df34dfa443e9a41160';
var rpc = new RpcClient(config.bitcoind); var rpc = new RpcClient(config.bitcoind);
rpc.getRawTransaction( hash, 1, function(err, ret) { rpc.getBlockCount( function(err, ret) {
console.log('Err:'); console.log('Err:');
console.log(err); console.log(err);

View File

@ -5,11 +5,11 @@ require('classtool');
function spec(b) { function spec(b) {
var TIMESTAMP_PREFIX = 'b-ts-'; // b-ts-<ts> => <hash> var TIMESTAMP_PREFIX = 'bts-'; // b-ts-<ts> => <hash>
var PREV_PREFIX = 'b-prev-'; // b-prev-<hash> => <prev_hash> var PREV_PREFIX = 'bpr-'; // b-prev-<hash> => <prev_hash>
var NEXT_PREFIX = 'b-next-'; // b-next-<hash> => <next_hash> var NEXT_PREFIX = 'bne-'; // b-next-<hash> => <next_hash>
var MAIN_PREFIX = 'b-main-'; // b-main-<hash> => 1/0 var MAIN_PREFIX = 'bma-'; // b-main-<hash> => 1/0
var TIP = 'b-tip-'; // last block on the chain var TIP = 'bti-'; // last block on the chain
/** /**
@ -125,14 +125,11 @@ function spec(b) {
BlockDb.prototype.has = function(hash, cb) { BlockDb.prototype.has = function(hash, cb) {
var k = PREV_PREFIX + hash; var k = PREV_PREFIX + hash;
db.get(k, function (err,val) { db.get(k, function (err,val) {
var ret; var ret = true;
if (err && err.notFound) { if (err && err.notFound) {
err = null; err = null;
ret = false; ret = false;
} }
if (typeof val !== 'undefined') {
ret = true;
}
return cb(err, ret); return cb(err, ret);
}); });
}; };
@ -170,8 +167,9 @@ function spec(b) {
fillCache: true fillCache: true
}) })
.on('data', function (data) { .on('data', function (data) {
var k = data.key.split('-');
list.push({ list.push({
ts: data.key.replace(TIMESTAMP_PREFIX, ''), ts: k[1],
hash: data.value, hash: data.value,
}); });
}) })

View File

@ -7,8 +7,6 @@ require('classtool');
function spec() { function spec() {
var util = require('util'); var util = require('util');
var RpcClient = require('bitcore/RpcClient').class(); 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 Script = require('bitcore/Script').class();
var networks = require('bitcore/networks'); var networks = require('bitcore/networks');
var async = require('async'); var async = require('async');
@ -35,7 +33,6 @@ function spec() {
this.syncPercentage = 0; this.syncPercentage = 0;
this.syncedBlocks = 0; this.syncedBlocks = 0;
this.skippedBlocks = 0;
this.orphanBlocks = 0; this.orphanBlocks = 0;
this.type =''; this.type ='';
} }
@ -95,7 +92,6 @@ function spec() {
status: this.status, status: this.status,
blockChainHeight: this.blockChainHeight, blockChainHeight: this.blockChainHeight,
syncPercentage: this.syncPercentage, syncPercentage: this.syncPercentage,
skippedBlocks: this.skippedBlocks,
syncedBlocks: this.syncedBlocks, syncedBlocks: this.syncedBlocks,
orphanBlocks: this.orphanBlocks, orphanBlocks: this.orphanBlocks,
syncTipHash: this.sync.tip, syncTipHash: this.sync.tip,
@ -105,7 +101,7 @@ function spec() {
}; };
HistoricSync.prototype.updatePercentage = function() { 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); this.syncPercentage = parseFloat(100 * r).toFixed(3);
if (this.syncPercentage > 100) this.syncPercentage = 100; if (this.syncPercentage > 100) this.syncPercentage = 100;
}; };
@ -113,13 +109,15 @@ function spec() {
HistoricSync.prototype.showProgress = function() { HistoricSync.prototype.showProgress = function() {
var self = this; 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) { if (self.error) {
p('ERROR: ' + self.error); p('ERROR: ' + self.error);
} }
else { 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) { if (self.opts.shouldBroadcastSync) {
sockets.broadcastSyncInfo(self.info()); sockets.broadcastSyncInfo(self.info());
@ -194,22 +192,21 @@ function spec() {
self.status = 'syncing'; self.status = 'syncing';
} }
if ( (scanOpts.upToExisting && existed && if ( blockEnd && blockEnd === blockHash) {
self.syncedBlocks >= self.blockChainHeight) || p('blockEnd found!:' + blockEnd);
(blockEnd && blockEnd === blockHash)) { self.found=1;
}
if ( self.found && self.syncedBlocks >= self.blockChainHeight ) {
self.status = 'finished'; self.status = 'finished';
p('DONE. Found block: ', blockHash); p('DONE. Height: ' , self.syncedBlocks);
self.showProgress();
return cb(err); return cb(err);
} }
// Continue // Continue
if (blockInfo) { if (blockInfo) {
if (existed) self.syncedBlocks++;
self.skippedBlocks++;
else
self.syncedBlocks++;
// recursion // recursion
if (scanOpts.prev && blockInfo.previousblockhash) 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) { HistoricSync.prototype.getBlockFromFile = function(cb) {
var self = this; var self = this;
@ -289,7 +250,7 @@ function spec() {
var s = new Script(o.s); var s = new Script(o.s);
var addrs = self.getAddrStr(s); var addrs = self.sync.getAddrStr(s);
// support only for p2pubkey p2pubkeyhash and p2sh // support only for p2pubkey p2pubkeyhash and p2sh
if (addrs.length === 1) { if (addrs.length === 1) {
@ -308,7 +269,6 @@ function spec() {
var self = this; var self = this;
self.showProgress(); self.showProgress();
self.getBlockFromFile(function(err, blockInfo) { self.getBlockFromFile(function(err, blockInfo) {
if (err) { if (err) {
self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]',
@ -363,6 +323,7 @@ function spec() {
var self = this; var self = this;
var lastBlock; var lastBlock;
var tip;
async.series([ async.series([
function(cb) { function(cb) {
@ -376,17 +337,25 @@ function spec() {
function (cb) { return self.getBlockCount(cb); }, function (cb) { return self.getBlockCount(cb); },
function(cb) { function(cb) {
if (!scanOpts.reverse) return cb(); if (!scanOpts.reverse) return cb();
self.rpc.getBlockHash(self.blockChainHeight, function(err, res) { self.rpc.getBlockHash(self.blockChainHeight, function(err, res) {
if (err) return cb(err); if (err) return cb(err);
lastBlock = res.result; lastBlock = res.result;
return cb(); return cb();
}); });
}, },
function(cb) { function(cb) {
if (scanOpts.upToExisting) { if (!scanOpts.reverse) return cb();
// should be isOrphan = true or null to be more accurate. 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) { self.countNotOrphan(function(err, count) {
if (err) return cb(err); if (err) return cb(err);
@ -421,7 +390,7 @@ function spec() {
if (scanOpts.reverse) { if (scanOpts.reverse) {
start = lastBlock; start = lastBlock;
end = self.genesis; end = tip || self.genesis;
scanOpts.prev = true; scanOpts.prev = true;
} }
else { else {
@ -448,12 +417,14 @@ function spec() {
}); });
}); });
}, function(err) { }, function(err) {
self.showProgress();
return next(err); return next(err);
}); });
} }
else { else {
self.type = 'from RPC calls'; self.type = 'from RPC calls';
self.getPrevNextBlock(start, end, scanOpts, function(err) { self.getPrevNextBlock(start, end, scanOpts, function(err) {
self.showProgress();
return next(err); return next(err);
}); });
} }
@ -490,10 +461,9 @@ function spec() {
} }
} }
else { 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'); p('Got ' + count + ' out of ' + self.blockChainHeight + ' blocks');
scanOpts.reverse = true; scanOpts.reverse = true;
scanOpts.upToExisting = true;
} }
return self.importHistory(scanOpts, next); return self.importHistory(scanOpts, next);
}); });

View File

@ -6,6 +6,7 @@ function spec() {
var CoinConst = require('bitcore/const'); var CoinConst = require('bitcore/const');
var coinUtil = require('bitcore/util/util'); var coinUtil = require('bitcore/util/util');
var Sync = require('./Sync').class(); var Sync = require('./Sync').class();
var Script = require('bitcore/Script').class();
var Peer = require('bitcore/Peer').class(); var Peer = require('bitcore/Peer').class();
var config = require('../config/config'); var config = require('../config/config');
var networks = require('bitcore/networks'); var networks = require('bitcore/networks');
@ -57,8 +58,23 @@ function spec() {
}; };
PeerSync.prototype.handleTx = function(info) { PeerSync.prototype.handleTx = function(info) {
var self =this;
var tx = info.message.tx.getStandardizedObject(); var tx = info.message.tx.getStandardizedObject();
console.log('[p2p_sync] Handle tx: ' + tx.hash); 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) { this.sync.storeTxs([tx], function(err) {
if (err) { if (err) {

View File

@ -6,7 +6,12 @@ require('classtool');
function spec() { function spec() {
var sockets = require('../app/controllers/socket.js'); var sockets = require('../app/controllers/socket.js');
var BlockDb = require('./BlockDb').class(); var BlockDb = require('./BlockDb').class();
var bitutil = require('bitcore/util/util');
var Address = require('bitcore/Address').class();
var TransactionDb = require('./TransactionDb').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'); var async = require('async');
@ -18,6 +23,7 @@ function spec() {
self.opts = opts; self.opts = opts;
this.bDb = new BlockDb(opts); this.bDb = new BlockDb(opts);
this.txDb = new TransactionDb(opts); this.txDb = new TransactionDb(opts);
this.network = config.network === 'testnet' ? networks.testnet: networks.livenet;
return cb(); return cb();
}; };
@ -276,6 +282,44 @@ function spec() {
return cb(err); 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; return Sync;
} }
module.defineClass(spec); module.defineClass(spec);

View File

@ -6,17 +6,17 @@ require('classtool');
function spec(b) { function spec(b) {
// blockHash -> txid mapping // blockHash -> txid mapping
var IN_BLK_PREFIX = 'tx-b-'; //tx-b-<txid>-<block> => 1/0 (connected or not) var IN_BLK_PREFIX = 'txb-'; //txb-<txid>-<block> => 1/0 (connected or not)
// Only for orphan blocks // Only for orphan blocks
var FROM_BLK_PREFIX = 'tx-'; //tx-<block>-<txid> => 1 var FROM_BLK_PREFIX = 'tx-'; //tx-<block>-<txid> => 1
// to show tx outs // to show tx outs
var OUTS_PREFIX = 'txouts-'; //txouts-<txid>-<n> => [addr, btc_sat] var OUTS_PREFIX = 'txo-'; //txo-<txid>-<n> => [addr, btc_sat]
var SPEND_PREFIX = 'txs-'; //txs-<txid(out)>-<n(out)>-<txid(in)>-<n(in)> = ts
// to sum up addr balance // to sum up addr balance (only outs, spends are gotten later)
var ADDR_PREFIX = 'txouts-addr-'; //txouts-addr-<addr>-<ts>-<txid>-<n> => + btc_sat var ADDR_PREFIX = 'txa-'; //txa-<addr>-<txid>-<n> => + btc_sat:ts
var SPEND_PREFIX = 'txouts-spend-';//txouts-spend-<txid(out)>-<n(out)>-<txid(in)>-<n(in)> = ts
// TODO: use bitcore networks module // TODO: use bitcore networks module
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'; 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.spendTxId) {
if (!r.multipleSpendAttempts) { if (!r.multipleSpendAttempts) {
r.multipleSpendAttempts = [{ r.multipleSpendAttempts = [{
@ -85,6 +85,7 @@ function spec(b) {
else { else {
r.spendTxId = txid; r.spendTxId = txid;
r.spendIndex = parseInt(index); r.spendIndex = parseInt(index);
r.spendTs = parseInt(ts);
} }
}; };
@ -118,12 +119,12 @@ function spec(b) {
db.createReadStream({start: k, end: k + '~'}) db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) { .on('data', function (data) {
var k = data.key.split('-'); 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 + 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) { .on('error', function (err) {
return cb(err); return cb(err);
@ -144,7 +145,7 @@ function spec(b) {
db.createReadStream({start: k, end: k + '~'}) db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) { .on('data', function (data) {
var k = data.key.split('-'); 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) { .on('error', function (err) {
return cb(err); return cb(err);
@ -173,6 +174,7 @@ function spec(b) {
return c_in(); // error not scalated return c_in(); // error not scalated
} }
info.firstSeenTs = ret.spendTs;
i.unconfirmedInput = i.unconfirmedInput; i.unconfirmedInput = i.unconfirmedInput;
i.addr = ret.addr; i.addr = ret.addr;
i.valueSat = ret.valueSat; i.valueSat = ret.valueSat;
@ -264,7 +266,7 @@ function spec(b) {
db.createReadStream({start: k, end: k + '~'}) db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) { .on('data', function (data) {
var k = data.key.split('-'); 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) { .on('error', function (error) {
return cb(error); return cb(error);
@ -320,10 +322,10 @@ function spec(b) {
var k = data.key.split('-'); var k = data.key.split('-');
var v = data.value.split(':'); var v = data.value.split(':');
ret.push({ ret.push({
txid: k[2],
index: parseInt(k[3]),
value_sat: parseInt(v[0]), value_sat: parseInt(v[0]),
ts: parseInt(k[3]), ts: parseInt(v[1]),
txid: k[4],
index: parseInt(k[5]),
}); });
}) })
.on('error', function (err) { .on('error', function (err) {
@ -336,7 +338,7 @@ function spec(b) {
db.createReadStream({start: k, end: k + '~'}) db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) { .on('data', function (data) {
var k = data.key.split('-'); 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) { .on('error', function (err) {
return e_c(err); return e_c(err);
@ -438,17 +440,12 @@ function spec(b) {
async.forEachLimit(tx.vin, CONCURRENCY, async.forEachLimit(tx.vin, CONCURRENCY,
function(i, next_out) { function(i, next_out) {
db.batch() 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); .write(next_out);
}, },
function (err) { function (err) {
if (err) { return p_c(err);
if (!err.message.match(/E11000/)) {
console.log('ERR at TX %s: %s', tx.txid, err);
return cb(err);
}
}
return p_c();
}); });
}, },
// Parse Outputs // Parse Outputs
@ -467,12 +464,20 @@ function spec(b) {
var addr = o.scriptPubKey.addresses[0]; var addr = o.scriptPubKey.addresses[0];
var sat = Math.round(o.value * util.COIN); 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 { else {
//console.log ('WARN in TX: %s could not parse OUTPUT %d', tx.txid, o.n); //console.log ('WARN in TX: %s could not parse OUTPUT %d', tx.txid, o.n);

View File

@ -578,3 +578,24 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
width: 20%; 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;
}

View File

@ -48,7 +48,10 @@ ScopedSocket.prototype.emit = function(event, data, callback) {
angular.module('insight.socket').factory('getSocket', angular.module('insight.socket').factory('getSocket',
function($rootScope) { function($rootScope) {
var socket = io.connect(); var socket = io.connect(null, {
'reconnect': true,
'reconnection delay': 500,
});
return function(scope) { return function(scope) {
var scopedSocket = new ScopedSocket(socket, $rootScope); var scopedSocket = new ScopedSocket(socket, $rootScope);
scope.$on('$routeChangeStart', function() { scope.$on('$routeChangeStart', function() {

View File

@ -12,6 +12,7 @@
</div> </div>
<div class="m20v"> <div class="m20v">
<h4>Summary</h4> <h4>Summary</h4>
<h5>Confirmed</h5>
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>
@ -30,8 +31,25 @@
<td class="small">No. Transactions</td> <td class="small">No. Transactions</td>
<td class="address ellipsis text-right">{{address.txApperances}}</td> <td class="address ellipsis text-right">{{address.txApperances}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div data-ng-show="address.unconfirmedTxApperances">
<h5>Unconfirmed</h5>
<table class="table">
<tbody>
<tr>
<td class="small">Unconfirmed Txs Balance</td>
<td class="address ellipsis text-right">{{$root.currency.getConvertion(address.unconfirmedBalance)}}</td>
</tr>
<tr>
<td class="small">No. Transactions</td>
<td class="address ellipsis text-right">{{address.unconfirmedTxApperances}}</td>
</tr>
</tbody>
</table>
</div>
</div> <!-- END OF TRANSACTIONS TABLE --> </div> <!-- END OF TRANSACTIONS TABLE -->
<div data-ng-include src="'/views/includes/currency.html'"></div> <div data-ng-include src="'/views/includes/currency.html'"></div>
</div> <!-- END OF COL-MD-3 --> </div> <!-- END OF COL-MD-3 -->

View File

@ -24,11 +24,18 @@
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li> <li>
<div class="status" data-ng-controller="StatusController"> <div class="status" data-ng-controller="StatusController">
<a href="#" data-ng-init="getSync()"> <div data-ng-init="getSync()" class="pull-left">
<span class="t text-danger" data-ng-show="sync.error" tooltip="{{sync.error}}" tooltip-placement="bottom"> ERROR </span> <span class="t text-danger" data-ng-show="sync.error" tooltip="{{sync.error}}" tooltip-placement="bottom">
<span class="t text-warning " tooltip="{{sync.syncedBlocks}} / {{sync.blockChainHeight}} synced. {{sync.skippedBlocks}} skipped" tooltip-placement="bottom" data-ng-show="sync.status==='syncing'"> {{sync.status}} {{sync.syncPercentage}}%</span> <span class="glyphicon glyphicon-warning-sign"></span>
<span class="t text-default" tooltip="Historic sync finished" tooltip-placement="bottom" data-ng-show="sync.status==='finished'"> On sync</span> ERROR
</a> &middot; </span>
<span class="t" tooltip="{{sync.syncedBlocks}} / {{sync.blockChainHeight}} synced. {{sync.skippedBlocks}} skipped" tooltip-placement="bottom" data-ng-show="sync.status==='syncing'">
<span class="glyphicon glyphicon-refresh icon-rotate"></span>
{{sync.status}} {{sync.syncPercentage}}%
</span>
<span class="t text-default" tooltip="Historic sync finished" tooltip-placement="bottom" data-ng-show="sync.status==='finished'"> On sync </span>
</div>
&nbsp; &middot;
<span data-ng-init="getStatus('Info')"> <span data-ng-init="getStatus('Info')">
<strong>Conn</strong> {{info.connections}} <strong>Conn</strong> {{info.connections}}
</span> &middot; </span> &middot;

View File

@ -62,7 +62,7 @@
and websocket APIs that can be used for writing web wallets and other apps and websocket APIs that can be used for writing web wallets and other apps
that need more advanced blockchain queries than provided by bitcoind RPC. that need more advanced blockchain queries than provided by bitcoind RPC.
Check out the <a href="http://github.com/bitpay/insight" target="_blank">source code</a>.</p> Check out the <a href="http://github.com/bitpay/insight" target="_blank">source code</a>.</p>
<p>Insight is still in development, so be sure to report any bugs and provide feedback for improvement at our <a href="https://github.com/bitpay/insight/issues">github issue tracker</a>.</p> <p>Insight is still in development, so be sure to report any bugs and provide feedback for improvement at our <a href="https://github.com/bitpay/insight/issues" target="_blank">github issue tracker</a>.</p>
<div id="powered" class="row"> <div id="powered" class="row">
<div class="powered-text"> <div class="powered-text">
<small class="text-muted">Powered by</small> <small class="text-muted">Powered by</small>

View File

@ -42,8 +42,31 @@
</tbody> </tbody>
</table> </table>
<h2>Last Block</h2>
<table class="table" style="table-layout: fixed" data-ng-controller="StatusController" data-ng-init="getStatus('LastBlockHash')">
<thead data-ng-include src="'/views/includes/infoStatus.html'"> </thead>
<tr>
<td>Last Block Hash (Bitcoind)</td>
<td class="text-right ellipsis"><a href="/block/{{lastblockhash}}">{{lastblockhash}}</a></td>
</tr>
<tr>
<td>Current Blockchain Tip(Insight)</td>
<td class="text-right ellipsis"><a href="/block/{{syncTipHash}}">{{syncTipHash}}</a></td>
</tbody>
</table>
<h2>Transaction Output Set Information</h2> <h2>Transaction Output Set Information</h2>
<table class="table" style="table-layout: fixed" data-ng-controller="StatusController" data-ng-init="getStatus('TxOutSetInfo')">
<div data-ng-controller="StatusController">
<button data-ng-click="txoutLoading=1;getStatus('TxOutSetInfo')" class="btn btn-default" data-ng-show="!txoutsetinfo.height">
Show Transaction Output data
<span data-ng-show="txoutLoading" class="glyphicon glyphicon-refresh icon-rotate"></span>
</button >
<table class="table" data-ng-show="txoutsetinfo.height" style="table-layout: fixed" >
<thead data-ng-include src="'/views/includes/infoStatus.html'"> </thead> <thead data-ng-include src="'/views/includes/infoStatus.html'"> </thead>
<tbody> <tbody>
<tr> <tr>
@ -76,23 +99,12 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<h2>Last Block</h2>
<table class="table" style="table-layout: fixed" data-ng-controller="StatusController" data-ng-init="getStatus('LastBlockHash')">
<thead data-ng-include src="'/views/includes/infoStatus.html'"> </thead>
<tr>
<td>Last Block Hash (Bitcoind)</td>
<td class="text-right ellipsis"><a href="/block/{{lastblockhash}}">{{lastblockhash}}</a></td>
</tr>
<tr>
<td>Current Blockchain Tip(Insight)</td>
<td class="text-right ellipsis"><a href="/block/{{syncTipHash}}">{{syncTipHash}}</a></td>
</tbody>
</table>
</div> <!-- END OF COL-8 --> </div> <!-- END OF COL-8 -->
<div class="col-xs-12 col-md-4 col-gray"> <div class="col-xs-12 col-md-4 col-gray">
<h2>Bitcoin node information</h2> <h2>Bitcoin node information</h2>
<table class="table" data-ng-controller="StatusController" data-ng-init="getStatus('Info')"> <table class="table" data-ng-controller="StatusController" data-ng-init="getStatus('Info')">
@ -118,11 +130,7 @@
<td>Connections to other nodes</td> <td>Connections to other nodes</td>
<td class="text-right">{{info.connections}}</td> <td class="text-right">{{info.connections}}</td>
</tr> </tr>
<tr> <tr>
<td>Proxy setting</td>
<td class="text-right">{{info.proxy}}</td>
</tr>
<tr>
<td>Mining Difficulty</td> <td>Mining Difficulty</td>
<td class="text-right">{{info.difficulty}}</td> <td class="text-right">{{info.difficulty}}</td>
</tr> </tr>
@ -131,16 +139,8 @@
<td class="text-right">{{info.testnet}}</td> <td class="text-right">{{info.testnet}}</td>
</tr> </tr>
<tr> <tr>
<td>Keypool Oldest Date</td> <td>Proxy setting</td>
<td class="text-right">{{info.keypoololdest*1000 | date:'medium' }}</td> <td class="text-right">{{info.proxy}}</td>
</tr>
<tr>
<td>Keypool Size</td>
<td class="text-right">{{info.keypoolsize}}</td>
</tr>
<tr>
<td>Default Transaction Fee (BTC)</td>
<td class="text-right">{{info.paytxfee}}</td>
</tr> </tr>
<tr> <tr>
<td>Info Errors</td> <td>Info Errors</td>

View File

@ -21,8 +21,15 @@
</tr> </tr>
<tr> <tr>
<td><strong>Received Time </strong></td> <td><strong>Received Time </strong></td>
<td class="text-muted text-right">{{tx.time * 1000|date:'medium'}}</td> <td data-ng-show="tx.firstSeenTs" class="text-muted text-right">{{tx.firstSeenTs * 1000|date:'medium'}}</td>
<td data-ng-show="!tx.firstSeenTs" class="text-muted text-right">N/A</td>
</tr> </tr>
<tr>
<td><strong>Mined Time </strong></td>
<td data-ng-show="tx.time" class="text-muted text-right">{{tx.time * 1000|date:'medium'}}</td>
<td data-ng-show="!tx.time" class="text-muted text-right">N/A</td>
</tr>
</tbody> </tbody>
</table> </table>
<div data-ng-include src="'/views/includes/currency.html'"></div> <div data-ng-include src="'/views/includes/currency.html'"></div>

View File

@ -6,7 +6,13 @@
<a class="txid" href="/tx/{{tx.txid}}">{{tx.txid}}</a> <a class="txid" href="/tx/{{tx.txid}}">{{tx.txid}}</a>
</div> </div>
</div> </div>
<div class="col-xs-12 col-md-6 text-right" data-ng-show="tx.time">
<div class="col-xs-12 col-md-6 text-right" data-ng-show="tx.firstSeenTs">
first seen at
<time>{{tx.firstSeenTs * 1000 | date:'medium'}}</time>
</div>
<div class="col-xs-12 col-md-6 text-right" data-ng-show="tx.time && !tx.firstSeenTs">
mined at
<time>{{tx.time * 1000 | date:'medium'}}</time> <time>{{tx.time * 1000 | date:'medium'}}</time>
</div> </div>
</div> </div>

View File

@ -30,6 +30,8 @@ describe('Address balances', function() {
a.update(function(err) { a.update(function(err) {
if (err) done(err); if (err) done(err);
assert.equal(v.addr, a.addrStr); assert.equal(v.addr, a.addrStr);
assert.equal(a.unconfirmedTxApperances ,0, 'unconfirmedTxApperances: 0');
assert.equal(a.unconfirmedBalanceSat ,0, 'unconfirmedBalanceSat: 0');
if (v.txApperances) if (v.txApperances)
assert.equal(v.txApperances, a.txApperances, 'txApperances: ' + a.txApperances); assert.equal(v.txApperances, a.txApperances, 'txApperances: ' + a.txApperances);
if (v.totalReceived) assert.equal(v.totalReceived, a.totalReceived, 'received: ' + a.totalReceived); if (v.totalReceived) assert.equal(v.totalReceived, a.totalReceived, 'received: ' + a.totalReceived);

View File

@ -43,10 +43,10 @@
}, },
{ {
"addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29", "addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29",
"txApperances": 2041,
"balance": 1199.74393853, "balance": 1199.74393853,
"totalReceived": 1199.74393853, "totalReceived": 1199.74393853,
"totalSent": 0 "totalSent": 0,
"txApperances": 5763
}, },
{ {
"addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H", "addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H",

View File

@ -6,8 +6,8 @@
{ {
"txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237", "txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237",
"toRm": [ "toRm": [
"txouts-spend-86a03cac7d87f596008c6d5a8d3fd8b88842932ea6f0337673eda16f6b472f7f-0", "txs-86a03cac7d87f596008c6d5a8d3fd8b88842932ea6f0337673eda16f6b472f7f-0",
"txouts-spend-bcd8da8ee847da377f8aaca92502c05e5f914c6a2452753146013b0e642a25a0-0" "txs-bcd8da8ee847da377f8aaca92502c05e5f914c6a2452753146013b0e642a25a0-0"
], ],
"items": [ "items": [
{ {
@ -25,7 +25,7 @@
{ {
"txid": "b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee", "txid": "b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee",
"toRm": [ "toRm": [
"txouts-spend-01621403689cb4a95699a3dbae029d7031c5667678ef14e2054793954fb27917-0" "txs-01621403689cb4a95699a3dbae029d7031c5667678ef14e2054793954fb27917-0"
], ],
"items": [ "items": [
{ {
@ -43,7 +43,7 @@
{ {
"txid": "ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b", "txid": "ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b",
"toRm": [ "toRm": [
"txouts-spend-2d7b680fb06e4d7eeb65ca49ac7522276586e0090b7fe662fc708129429c5e6a-0" "txs-2d7b680fb06e4d7eeb65ca49ac7522276586e0090b7fe662fc708129429c5e6a-0"
], ],
"items": [ "items": [
{ {

View File

@ -13,11 +13,11 @@ var async = require('async');
program program
.version(SYNC_VERSION) .version(SYNC_VERSION)
.option('-N --network [livenet]', 'Set bitcoin network [testnet]', 'testnet') .option('-N --network [livenet]', 'Set bitcoin network [testnet]', 'testnet')
.option('-S --smart', 'genesis stored? uptoexisting = 1', 1)
.option('-D --destroy', 'Remove current DB (and start from there)', 0) .option('-D --destroy', 'Remove current DB (and start from there)', 0)
.option('-R --reverse', 'Sync backwards', 0) .option('-R --reverse', 'Sync backwards', 0)
.option('-U --uptoexisting', 'Sync only until an existing block is found', 0) .option('-U --uptoexisting', 'Sync only until old Tip block is found', 0)
.option('-F --fromfiles', 'Sync using bitcoind .dat block files (faster)', 0) .option('-F --fromfiles', 'Sync using bitcoind .dat block files (faster)', 0)
.option('-S --smart', 'genesis stored? uptoexisting = 1, fromFiles=1 [default]', true)
.option('-v --verbose', 'Verbose 0/1', 0) .option('-v --verbose', 'Verbose 0/1', 0)
.parse(process.argv); .parse(process.argv);
@ -34,7 +34,7 @@ async.series([
}, },
function(cb) { function(cb) {
if (program.smart) { if (typeof program.smart === 'undefined' || parseInt(program.smart) ) {
historicSync.smartImport({ historicSync.smartImport({
destroy: program.destroy, destroy: program.destroy,
},cb); },cb);
@ -43,7 +43,6 @@ async.series([
historicSync.importHistory({ historicSync.importHistory({
destroy: program.destroy, destroy: program.destroy,
reverse: program.reverse, reverse: program.reverse,
upToExisting: program.uptoexisting,
fromFiles: program.fromfiles, fromFiles: program.fromfiles,
}, cb); }, cb);
} }