conflict solved

This commit is contained in:
Mario Colque 2014-02-10 04:10:13 -03:00
commit 3e9c0e0517
30 changed files with 1227 additions and 570 deletions

View File

@ -72,7 +72,6 @@ console.log('[blocks.js.60]: could not get %s from RPC. Orphan? Error?', blockha
* List of blocks by date
*/
exports.list = function(req, res) {
var limit = req.query.limit || -1;
var isToday = false;
//helper to convert timestamps to yyyy-mm-dd format
@ -103,14 +102,18 @@ exports.list = function(req, res) {
var prev = formatTimestamp(new Date((gte - 86400) * 1000));
var next = formatTimestamp(new Date(lte * 1000));
bdb.getBlocksByDate(gte, lte, limit, function(err, blocks) {
bdb.getBlocksByDate(gte, lte, function(err, blocks) {
if (err) {
res.status(500).send(err);
}
else {
var blockshashList = [];
for(var i=0;i<blocks.length;i++) {
blockshashList.unshift(blocks[i].hash);
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, getBlock, function(err, allblocks) {
res.jsonp({
@ -118,9 +121,10 @@ exports.list = function(req, res) {
length: allblocks.length,
pagination: {
next: next,
prev: prev,
current: dateStr,
isToday: isToday
prev: prev,
currentTs: lte-1,
current: dateStr,
isToday: isToday
}
});
});

View File

@ -14,15 +14,15 @@ module.exports.init = function(app, io_ext) {
});
};
module.exports.broadcast_tx = function(tx) {
module.exports.broadcastTx = function(tx) {
if (ios) ios.sockets.in('inv').emit('tx', tx);
};
module.exports.broadcast_block = function(block) {
module.exports.broadcastBlock = function(block) {
if (ios) ios.sockets.in('inv').emit('block', block);
};
module.exports.broadcast_address_tx = function(address, tx) {
module.exports.broadcastAddressTx = function(address, tx) {
if (ios) ios.sockets.in(address).emit(address, tx);
};

View File

@ -21,8 +21,7 @@ function spec() {
var a = new BitcoreAddress(addrStr);
a.validate();
this.addrStr = addrStr;
Object.defineProperty(this, 'totalSent', {
get: function() {
return parseFloat(this.totalSentSat) / parseFloat(BitcoreUtil.COIN);
@ -56,26 +55,30 @@ function spec() {
Address.prototype.update = function(next) {
var self = this;
if (!self.addrStr) return next();
var db = new TransactionDb();
async.series([
function (cb) {
db.fromAddr(self.addrStr, function(err,txOut){
if (err) return cb(err);
txOut.forEach(function(txItem){
var v = txItem.value_sat;
self.totalReceivedSat += v;
self.transactions.push(txItem.txid);
if (! txItem.spendTxId) {
// unspent
self.balanceSat += v;
self.txApperances +=1;
}
else {
// spent
self.totalSentSat += v;
self.transactions.push(txItem.spendTxId);
self.txApperances +=2;
if (txItem.isConfirmed) {
var v = txItem.value_sat;
self.totalReceivedSat += v;
self.transactions.push(txItem.txid);
if (! txItem.spendTxId || !txItem.spendIsConfirmed) {
// unspent
self.balanceSat += v;
self.txApperances +=1;
}
else {
// spent
self.totalSentSat += v;
self.transactions.push(txItem.spendTxId);
self.txApperances +=2;
}
}
});
return cb();

View File

@ -5,10 +5,12 @@ require('classtool');
function spec() {
var async = require('async');
var RpcClient = require('bitcore/RpcClient').class();
var BlockDb = require('../../lib/BlockDb').class();
var config = require('../../config/config');
var rpc = new RpcClient(config.bitcoind);
function Status() {
this.bDb = new BlockDb();
}
Status.prototype.getInfo = function(next) {
@ -21,7 +23,7 @@ function spec() {
that.info = info.result;
return cb();
});
}
},
], function (err) {
return next(err);
});
@ -69,7 +71,8 @@ function spec() {
that.bestblockhash = bbh.result;
return cb();
});
}
},
], function (err) {
return next(err);
});
@ -77,27 +80,29 @@ function spec() {
Status.prototype.getLastBlockHash = function(next) {
var that = this;
async.waterfall(
[
function(callback){
rpc.getBlockCount(function(err, bc){
if (err) return callback(err);
callback(null, bc.result);
});
},
function(bc, callback){
rpc.getBlockHash(bc, function(err, bh){
if (err) return callback(err);
callback(null, bh.result);
});
}
],
function (err, result) {
that.lastblockhash = result;
return next();
}
);
that.bDb.getTip(function(err,tip) {
that.syncTipHash = tip;
async.waterfall(
[
function(callback){
rpc.getBlockCount(function(err, bc){
if (err) return callback(err);
callback(null, bc.result);
});
},
function(bc, callback){
rpc.getBlockHash(bc, function(err, bh){
if (err) return callback(err);
callback(null, bh.result);
});
}
],
function (err, result) {
that.lastblockhash = result;
return next();
}
);
});
};
return Status;

30
dev-util/level.js Executable file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env node
'use strict';
var config = require('../config/config'),
levelup = require('levelup');
var s = process.argv[2];
var isBlock = process.argv[3] === '1';
var dbPath = config.leveldb + (isBlock ? '/blocks' : '/txs');
console.log('DB: ',dbPath); //TODO
var db = levelup(dbPath );
db.createReadStream({start: s, end: s+'~'})
.on('data', function (data) {
console.log(data.key + ' => ' + data.value); //TODO
})
.on('error', function () {
})
.on('end', function () {
});

View File

@ -1,19 +0,0 @@
#!/usr/bin/env node
var
config = require('../config/config'),
levelup = require('levelup');
db = levelup(config.leveldb + '/txs');
var s = 'txouts-addr-mgqvRGJMwR9JU5VhJ3x9uX9MTkzTsmmDgQ';
db.createReadStream({start: s, end: s+'~'})
.on('data', function (data) {
console.log('[block-level.js.11:data:]',data); //TODO
if (data==false) c++;
})
.on('end', function () {
});

View File

@ -7,8 +7,8 @@ txindex=1
#rpcallowip=192.168.1.*
#rpcallowip='192.168.1.*'
#rpcallowip=127.0.0.1
#rpcallowip=*
rpcallowip=*
port=18333
rpcport=18332
testnet=3

View File

@ -40,6 +40,21 @@ var walk = function(path) {
walk(models_path);
var syncOpts = {
};
/**
* p2pSync process
*/
if (!config.disableP2pSync) {
var ps = new PeerSync();
ps.init({
shouldBroadcast: true,
}, function() {
ps.run();
});
}
/**
* historic_sync process
*/
@ -49,8 +64,7 @@ if (!config.disableHistoricSync) {
historicSync = new HistoricSync();
historicSync.init({
shouldBroadcast: true,
networkName: config.network
shouldBroadcastSync: true,
}, function(err) {
if (err) {
var txt = 'ABORTED with error: ' + err.message;
@ -60,25 +74,16 @@ if (!config.disableHistoricSync) {
historicSync.smartImport({}, function(err){
var txt = 'ended.';
if (err) txt = 'ABORTED with error: ' + err.message;
else
ps.allowReorgs = true;
console.log('[historic_sync] ' + txt, historicSync.info());
});
}
});
}
/**
* p2pSync process
*/
if (!config.disableP2pSync) {
var ps = new PeerSync();
ps.init({
broadcast_txs: true,
broadcast_address_tx: true,
broadcast_blocks: true,
}, function() {
ps.run();
});
}
//express settings
require('./config/express')(expressApp, historicSync);

View File

@ -5,8 +5,11 @@ require('classtool');
function spec(b) {
var TIMESTAMP_ROOT = 'b-ts-'; // b-ts-<ts> => <hash>
var PREV_ROOT = 'b-prev-'; // b-prev-<hash> => <prev_hash> (0 if orphan)
var TIMESTAMP_PREFIX = 'b-ts-'; // b-ts-<ts> => <hash>
var PREV_PREFIX = 'b-prev-'; // b-prev-<hash> => <prev_hash>
var NEXT_PREFIX = 'b-next-'; // b-next-<hash> => <next_hash>
var MAIN_PREFIX = 'b-main-'; // b-main-<hash> => 1/0
var TIP = 'b-tip-'; // last block on the chain
/**
@ -20,7 +23,6 @@ function spec(b) {
var db = b.db || levelup(config.leveldb + '/blocks');
var rpc = b.rpc || new RpcClient(config.bitcoind);
var BlockDb = function() {
};
@ -38,53 +40,76 @@ function spec(b) {
});
};
// adds a block. Does not update Next pointer in
// the block prev to the new block, nor TIP pointer
//
BlockDb.prototype.add = function(b, cb) {
if (!b.hash) return cb(new Error('no Hash at Block.save'));
var time_key = TIMESTAMP_ROOT +
var time_key = TIMESTAMP_PREFIX +
( b.time || Math.round(new Date().getTime() / 1000) );
db.batch()
return db.batch()
.put(time_key, b.hash)
.put(PREV_ROOT + b.hash, b.previousblockhash)
.put(MAIN_PREFIX + b.hash, 1)
.put(PREV_PREFIX + b.hash, b.previousblockhash)
.write(cb);
};
BlockDb.prototype.setOrphan = function(hash, cb) {
var k = PREV_ROOT + hash;
db.get(k, function (err,oldPrevHash) {
if (err || !oldPrevHash) return cb(err);
db.put(PREV_ROOT + hash, 0, function() {
return cb(err, oldPrevHash);
});
BlockDb.prototype.getTip = function(cb) {
db.get(TIP, function(err, val) {
return cb(err,val);
});
// We keep the block in TIMESTAMP_ROOT
};
//mainly for testing
BlockDb.prototype.setPrev = function(hash, prevHash, cb) {
db.put(PREV_ROOT + hash, prevHash, function(err) {
BlockDb.prototype.setTip = function(hash, cb) {
db.put(TIP, hash, function(err) {
return cb(err);
});
};
//mainly for testing
BlockDb.prototype.setPrev = function(hash, prevHash, cb) {
db.put(PREV_PREFIX + hash, prevHash, function(err) {
return cb(err);
});
};
BlockDb.prototype.getPrev = function(hash, cb) {
db.get(PREV_ROOT + hash, function(err,val) {
db.get(PREV_PREFIX + hash, function(err,val) {
if (err && err.notFound) { err = null; val = null;}
return cb(err,val);
});
};
BlockDb.prototype.getNext = function(hash, cb) {
db.get(NEXT_PREFIX + hash, function(err,val) {
if (err && err.notFound) { err = null; val = null;}
return cb(err,val);
});
};
BlockDb.prototype.isMain = function(hash, cb) {
db.get(MAIN_PREFIX + hash, function(err, val) {
if (err && err.notFound) { err = null; val = 0;}
return cb(err,parseInt(val));
});
};
BlockDb.prototype.setMain = function(hash, isMain, cb) {
if (!isMain) console.log('\tNew orphan: %s',hash);
db.put(MAIN_PREFIX + hash, isMain?1:0, function(err) {
return cb(err);
});
};
BlockDb.prototype.setNext = function(hash, nextHash, cb) {
db.put(NEXT_PREFIX + hash, nextHash, function(err) {
return cb(err);
});
};
BlockDb.prototype.countNotOrphan = function(cb) {
var c = 0;
console.log('Counting connected blocks. This could take some minutes');
db.createReadStream({start: PREV_ROOT, end: PREV_ROOT + '~' })
db.createReadStream({start: MAIN_PREFIX, end: MAIN_PREFIX + '~' })
.on('data', function (data) {
if (data.value !== 0) c++;
})
@ -96,8 +121,9 @@ function spec(b) {
});
};
// .has() return true orphans also
BlockDb.prototype.has = function(hash, cb) {
var k = PREV_ROOT + hash;
var k = PREV_PREFIX + hash;
db.get(k, function (err,val) {
var ret;
if (err && err.notFound) {
@ -118,8 +144,8 @@ function spec(b) {
if (err && err.code === -5) return cb();
if (err) return cb(err);
info.reward = BitcoreBlock.getBlockValue(info.height) / util.COIN ;
if (info.result.height)
info.result.reward = BitcoreBlock.getBlockValue(info.result.height) / util.COIN ;
return cb(null, {
hash: hash,
info: info.result,
@ -127,17 +153,16 @@ function spec(b) {
});
};
BlockDb.prototype.getBlocksByDate = function(start_ts, end_ts, limit, cb) {
BlockDb.prototype.getBlocksByDate = function(start_ts, end_ts, cb) {
var list = [];
db.createReadStream({
start: TIMESTAMP_ROOT + start_ts,
end: TIMESTAMP_ROOT + end_ts,
fillCache: true,
limit: parseInt(limit) // force to int
start: TIMESTAMP_PREFIX + start_ts,
end: TIMESTAMP_PREFIX + end_ts,
fillCache: true
})
.on('data', function (data) {
list.push({
ts: data.key.replace(TIMESTAMP_ROOT, ''),
ts: data.key.replace(TIMESTAMP_PREFIX, ''),
hash: data.value,
});
})
@ -145,7 +170,7 @@ function spec(b) {
return cb(err);
})
.on('end', function () {
return cb(null, list);
return cb(null, list.reverse());
});
};

View File

@ -97,6 +97,7 @@ function spec() {
skippedBlocks: this.skippedBlocks,
syncedBlocks: this.syncedBlocks,
orphanBlocks: this.orphanBlocks,
syncTipHash: this.sync.tip,
error: this.error,
type: this.type,
};
@ -105,6 +106,8 @@ function spec() {
HistoricSync.prototype.showProgress = function() {
var self = this;
if ( ( self.syncedBlocks + self.skippedBlocks) % self.step !== 1) return;
if (self.error) {
p('ERROR: ' + self.error);
}
@ -114,9 +117,14 @@ function spec() {
p(util.format('status: [%d%%] skipped: %d ', self.syncPercentage, self.skippedBlocks));
}
if (self.opts.shouldBroadcast) {
if (self.opts.shouldBroadcastSync) {
sockets.broadcastSyncInfo(self.info());
}
//TODO
// if (self.syncPercentage > 10) {
// process.exit(-1);
// }
};
HistoricSync.prototype.getPrevNextBlock = function(blockHash, blockEnd, scanOpts, cb) {
@ -143,18 +151,23 @@ function spec() {
},
//show some (inacurate) status
function(c) {
if ( ( self.syncedBlocks + self.skippedBlocks) % self.step === 1) {
self.showProgress();
}
self.showProgress();
return c();
},
function(c) {
self.rpc.getBlock(blockHash, function(err, ret) {
if (err) return c(err);
if (ret) {
blockInfo = ret.result;
// this is to match block retreived from file
if (blockInfo.hash === self.genesis)
blockInfo.previousblockhash = self.network.genesisBlock.prev_hash.toString('hex');
}
else {
blockInfo = null;
}
blockInfo = ret ? ret.result : null;
return c();
});
},
@ -162,34 +175,26 @@ function spec() {
function(c) {
if (existed) return c();
self.sync.storeBlock(blockInfo, function(err) {
// When storing files from RPC recusively, reorgs are disabled
self.sync.storeTipBlock(blockInfo, false, function(err) {
return c(err);
});
},
/* TODO: Should Start to sync backwards? (this is for partial syncs)
function(c) {
if (blockInfo.result.prevblockhash != current.blockHash) {
p("reorg?");
scanOpts.prev = 1;
}
return c();
}
*/
], function(err) {
}], function(err) {
if (err) {
self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', blockHash, err, self.syncedBlocks));
self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]',
blockHash, err, self.syncedBlocks));
return cb(err);
}
else {
self.status = 'syncing';
}
if ( (scanOpts.upToExisting && existed && self.syncedBlocks >= self.blockChainHeight) ||
if ( (scanOpts.upToExisting && existed &&
self.syncedBlocks >= self.blockChainHeight) ||
(blockEnd && blockEnd === blockHash)) {
self.status = 'finished';
p('DONE. Found existing block: ', blockHash);
p('DONE. Found block: ', blockHash);
self.showProgress();
return cb(err);
}
@ -251,106 +256,79 @@ function spec() {
return addrStrs;
};
HistoricSync.prototype.getBlockFromFile = function(scanOpts, cb) {
HistoricSync.prototype.getBlockFromFile = function(cb) {
var self = this;
var blockInfo;
var existed;
async.series([
//show some (inacurate) status
function(c) {
if ( ( self.syncedBlocks + self.skippedBlocks) % self.step === 1) {
self.showProgress();
}
return c();
},
//get Info
function(c) {
self.blockExtractor.getNextBlock(function(err, b) {
if (err || ! b) return cb(err);
self.blockExtractor.getNextBlock(function(err, b) {
if (err || ! b) return c(err);
blockInfo = b.getStandardizedObject(b.txs, self.network);
// blockInfo.curWork = Deserialize.intFromCompact(b.bits);
// We keep the RPC field names
blockInfo.previousblockhash = blockInfo.prev_block;
blockInfo = b.getStandardizedObject(b.txs, self.network);
// blockInfo.curWork = Deserialize.intFromCompact(b.bits);
// We keep the RPC field names
blockInfo.previousblockhash = blockInfo.prev_block;
var ti=0;
// Get TX Address
b.txs.forEach(function(t) {
var ti=0;
// Get TX Address
b.txs.forEach(function(t) {
var objTx = blockInfo.tx[ti++];
var objTx = blockInfo.tx[ti++];
//add time from block
objTx.time = blockInfo.time;
//add time from block
objTx.time = blockInfo.time;
var to=0;
t.outs.forEach( function(o) {
var to=0;
t.outs.forEach( function(o) {
var s = new Script(o.s);
var addrs = self.getAddrStr(s);
var s = new Script(o.s);
var addrs = self.getAddrStr(s);
// support only p2pubkey p2pubkeyhash and p2sh
if (addrs.length === 1) {
objTx.out[to].addrStr = addrs[0];
}
to++;
});
// support only for p2pubkey p2pubkeyhash and p2sh
if (addrs.length === 1) {
objTx.out[to].addrStr = addrs[0];
}
to++;
});
return c();
});
},
//check prev
function(c) {
if (blockInfo && self.prevHash && blockInfo.previousblockhash !== self.prevHash) {
console.log('Hole found @%s', blockInfo.hash);
console.log('From: %s To: %s', self.prevHash, blockInfo.previousblockhash);
self.sync.checkOrphan(self.prevHash, blockInfo.previousblockhash, c);
}
else return c();
},
//check it
function(c) {
if (!blockInfo) return c();
self.sync.bDb.has(blockInfo.hash, function(err, had) {
existed = had;
return c(err);
});
},
//store it
function(c) {
if (!blockInfo || existed) return c();
self.sync.storeBlock(blockInfo, function(err) {
return c(err);
});
},
function(c) {
if (blockInfo && blockInfo.hash) {
self.prevHash = blockInfo.hash;
if (existed)
self.skippedBlocks++;
else
self.syncedBlocks++;
} else
self.status = 'finished';
return c();
},
], function(err) {
if (err) {
self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]', blockInfo ? blockInfo.hash : '-', err, self.syncedBlocks));
}
return cb(err);
return cb(err,blockInfo);
});
};
HistoricSync.prototype.nextBlockFromFile = function(scanOpts, cb) {
var self = this;
self.showProgress();
self.getBlockFromFile(function(err, blockInfo) {
if (err) {
self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]',
blockInfo ? blockInfo.hash : '-', err, self.syncedBlocks));
return cb(err);
}
self.sync.storeTipBlock(blockInfo, function(err) {
if (blockInfo && blockInfo.hash) {
self.syncedBlocks++;
} else
self.status = 'finished';
if (err) {
self.setError(util.format('ERROR: @%s: %s [count: syncedBlocks: %d]',
blockInfo ? blockInfo.hash : '-', err, self.syncedBlocks));
}
return cb(err);
});
});
};
HistoricSync.prototype.countNotOrphan = function(cb) {
var self = this;
@ -452,12 +430,15 @@ function spec() {
p(' scanOpts: ', JSON.stringify(scanOpts));
if (scanOpts.fromFiles) {
self.status = 'syncing';
self.type = 'from .dat Files';
self.type = 'from .dat Files';
async.whilst(function() {
return self.status === 'syncing';
}, function (w_cb) {
self.getBlockFromFile(scanOpts, function(err) {
self.nextBlockFromFile(scanOpts, function(err) {
setImmediate(function(){
return w_cb(err);
});

View File

@ -8,6 +8,7 @@ function spec() {
var Sync = require('./Sync').class();
var Peer = require('bitcore/Peer').class();
var config = require('../config/config');
var networks = require('bitcore/networks');
var peerdb_fn = 'peerdb.json';
@ -16,18 +17,12 @@ function spec() {
PeerSync.prototype.init = function(opts, cb) {
if (!opts) opts = {};
var network = opts && (opts.network || 'testnet');
this.verbose = opts.verbose;
this.peerdb = undefined;
this.sync = new Sync({
networkName: network
});
this.allowReorgs = false;
this.sync = new Sync();
this.PeerManager = require('bitcore/PeerManager').createClass({
opts: {
network: network
}
network: (config.network === 'testnet' ? networks.testnet : networks.livenet)
});
this.peerman = new this.PeerManager();
this.load_peers();
@ -45,49 +40,42 @@ function spec() {
fs.writeFileSync(peerdb_fn, JSON.stringify(this.peerdb));
};
PeerSync.prototype.handle_inv = function(info) {
var self = this;
PeerSync.prototype.handleInv = function(info) {
var invs = info.message.invs;
invs.forEach(function(inv) {
if (self.verbose) {
console.log('[p2p_sync] Handle inv for a ' + CoinConst.MSG.to_str(inv.type));
}
console.log('[p2p_sync] Handle inv for a ' + CoinConst.MSG.to_str(inv.type));
});
// TODO: should limit the invs to objects we haven't seen yet
info.conn.sendGetData(invs);
};
PeerSync.prototype.handle_tx = function(info) {
PeerSync.prototype.handleTx = function(info) {
var tx = info.message.tx.getStandardizedObject();
if (this.verbose) {
console.log('[p2p_sync] Handle tx: ' + tx.hash);
}
console.log('[p2p_sync] Handle tx: ' + tx.hash);
this.sync.storeTxs([tx.hash], function(err) {
if (err) {
console.log('[PeerSync.js.71:err:]',err); //TODO
console.log('[p2p_sync] Error in handle TX: ' + JSON.stringify(err));
}
});
};
PeerSync.prototype.handle_block = function(info) {
PeerSync.prototype.handleBlock = function(info) {
var self = this;
var block = info.message.block;
var blockHash = coinUtil.formatHashFull(block.calcHash());
if (this.verbose) {
console.log('[p2p_sync] Handle block: ' + blockHash);
}
console.log('[p2p_sync] Handle block: %s (allowReorgs: %s)', blockHash, self.allowReorgs);
var tx_hashes = block.txs.map(function(tx) {
return coinUtil.formatHashFull(tx.hash);
});
this.sync.storeBlock({
this.sync.storeTipBlock({
'hash': blockHash,
'tx': tx_hashes,
// TODO NEXT BLOCK / PREV BLOCK?
},
function(err) {
'previousblockhash': coinUtil.formatHashFull(block.prev_hash),
}, self.allowReorgs, function(err) {
if (err) {
console.log('[p2p_sync] Error in handle Block: ' + err);
}
@ -97,9 +85,7 @@ function spec() {
PeerSync.prototype.handle_connected = function(data) {
var peerman = data.pm;
var peers_n = peerman.peers.length;
if (this.verbose) {
console.log('[p2p_sync] Connected to ' + peers_n + ' peer' + (peers_n !== 1 ? 's': ''));
}
console.log('[p2p_sync] Connected to ' + peers_n + ' peer' + (peers_n !== 1 ? 's': ''));
};
PeerSync.prototype.run = function() {
@ -111,9 +97,9 @@ function spec() {
});
this.peerman.on('connection', function(conn) {
conn.on('inv', self.handle_inv.bind(self));
conn.on('block', self.handle_block.bind(self));
conn.on('tx', self.handle_tx.bind(self));
conn.on('inv', self.handleInv.bind(self));
conn.on('block', self.handleBlock.bind(self));
conn.on('tx', self.handleTx.bind(self));
});
this.peerman.on('connect', self.handle_connected.bind(self));

View File

@ -11,15 +11,13 @@ function spec() {
function Sync() {
this.bDb = new BlockDb();
this.txDb = new TransactionDb();
}
Sync.prototype.init = function(opts, cb) {
var self = this;
self.opts = opts;
this.bDb = new BlockDb(opts);
this.txDb = new TransactionDb(opts);
return cb();
};
@ -39,101 +37,246 @@ function spec() {
], next);
};
/*
* Arrives a NEW block, which is the new TIP
*
* Case 0) Simple case
* A-B-C-D-E(TIP)-NEW
*
* Case 1)
* A-B-C-D-E(TIP)
* \
* NEW
*
* 1) Declare D-E orphans (and possible invalidate TXs on them)
*
* Case 2)
* A-B-C-D-E(TIP)
* \
* F-G-NEW
* 1) Set F-G as connected (mark TXs as valid)
* 2) Declare D-E orphans (and possible invalidate TXs on them)
*
*
* Case 3)
*
* A-B-C-D-E(TIP) ... NEW
*
* NEW is ignored
*
*/
Sync.prototype.storeBlock = function(block, cb) {
Sync.prototype.storeTipBlock = function(b, allowReorgs, cb) {
if (typeof allowReorgs === 'function') {
cb = allowReorgs;
allowReorgs = true;
}
if (!b) return cb();
var self = this;
var oldTip, oldNext, needReorg = false;
var newPrev = b.previousblockhash;
var updatedTxs, updatedAddrs;
async.series([
function(c) {
self.bDb.has(b.hash, function(err, val) {
return c(err ||
(val ? new Error('WARN: Ignoring already existing block:' + b.hash) : null ));
});
},
function(c) {
if (!allowReorgs) return c();
self.bDb.has(newPrev, function(err, val) {
if (!val && newPrev.match(/^0+$/)) return c();
return c(err ||
(!val ? new Error('WARN: Ignoring block with non existing prev:' + b.hash) : null ));
});
},
function(c) {
self.txDb.createFromBlock(b, function(err, addrs) {
updatedTxs = b.tx;
updatedAddrs = addrs;
return c(err);
});
},
function(c) {
if (!allowReorgs) return c();
self.bDb.getTip(function(err, val) {
oldTip = val;
if (oldTip && newPrev !== oldTip) needReorg = true;
return c();
});
},
function(c) {
if (!needReorg) return c();
self.bDb.getNext( newPrev, function(err, val) {
if (err) return c(err);
oldNext = val;
return c();
});
},
function(c) {
self.bDb.add(b, c);
},
function(c) {
if (!needReorg) return c();
console.log('NEW TIP: %s NEED REORG (old tip: %s)', b.hash, oldTip);
// TODO should modify updatedTxs and addrs.
self.processReorg(oldTip, oldNext, newPrev, c);
},
function(c) {
self.bDb.setTip(b.hash, function(err) {
if (err) return c(err);
self.bDb.setNext(newPrev, b.hash, function(err) {
return c(err);
});
});
}],
function(err) {
if (!err) self._handleBroadcast(b.hash, updatedTxs, updatedAddrs);
if (err && err.toString().match(/WARN/) ) {
console.log(err);
err=null;
}
return cb(err);
});
};
Sync.prototype.processReorg = function(oldTip, oldNext, newPrev, cb) {
var self = this;
self.txDb.createFromBlock(block, function(err, insertedTxs, updateAddrs) {
if (err) return cb(err);
var orphanizeFrom;
self.bDb.add(block, function(err){
if (err) return cb(err);
self._handleBroadcast(block, insertedTxs, updateAddrs);
return cb();
});
async.series([
function(c) {
self.bDb.isMain(newPrev, function(err,val) {
if (!val) return c();
console.log('# Reorg Case 1)');
// case 1
orphanizeFrom = oldNext;
return c(err);
});
},
function(c) {
if (orphanizeFrom) return c();
console.log('# Reorg Case 2)');
self.setBranchConnectedBackwards(newPrev, function(err, yHash, newYHashNext) {
if (err) return c(err);
self.bDb.getNext(yHash, function(err, yHashNext) {
orphanizeFrom = yHashNext;
self.bDb.setNext(yHash, newYHashNext, function(err) {
return c(err);
});
});
});
},
function(c) {
if (!orphanizeFrom) return c();
self.setBranchOrphan(orphanizeFrom, function(err) {
return c(err);
});
},
],
function(err) {
return cb(err);
});
};
Sync.prototype.setBlockMain = function(hash, isMain, cb) {
var self = this;
self.bDb.setMain(hash, isMain, function(err) {
if (err) return cb(err);
return self.txDb.handleBlockChange(hash, isMain, cb);
});
};
Sync.prototype.checkOrphan = function(fromBlock, toBlock, c) {
var self = this;
var hash = fromBlock;
var co = 0;
var limit = 10;
var cont = 1;
Sync.prototype.setBranchOrphan = function(fromHash, cb) {
var self = this,
hashInterator = fromHash;
async.whilst(
function () {
if (++co > limit) {
console.log('[Sync.js.109] WARN: Reach reog depth limit'); //TODO
}
return cont && hash && hash !== toBlock && co < limit;
},
function (w_c) {
//check with RPC if the block is mainchain
self.bDb.fromHashWithInfo(hash, function (err, info) {
function() { return hashInterator; },
function(c) {
self.setBlockMain(hashInterator, false, function(err) {
if (err) return cb(err);
self.bDb.getNext(hashInterator, function (err, val) {
hashInterator = val;
return c(err);
});
});
}, cb);
};
if (!info) {
console.log('[Sync.js.107:hash:ORPHAN]',hash); //TODO
self.txDb.setOrphan(hash, function(err) {
if (err) return w_c(err);
self.bDb.setOrphan(hash, function(err, prevHash){
hash = prevHash;
return w_c(err);
});
Sync.prototype.setBranchConnectedBackwards = function(fromHash, cb) {
var self = this,
hashInterator = fromHash,
lastHash = fromHash,
isMain;
async.doWhilst(
function(c) {
self.setBlockMain(hashInterator, true, function (err) {
if (err) return c(err);
self.bDb.getPrev(hashInterator, function (err, val) {
if (err) return c(err);
lastHash = hashInterator;
hashInterator = val;
self.bDb.isMain(hashInterator, function (err, val) {
isMain = val;
return c();
});
}
else {
console.log('[Sync.js.107:hash:NOT ORPHAN]',hash); //TODO
cont = 0;
return w_c();
}
});
});
},
function (err) {
return c(err);
function() { return hashInterator && !isMain; },
function(err) {
console.log('\tFound yBlock:', hashInterator);
return cb(err, hashInterator, lastHash);
}
);
};
Sync.prototype._handleBroadcast = function(hash, inserted_txs, updated_addrs) {
Sync.prototype._handleBroadcast = function(hash, updatedTxs, updatedAddrs) {
var self = this;
if (hash && self.opts.broadcast_blocks) {
sockets.broadcast_block({hash: hash});
}
if (inserted_txs && self.opts.broadcast_txs) {
inserted_txs.forEach(function(tx) {
sockets.broadcast_tx(tx);
});
}
if (updated_addrs && self.opts.broadcast_addresses) {
updated_addrs.forEach(function(addr, txs){
txs.forEach(function(addr, t){
sockets.broadcast_address_tx(addr, {'txid': t});
if (self.opts.shouldBroadcast) {
if (hash) {
sockets.broadcastBlock(hash);
}
if (updatedTxs) {
updatedTxs.forEach(function(tx) {
sockets.broadcastTx(tx);
});
});
}
if (updatedAddrs ) {
updatedAddrs.forEach(function(addr, txs){
txs.forEach(function(addr, t){
sockets.broadcastAddressTx(addr, t);
});
});
}
}
};
Sync.prototype.storeTxs = function(txs, cb) {
var self = this;
// TODO -- Peertopeer
/*
self.txDb.createFromTxs(txs, function(err, inserted_txs, updated_addrs) {
self.txDb.createFromArray(txs, null, function(err, updatedAddrs) {
if (err) return cb(err);
self._handleBroadcast(null, inserted_txs, updated_addrs);
self._handleBroadcast(null, txs, updatedAddrs);
return cb(err);
});
*/
};
return Sync;
}

View File

@ -5,15 +5,18 @@ require('classtool');
function spec(b) {
// blockHash -> txid mapping (to reorgs)
var ROOT = 'tx-b-'; //tx-b-<txid>-<block> => 1/0 (connected or not)
// blockHash -> txid mapping
var IN_BLK_PREFIX = 'tx-b-'; //tx-b-<txid>-<block> => 1/0 (connected or not)
// Only for orphan blocks
var FROM_BLK_PREFIX = 'tx-'; //tx-<block>-<txid> => 1
// to show tx outs
var OUTS_ROOT = 'txouts-'; //txouts-<txid>-<n> => [addr, btc_sat]
var OUTS_PREFIX = 'txouts-'; //txouts-<txid>-<n> => [addr, btc_sat]
// to sum up addr balance
var ADDR_ROOT = 'txouts-addr-'; //txouts-addr-<addr>-<ts>-<txid>-<n> => + btc_sat
var SPEND_ROOT = 'txouts-spend-';//txouts-spend-<txid(out)>-<n(out)> => [txid(in),n(in),ts]
var ADDR_PREFIX = 'txouts-addr-'; //txouts-addr-<addr>-<ts>-<txid>-<n> => + btc_sat
var SPEND_PREFIX = 'txouts-spend-';//txouts-spend-<txid(out)>-<n(out)> => [txid(in),n(in),ts]
// TODO: use bitcore networks module
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
@ -51,7 +54,7 @@ function spec(b) {
// TransactionDb.prototype.fromTxIdOne = function(txid, cb) { TODO
TransactionDb.prototype.has = function(txid, cb) {
var k = OUTS_ROOT + txid;
var k = OUTS_PREFIX + txid;
db.get(k, function (err,val) {
var ret;
@ -69,7 +72,7 @@ function spec(b) {
TransactionDb.prototype.fromTxId = function(txid, cb) {
var k = OUTS_ROOT + txid;
var k = OUTS_PREFIX + txid;
var ret=[];
// outs.
@ -87,7 +90,7 @@ function spec(b) {
return cb(err);
})
.on('end', function () {
var k = SPEND_ROOT + txid;
var k = SPEND_PREFIX + txid;
var l = ret.length;
db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) {
@ -174,23 +177,41 @@ function spec(b) {
TransactionDb.prototype.fromTxIdN = function(txid, n, cb) {
var k = OUTS_ROOT + txid + '-' + n;
var k = OUTS_PREFIX + txid + '-' + n;
db.get(k, function (err,val) {
if (err && err.notFound) {
err = null;
}
var a = val.split(':');
var a = val?val.split(':'):[null,null];
return cb(err, a[0], parseInt(a[1]));
});
};
TransactionDb.prototype.fromAddr = function(addr, cb) {
TransactionDb.prototype.fillConfirmations = function(o, cb) {
var self = this;
var k = ADDR_ROOT + addr;
self.isConfirmed(o.txid, function(err,is) {
if (err) return cb(err);
o.isConfirmed = is;
if (!o.spendTxId) return cb();
self.isConfirmed(o.spendTxId, function(err,is) {
if (err) return cb(err);
o.spendIsConfirmed = is;
return cb();
});
});
};
TransactionDb.prototype.fromAddr = function(addr, cb) {
var self = this;
var k = ADDR_PREFIX + addr;
var ret=[];
//
db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) {
var k = data.key.split('-');
@ -208,7 +229,7 @@ function spec(b) {
.on('end', function () {
async.each(ret, function(o, e_c) {
var k = SPEND_ROOT + o.txid + '-' + o.index;
var k = SPEND_PREFIX + o.txid + '-' + o.index;
db.get(k, function(err, val) {
if (err && err.notFound) err=null;
if (err || !val) return e_c(err);
@ -220,8 +241,12 @@ function spec(b) {
return e_c();
});
},
function(err) {
return cb(err,ret);
function() {
async.each(ret, function(o, e_c){
self.fillConfirmations(o,e_c);
},function(err) {
return cb(err,ret);
});
});
});
};
@ -233,16 +258,16 @@ function spec(b) {
async.series([
function(c) {
db.createReadStream({
start: OUTS_ROOT + txid,
end: OUTS_ROOT + txid + '~',
start: OUTS_PREFIX + txid,
end: OUTS_PREFIX + txid + '~',
}).pipe(
db.createWriteStream({type:'del'})
).on('close', c);
},
function(c) {
db.createReadStream({
start: SPEND_ROOT + txid,
end: SPEND_ROOT + txid + '~'
start: SPEND_PREFIX + txid,
end: SPEND_PREFIX + txid + '~'
})
.pipe(
db.createWriteStream({type:'del'})
@ -293,7 +318,8 @@ function spec(b) {
};
TransactionDb.prototype.add = function(tx, cb) {
TransactionDb.prototype.add = function(tx, blockhash, cb) {
var self = this;
var addrs = [];
var is_new = true;
@ -309,7 +335,7 @@ function spec(b) {
async.forEachLimit(tx.vin, CONCURRENCY,
function(i, next_out) {
db.batch()
.put( SPEND_ROOT + i.txid + '-' + i.vout ,
.put( SPEND_PREFIX + i.txid + '-' + i.vout ,
tx.txid + ':' + i.n + ':' + ts)
.write(next_out);
},
@ -333,15 +359,15 @@ function spec(b) {
! o.scriptPubKey.addresses[1] // TODO : not supported
){
// This is only to broadcast (WIP)
// if (addrs.indexOf(o.scriptPubKey.addresses[0]) === -1) {
// addrs.push(o.scriptPubKey.addresses[0]);
// }
if (addrs.indexOf(o.scriptPubKey.addresses[0]) === -1) {
addrs.push(o.scriptPubKey.addresses[0]);
}
var addr = o.scriptPubKey.addresses[0];
var sat = Math.round(o.value * util.COIN);
db.batch()
.put( OUTS_ROOT + tx.txid + '-' + o.n, addr + ':' + sat)
.put( ADDR_ROOT + addr + '-' + ts + '-' + tx.txid +
.put( OUTS_PREFIX + tx.txid + '-' + o.n, addr + ':' + sat)
.put( ADDR_PREFIX + addr + '-' + ts + '-' + tx.txid +
'-' + o.n, sat)
.write(next_out);
@ -363,19 +389,80 @@ function spec(b) {
}
return p_c();
});
}], function(err) {
},
function (p_c) {
if (!blockhash) return p_c();
return self.setConfirmation(tx.txid,blockhash, true, p_c);
},
], function(err) {
return cb(err, addrs, is_new);
});
};
TransactionDb.prototype.setConfirmation = function(txId, blockHash, confirmed, c) {
if (!blockHash) return c();
confirmed = confirmed ? 1 : 0;
db.batch()
.put(IN_BLK_PREFIX + txId + '-' + blockHash, confirmed)
.put(FROM_BLK_PREFIX + blockHash + '-' + txId, 1)
.write(c);
};
// This slowdown addr balance calculation by 100%
TransactionDb.prototype.isConfirmed = function(txId, c) {
var k = IN_BLK_PREFIX + txId;
var ret = false;
db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) {
if (data.value === '1') ret = true;
})
.on('error', function (err) {
return c(err);
})
.on('end', function (err) {
return c(err,ret);
});
};
TransactionDb.prototype.handleBlockChange = function(hash, isMain, cb) {
var toChange = [];
console.log('\tSearching Txs from block:' + hash);
var k = FROM_BLK_PREFIX + hash;
var k2 = IN_BLK_PREFIX;
// This is slow, but prevent us to create a new block->tx index.
db.createReadStream({start: k, end: k + '~'})
.on('data', function (data) {
var ks = data.key.split('-');
toChange.push({
key: k2 + ks[2] + '-' + ks[1],
type: 'put',
value: isMain?1:0,
});
})
.on('error', function (err) {
return cb(err);
})
.on('end', function (err) {
if (err) return cb(err);
console.log('\t%s %d Txs', isMain?'Confirming':'Invalidating',toChange.length);
db.batch(toChange, cb);
});
};
// txs can be a [hashes] or [txObjects]
TransactionDb.prototype.createFromArray = function(txs, blockHash, next) {
var self = this;
if (!txs) return next();
// TODO
var insertedTxs = [];
var updatedAddrs = {};
var updatedAddrs = []; // TODO
async.forEachLimit(txs, CONCURRENCY, function(t, each_cb) {
if (typeof t === 'string') {
@ -387,32 +474,19 @@ function spec(b) {
TransactionRpc.getRpcInfo(t, function(err, inInfo) {
if (!inInfo) return each_cb(err);
self.add(inInfo, function(err) {
if (err || !blockHash) return each_cb(err);
db.put(ROOT + t + '-' + blockHash, 1, function(err) {
return each_cb(err);
});
});
return self.add(inInfo, blockHash, each_cb);
});
}
else {
self.add(t, function(err) {
if (err) return each_cb(err);
db.put(ROOT + t.txid + '-' + blockHash, 1, function(err) {
return each_cb(err);
});
});
return self.add(t, blockHash, each_cb);
}
},
function(err) {
return next(err, insertedTxs, updatedAddrs);
});
};
return next(err, updatedAddrs);
});
};
// txs can be a [hashes] or [txObjects]
TransactionDb.prototype.createFromBlock = function(b, next) {
var self = this;
if (!b || !b.tx) return next();

View File

@ -7,8 +7,8 @@ function spec(b) {
var RpcClient = require('bitcore/RpcClient').class(),
// networks = require('bitcore/network'),
BitcoreTransaction = require('bitcore/Transaction').class(),
BitcoreBlock = require('bitcore/Block').class(),
util = require('bitcore/util/util'),
// BitcoreBlock = require('bitcore/Block').class(),
// util = require('bitcore/util/util'),
config = require('../config/config');
var rpc = b.rpc || new RpcClient(config.bitcoind);
@ -24,10 +24,6 @@ function spec(b) {
// Inputs
if (tx.isCoinBase()) {
info.isCoinBase = true;
var reward = BitcoreBlock.getBlockValue(info.height) / util.COIN;
info.vin[0].reward = reward;
info.valueIn = reward;
}
var n =0;

View File

@ -31,10 +31,8 @@ function($scope, $rootScope, $routeParams, $location, Global, Address, getSocket
var socket = getSocket($scope);
socket.emit('subscribe', $routeParams.addrStr);
socket.on($routeParams.addrStr, function(tx) {
console.log('atx ' + tx.txid);
var beep = new Audio('/sound/transaction.mp3');
beep.play();
$rootScope.$broadcast('tx', tx.txid);
console.log('AddressTx event received ' + tx);
$rootScope.$broadcast('tx', tx);
});
$scope.params = $routeParams;

View File

@ -16,6 +16,14 @@ angular.module('insight.blocks').controller('BlocksController',
});
}
$scope.humanSince = function(time) {
var m = moment.unix(time).startOf('day');
var b = moment().startOf('day');
return m.max().from(b);
};
$scope.list = function() {
$scope.loading = true;

View File

@ -33,7 +33,7 @@ angular.module('insight.system').controller('HeaderController',
};
socket.on('block', function(block) {
var blockHash = block.hash.toString();
var blockHash = block.toString();
console.log('Updated Blocks Height!');
_getBlock(blockHash);
});

View File

@ -4,9 +4,18 @@ var TRANSACTION_DISPLAYED = 5;
var BLOCKS_DISPLAYED = 5;
angular.module('insight.system').controller('IndexController',
function($scope, $rootScope, Global, getSocket, Blocks, Block, Transactions, Transaction) {
function($scope, $rootScope, Global, getSocket, Blocks, Transaction) {
$scope.global = Global;
var _getBlocks = function() {
Blocks.get({
limit: BLOCKS_DISPLAYED
}, function(res) {
$scope.blocks = res.blocks;
$scope.blocksLength = res.lenght;
});
};
var _getTransaction = function(txid, cb) {
Transaction.get({
txId: txid
@ -15,14 +24,6 @@ angular.module('insight.system').controller('IndexController',
});
};
var _getBlock = function(hash) {
Block.get({
blockHash: hash
}, function(res) {
$scope.blocks.unshift(res);
});
};
var socket = getSocket($scope);
socket.emit('subscribe', 'inv');
@ -32,7 +33,7 @@ angular.module('insight.system').controller('IndexController',
socket.on('tx', function(tx) {
console.log('Transaction received! ' + JSON.stringify(tx));
var txStr = tx.txid.toString();
var txStr = tx.toString();
_getTransaction(txStr, function(res) {
$scope.txs.unshift(res);
if (parseInt($scope.txs.length, 10) >= parseInt(TRANSACTION_DISPLAYED, 10)) {
@ -42,13 +43,9 @@ angular.module('insight.system').controller('IndexController',
});
socket.on('block', function(block) {
var blockHash = block.hash.toString();
console.log('Block received! ' + JSON.stringify(block));
if (parseInt($scope.blocks.length, 10) > parseInt(BLOCKS_DISPLAYED, 10) - 1) {
$scope.blocks.pop();
}
_getBlock(blockHash);
var blockHash = block.toString();
console.log('Block received! ' + JSON.stringify(blockHash));
_getBlocks();
});
$scope.humanSince = function(time) {
@ -57,18 +54,7 @@ angular.module('insight.system').controller('IndexController',
};
$scope.index = function() {
Blocks.get({
limit: BLOCKS_DISPLAYED
}, function(res) {
$scope.blocks = res.blocks;
$scope.blocksLength = res.lenght;
});
Transactions.get({
limit: TRANSACTION_DISPLAYED
}, function(res) {
$scope.txs = res.txs;
});
_getBlocks();
};
$scope.txs = [];

View File

@ -47,6 +47,11 @@
<td> <strong> Height </strong></td>
<td class="text-right text-muted">{{block.height}}</td>
</tr>
<tr>
<td> <strong> Block Reward </strong></td>
<td class="text-right text-muted">{{$root.currency.getConvertion(block.reward)}}</td>
</tr>
<tr>
<td> <strong> Timestamp </strong></td>
<td class="text-right text-muted">{{block.time * 1000 | date:'medium'}}</td>

View File

@ -8,10 +8,15 @@
<h3>Blocks <br> mined on:</h3>
</div>
</div>
<p class="lead text-center m20v">{{pagination.current}}</p>
<p class="lead text-center m20v">{{pagination.current}} UTC</p>
<p class="lead text-center m20v" data-ng-show="loading">&nbsp;</p>
<p class="text-center m20v" data-ng-show="pagination.isToday && !loading">Today</p>
<p class="text-center m20v" data-ng-show="!pagination.isToday && !loading">{{humanSince(pagination.currentTs)}}
<p class="text-center m20v" data-ng-show="loading">&nbsp;</p>
<div class="m50v text-center">
<a class="btn btn-primary" href="/blocks-date/{{pagination.prev}}"><small>&larr; {{pagination.prev}}</small></a>
<a class="btn btn-primary" href="/blocks-date/{{pagination.next}}" data-ng-disabled="pagination.isToday"><small>{{pagination.next}} &rarr;</small></a>
<a class="btn btn-primary" href="/blocks-date/{{pagination.next}}" data-ng-show="!pagination.isToday"><small>{{pagination.next}} &rarr;</small></a>
</div>
</div>
<div class="col-xs-12 col-md-9 col-md-offset-3">

View File

@ -31,9 +31,7 @@
</tbody>
</table>
<h2> About </h2>
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quas, sint, neque harum libero eos maiores rerum rem fuga quae architecto ea incidunt dolore optio ullam sit placeat vero perferendis beatae?</p>
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, unde quidem commodi dolor asperiores ullam molestias sit a sapiente ipsa!</p>
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt tempora fugiat dolorem cupiditate perspiciatis praesentium.</p>
<p class="text-muted">Insight is a bitcoin blockchain API for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the <a href="http://github.com/bitpay/insight">source code</a>.</p>
</div>
<div class="col-xs-12 col-md-4 col-gray">
@ -48,43 +46,16 @@
</tr>
</thead>
<tbody>
<tr data-ng-show="!txs.length"><td colspan="5">Waiting for transactions...</td></tr>
<tr data-ng-show="!txs.length"><td colspan="3">Waiting for transactions...</td></tr>
<tr class="fader" data-ng-repeat='tx in txs'>
<td>
<a class="ellipsis" href="/tx/{{tx.txid}}">{{tx.txid}}</a>
</td>
<td><span class="ellipsis">{{humanSince(tx.time)}}</span></td>
<td>{{tx.valueOut}}</td>
<td><span class="ellipsis">{{tx.valueOut}}</span></td>
</tr>
</tbody>
</table>
<h3> Other Bitcoin Links </h3>
<ul>
<li>
<a href="">Most Popular Addresses</a>
<small> - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores.</small>
</li>
<li>
<a href="">Addresses</a>
<small> - Addresses which have received the most payments</small>
</li>
<li>
<a href="">Lorem ipsum dolor.</a>
<small> - Lorem ipsum dolor sit amet.</small>
</li>
<li>
<a href="">Most Popular Addresses</a>
<small> - Addresses which have received the most payments</small>
</li>
<li>
<a href="">Lorem ipsum dolor sit.</a>
<small> - Lorem ipsum dolor sit amet, consectetur adipisicing.</small>
</li>
<li>
<a href="">Addresses</a>
<small> - Addresses which have received the most payments</small>
</li>
</ul>
</div> <!-- END OF COL-3 -->
</div>
</div>

View File

@ -38,6 +38,7 @@
<tr>
<td>Skipped Blocks (previously synced)</td>
<td class="text-right">{{sync.skippedBlocks}}</td>
</tbody>
</table>
@ -80,9 +81,14 @@
<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</td>
<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 -->

View File

@ -14,7 +14,6 @@
<div class="col-md-5">
<div class="row" data-ng-show="tx.isCoinBase">
<div class="col-md-12 transaction-vin-vout" data-ng-repeat="vin in tx.vin">
<div class="text-muted pull-right btc-value"><small>{{$root.currency.getConvertion(vin.reward)}}</small></div>
<div class="ellipsis">
<span>No Inputs (Newly Generated Coins)</span>
</div>

548
test/integration/99-sync.js Normal file
View File

@ -0,0 +1,548 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var
assert = require('assert'),
async = require('async'),
HistoricSync = require('../../lib/HistoricSync').class();
var s;
var b = [
'00000000c4cbd75af741f3a2b2ff72d9ed4d83a048462c1efe331be31ccf006b', //0 B#16
'00000000fe198cce4c8abf9dca0fee1182cb130df966cc428ad2a230df8da743', //1
'000000008d55c3e978639f70af1d2bf1fe6f09cb3143e104405a599215c89a48', //2
'000000009b3bca4909f38313f2746120129cce4a699a1f552390955da470c5a9', //3
'00000000ede57f31cc598dc241d129ccb4d8168ef112afbdc870dc60a85f5dd3', //4 B#20
];
var t = [
'd08582d3711f75d085c618874fb0d049ae09d5ec95ec6f5abd289f4b54712c54', // TX from B#16
'1729001087e0cebea8d14de1653d5cf59628d9746bc1ae65f776f1cbaff7ebad', //1
'cf53d7ccd83a099acfbc319ee10c1e3b10e3d42ba675b569fdd6b69cb8d2db4e', //2
'73a4988adf462b6540cfa59097804174b298cfa439f73c1a072c2c6fbdbe57c7', //3
'd45f9da73619799e9d7bd03cc290e70875ea4cbad56b8bffa15135fbbb3df9ea', //4 Tx from B20
];
var test = function(cb) {
async.each([2,3,4], function(i,c) {
s.sync.bDb.getPrev(b[i], function(err, p) {
assert.equal(p,b[i-1]);
return c();
});
}, function() {
async.each([0,1,2,3,4], function(i,c) {
s.sync.bDb.has(b[i], function(err, p) {
assert(p);
return c();
});
}, function() {
async.each([0,1,2,3], function(i,c) {
s.sync.bDb.getNext(b[i], function(err, p) {
assert.equal(p,b[i+1]);
return c();
});
}, cb);
});
});
};
/*
* TEST CASES
*
* Blocks: 0-1-2-3-4
* case 1)
* 0-1-2-3-4
* \
* C1*
*
* case 2)
* 0-1-2---3-4
* \ \
* C1 C2*
*
* case 2b)
* 0-1-2---3-4
* \ \
* C1 C2-C2b(TX=C1.TX)*
* case 2c)
* 0-1-2---3-4
* \ \
* C1 C2-C2b(TX=C1.TX)
* \
* C2c(TX=C2.TX)*
*
*/
describe('Sync Reorgs', function(){
before(function(done) {
s = new HistoricSync();
s.init({}, function(err) {
if (err) return done(err);
s.sync.destroy(done);
});
});
it('simple RPC forward syncing', function(done) {
s.getPrevNextBlock(s.genesis,b[4], {
next: true,
}, function(err) {
if (err) return done(err);
test(done);
});
});
var case1 = {
hash: '0000000000000000000000000000000000000000000000000000000000000001',
tx: [ 'f0596531810160d090813673b4a397f4617aab44eb26c7f06c8a766eac984b91' ],
time: 1296690099,
previousblockhash: b[2],
};
it('reorg, case 1', function(done) {
async.series([
function (c) {
s.sync.txDb.isConfirmed(t[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[3], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[4], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.storeTipBlock(case1, function(err) {
assert(!err, 'shouldnt return error' + err);
return c();
});
},
function (c) {
s.sync.bDb.isMain(b[2], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.isMain(b[3], function(err,is) {
assert(!err);
assert(!is, b[3] + 'should not be on main chain');
return c();
});
},
function (c) {
s.sync.bDb.isMain(b[4], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[3], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[4], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case1.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
], done );
});
it('reorg, case 1 (repeat)', function(done) {
s.sync.storeTipBlock(case1, function(err) {
assert(!err, 'shouldnt return error' + err);
return done();
});
});
var case2 = {
hash: '0000000000000000000000000000000000000000000000000000000000000002',
tx: [ '99bb359a4b12a588fcb9e59e5e8d92d593ce7a56d2ba42085fe86d9a0b4fde15' ],
time: 1296690099,
previousblockhash: b[3],
};
it('reorg, case 2', function(done) {
async.series([
function (c) {
s.sync.txDb.isConfirmed(t[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case1.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[4], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.storeTipBlock(case2, function(err) {
assert(!err, 'shouldnt return error' + err);
return c();
});
},
function (c) {
s.sync.bDb.isMain(b[3], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.isMain(b[4], function(err,is) {
assert(!err);
assert(!is, b[3] + 'should not be on main chain');
return c();
});
},
function (c) {
s.sync.bDb.isMain(case1.hash, function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.bDb.isMain(case2.hash, function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[3], function(err,is) {
assert(!err);
assert(is, 'transaction t[3] should be valid:' + t[3]);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case1.tx[0], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case2.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[4], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.bDb.getNext(b[2], function(err, val) {
assert(!err);
assert.equal(val,b[3]);
return c();
});
},
], done );
});
var case2b = {
hash: '0000000000000000000000000000000000000000000000000000000000000003',
tx: case1.tx,
time: 1296690099,
previousblockhash: case2.hash,
};
it('reorg, case 2b', function(done) {
async.series([
function (c) {
s.sync.txDb.isConfirmed(case2b.tx[0], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.storeTipBlock(case2b, function(err) {
assert(!err, 'shouldnt return error' + err);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[3], function(err,is) {
assert(!err);
assert(is, 'transaction t[3] should be valid:' + t[3]);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case2b.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
], done );
});
var case2c = {
hash: '0000000000000000000000000000000000000000000000000000000000000004',
tx: case2.tx,
time: 1296690099,
previousblockhash: case1.hash,
};
it('reorg, case 2c', function(done) {
async.series([
function (c) {
s.sync.txDb.isConfirmed(case1.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.isMain(case1.hash, function(err,is) {
assert(!err);
assert(!is, 'case1 block shouldnt be main:' + case1.hash);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case2c.tx[0], function(err,is) {
assert(!err);
assert(is); //It was there before (from case2)
return c();
});
},
function (c) {
s.sync.storeTipBlock(case2c, function(err) {
assert(!err, 'shouldnt return error' + err);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case1.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.has(case1.hash, function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.has(case2c.hash, function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case2c.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[3], function(err,is) {
assert(!err);
assert(!is, 'TX t[3]: shouldnt be confirmed:' + t[3] +':'+ is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[4], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case2.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
], done );
});
var case3 = {
hash: '0000000000000000000000000000000000000000000000000000000000000005',
tx: case2.tx,
time: 1296690099,
previousblockhash: '666',
};
it('reorg, case 3)', function(done) {
async.series([
function (c) {
s.sync.storeTipBlock(case3, function(err) {
assert(!err, 'shouldnt return error' + err);
return c();
});
},
//shoudnt change anything
function (c) {
s.sync.txDb.isConfirmed(case1.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.has(case1.hash, function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.has(case2c.hash, function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case2c.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[3], function(err,is) {
assert(!err);
assert(!is, 'TX t[3]: shouldnt be confirmed:' + t[3] +':'+ is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(t[4], function(err,is) {
assert(!err);
assert(!is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(case2.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
], done );
});
var p2p = {
hash: '0000000000000000000000000000000000000000000000000000000000000006',
tx: ['f6c2901f39fd07f2f2e503183d76f73ecc1aee9ac9216fde58e867bc29ce674e'],
time: 1296690099,
previousblockhash: '111',
};
it('p2p, no reorg allowed', function(done) {
async.series([
function (c) {
s.sync.storeTipBlock(p2p, false, function(err) {
assert(!err, 'shouldnt return error' + err);
return c();
});
},
function (c) {
s.sync.bDb.has(p2p.hash, function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.txDb.isConfirmed(p2p.tx[0], function(err,is) {
assert(!err);
assert(is);
return c();
});
},
function (c) {
s.sync.bDb.getNext(p2p.hash, function(err,v) {
assert(!err);
assert.equal(v,p2p.nextblockhash);
return c();
});
},
function (c) {
s.sync.bDb.getNext(p2p.previousblockhash, function(err,v) {
assert(!err);
assert.equal(v,p2p.hash);
return c();
});
},
], done );
});
});

View File

@ -37,14 +37,15 @@
},
{
"addr": "mgqvRGJMwR9JU5VhJ3x9uX9MTkzTsmmDgQ",
"txApperances": 27,
"balance": 5.1
"txApperances": 28,
"balance": 0,
"totalReceived": 54.81284116
},
{
"addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29",
"txApperances": 6033,
"balance": 1049.69744101,
"totalReceived": 1049.69744101,
"txApperances": 6046,
"balance": 1149.19744101,
"totalReceived": 1149.19744101,
"totalSent": 0
},
{

View File

@ -8,7 +8,7 @@ var TESTING_BLOCK = '000000000185678d3d7ecc9962c96418174431f93fe20bf216d55652724
var
assert = require('assert'),
config = require('../../config/config'),
// config = require('../../config/config'),
BlockDb = require('../../lib/BlockDb').class();
var bDb;
@ -36,22 +36,5 @@ describe('BlockDb fromHashWithInfo', function(){
done();
});
});
it('setOrphan', function(done) {
var b16 = '00000000c4cbd75af741f3a2b2ff72d9ed4d83a048462c1efe331be31ccf006b';
var b17 = '00000000fe198cce4c8abf9dca0fee1182cb130df966cc428ad2a230df8da743';
bDb.has(b17, function(err, has) {
assert(has);
bDb.setOrphan(b17, function(err, oldPrev) {
assert.equal(oldPrev, b16);
bDb.setPrev(b17, b16, function(err, oldPrev) {
bDb.getPrev(b17, function(err, p) {
assert.equal(p, b16);
done();
});
});
});
});
});
});

View File

@ -3,11 +3,10 @@
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var TESTING_BLOCK0 = '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943';
var TESTING_BLOCK1 = '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206';
var TESTING_BLOCK0 = '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206';
var TESTING_BLOCK1 = '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943';
var START_TS = 1;
var END_TS = '1296688928~'; // 2/2/2011 23:23PM
var LIMIT = 2;
var assert = require('assert'),
BlockDb = require('../../lib/BlockDb').class();
@ -24,7 +23,7 @@ describe('BlockDb getBlocksByDate', function(){
it('Get Hash by Date', function(done) {
bDb.getBlocksByDate(START_TS, END_TS, LIMIT, function(err, list) {
bDb.getBlocksByDate(START_TS, END_TS, function(err, list) {
if (err) done(err);
assert(list, 'returns list');
assert.equal(list.length,2, 'list has 2 items');

View File

@ -1,92 +0,0 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var
assert = require('assert'),
async = require('async'),
Sync = require('../../lib/Sync').class();
var b = [
'00000000c4cbd75af741f3a2b2ff72d9ed4d83a048462c1efe331be31ccf006b', //B#16
'00000000fe198cce4c8abf9dca0fee1182cb130df966cc428ad2a230df8da743',
'000000008d55c3e978639f70af1d2bf1fe6f09cb3143e104405a599215c89a48',
'000000009b3bca4909f38313f2746120129cce4a699a1f552390955da470c5a9',
'00000000ede57f31cc598dc241d129ccb4d8168ef112afbdc870dc60a85f5dd3', //B#20
];
var fix = function(s,cb) {
async.each([1,2,3,4], function(i,c) {
s.bDb.setPrev(b[i],b[i-1], function() {
return c();
});
}, cb);
};
var test = function(s,cb) {
async.each([2,3,4], function(i,c) {
s.bDb.getPrev(b[i], function(err, p) {
assert.equal(p,0);
return c();
});
}, function() {
s.bDb.getPrev(b[1], function(err, p) {
assert.equal(p,b[0]);
return cb();
});
});
};
var testNo = function(s,cb) {
async.each([2,3,4], function(i,c) {
s.bDb.getPrev(b[i], function(err, p) {
assert.equal(p,b[i-1]);
return c();
});
}, function() {
s.bDb.getPrev(b[1], function(err, p) {
assert.equal(p,b[0]);
return cb();
});
});
};
var s;
describe('Sync checkOrphan', function(){
before(function(done) {
s = new Sync();
fix(s,done);
});
after(function(done) {
fix(s,function() {
s.close(done);
});
});
it('checkOrphan', function(done) {
this.timeout(100000);
s.bDb.has(b[0], function(err, has) {
assert(has);
s.bDb.has(b[1], function(err, has) {
assert(has);
s.checkOrphan(b[4],b[1], function() {
testNo(s,done);
});
});
});
});
});

View File

@ -11,10 +11,16 @@ var program = require('commander');
program
.version(PROGRAM_VERSION)
.option('-N --network [testnet]', 'Set bitcoin network [testnet]', 'testnet')
.option('-V --verbose', 'Verbose', 1)
.parse(process.argv);
var ps = new PeerSync();
ps.init(program);
ps.run();
ps.init(program, function(err){
if (err) {
console.log(err);
process.exit(1);
}
ps.run();
});

View File

@ -18,6 +18,7 @@ program
.option('-R --reverse', 'Sync backwards', 0)
.option('-U --uptoexisting', 'Sync only until an existing block is found', 0)
.option('-F --fromfiles', 'Sync using bitcoind .dat block files (faster)', 0)
.option('-v --verbose', 'Verbose 0/1', 0)
.parse(process.argv);
var historicSync = new HistoricSync();