2014-01-16 11:11:20 -08:00
'use strict' ;
require ( 'classtool' ) ;
function spec ( ) {
2014-01-21 08:28:01 -08:00
var util = require ( 'util' ) ;
var RpcClient = require ( 'bitcore/RpcClient' ) . class ( ) ;
var networks = require ( 'bitcore/networks' ) ;
var async = require ( 'async' ) ;
var config = require ( '../config/config' ) ;
var Block = require ( '../app/models/Block' ) ;
var Sync = require ( './Sync' ) . class ( ) ;
var sockets = require ( '../app/controllers/socket.js' ) ;
2014-01-16 11:11:20 -08:00
function HistoricSync ( opts ) {
2014-01-21 08:28:01 -08:00
this . network = config . network === 'testnet' ? networks . testnet : networks . livenet ;
2014-01-17 11:36:34 -08:00
var genesisHashReversed = new Buffer ( 32 ) ;
this . network . genesisBlock . hash . copy ( genesisHashReversed ) ;
this . genesis = genesisHashReversed . reverse ( ) . toString ( 'hex' ) ;
2014-01-21 08:28:01 -08:00
this . sync = new Sync ( opts ) ;
2014-01-19 07:33:39 -08:00
2014-01-18 13:28:24 -08:00
//available status: new / syncing / finished / aborted
2014-01-21 08:28:01 -08:00
this . status = 'new' ;
2014-01-18 13:28:24 -08:00
this . syncInfo = { } ;
2014-01-16 11:11:20 -08:00
}
function p ( ) {
2014-01-16 18:23:10 -08:00
var args = [ ] ;
2014-01-21 08:28:01 -08:00
Array . prototype . push . apply ( args , arguments ) ;
2014-01-19 07:33:39 -08:00
2014-01-16 18:23:10 -08:00
args . unshift ( '[historic_sync]' ) ;
/*jshint validthis:true */
console . log . apply ( this , args ) ;
2014-01-16 11:11:20 -08:00
}
2014-01-21 08:28:01 -08:00
HistoricSync . prototype . init = function ( opts , cb ) {
2014-01-16 11:11:20 -08:00
this . rpc = new RpcClient ( config . bitcoind ) ;
this . opts = opts ;
this . sync . init ( opts , cb ) ;
} ;
HistoricSync . prototype . close = function ( ) {
this . sync . close ( ) ;
} ;
2014-01-19 07:33:39 -08:00
HistoricSync . prototype . showProgress = function ( ) {
var self = this ;
var i = self . syncInfo ;
var per = parseInt ( 100 * i . syncedBlocks / i . blocksToSync ) ;
p ( util . format ( 'status: %d/%d [%d%%]' , i . syncedBlocks , i . blocksToSync , per ) ) ;
2014-01-21 08:28:01 -08:00
if ( self . opts . shouldBroadcast ) {
2014-01-19 07:33:39 -08:00
sockets . broadcastSyncInfo ( self . syncInfo ) ;
}
} ;
2014-01-16 11:11:20 -08:00
HistoricSync . prototype . getPrevNextBlock = function ( blockHash , blockEnd , opts , cb ) {
2014-01-17 11:36:34 -08:00
var self = this ;
2014-01-16 11:11:20 -08:00
// recursion end.
2014-01-21 08:28:01 -08:00
if ( ! blockHash ) return cb ( ) ;
2014-01-16 11:11:20 -08:00
2014-01-21 08:28:01 -08:00
var existed = false ;
2014-01-16 11:11:20 -08:00
var blockInfo ;
var blockObj ;
2014-01-16 18:23:10 -08:00
async . series ( [
2014-01-21 08:28:01 -08:00
// Already got it?
function ( c ) {
Block . findOne ( {
hash : blockHash
2014-01-16 11:11:20 -08:00
} ,
2014-01-21 08:28:01 -08:00
function ( err , block ) {
if ( err ) {
p ( err ) ;
return c ( err ) ;
}
if ( block ) {
existed = true ;
blockObj = block ;
2014-01-16 11:11:20 -08:00
}
return c ( ) ;
2014-01-21 08:28:01 -08:00
} ) ;
} ,
//show some (inacurate) status
function ( c ) {
if ( ! self . step ) {
var step = parseInt ( self . syncInfo . blocksToSync / 100 ) ;
if ( self . opts . progressStep ) {
step = self . opts . progressStep ;
}
if ( step < 2 ) step = 2 ;
self . step = step ;
}
if ( self . syncInfo . syncedBlocks % self . step === 1 ) {
self . showProgress ( ) ;
}
return c ( ) ;
} ,
//get Info from RPC
function ( c ) {
2014-01-16 11:11:20 -08:00
2014-01-21 08:28:01 -08:00
// TODO: if we store prev/next, no need to go to RPC
// if (blockObj && blockObj.nextBlockHash) return c();
self . rpc . getBlock ( blockHash , function ( err , ret ) {
if ( err ) return c ( err ) ;
2014-01-16 11:11:20 -08:00
2014-01-21 08:28:01 -08:00
blockInfo = ret ;
return c ( ) ;
} ) ;
} ,
//store it
function ( c ) {
if ( existed ) return c ( ) ;
self . sync . storeBlock ( blockInfo . result , function ( err ) {
2014-01-17 11:36:34 -08:00
2014-01-21 08:28:01 -08:00
existed = err && err . toString ( ) . match ( /E11000/ ) ;
2014-01-17 11:36:34 -08:00
2014-01-21 08:28:01 -08:00
if ( err && ! existed ) return c ( err ) ;
return c ( ) ;
} ) ;
} ,
/ * T O D O : S h o u l d S t a r t t o s y n c b a c k w a r d s ? ( t h i s i s f o r p a r t i a l s y n c s )
2014-01-16 11:11:20 -08:00
function ( c ) {
if ( blockInfo . result . prevblockhash != current . blockHash ) {
p ( "reorg?" ) ;
opts . prev = 1 ;
}
return c ( ) ;
}
* /
2014-01-21 08:28:01 -08:00
] , function ( err ) {
2014-01-17 11:36:34 -08:00
2014-01-21 08:28:01 -08:00
if ( err ) {
self . err = util . format ( 'ERROR: @%s: %s [count: syncedBlocks: %d]' , blockHash , err , self . syncInfo . syncedBlocks ) ;
self . status = 'aborted' ;
p ( self . err ) ;
}
2014-01-18 13:28:24 -08:00
2014-01-21 08:28:01 -08:00
else {
self . err = null ;
self . status = 'syncing' ;
}
2014-01-17 05:22:00 -08:00
2014-01-21 08:28:01 -08:00
if ( opts . upToExisting && existed ) {
var diff = self . syncInfo . blocksToSync - self . syncInfo . syncedBlocks ;
if ( diff <= 0 ) {
2014-01-18 13:28:24 -08:00
self . status = 'finished' ;
2014-01-21 08:28:01 -08:00
p ( 'DONE. Found existing block: ' , blockHash ) ;
2014-01-17 11:36:34 -08:00
return cb ( err ) ;
}
2014-01-21 08:28:01 -08:00
else {
self . syncInfo . skipped _blocks = self . syncInfo . skipped _blocks || 1 ;
if ( ( self . syncInfo . skipped _blocks ++ % 1000 ) === 1 ) {
p ( 'WARN found target block\n\tbut blockChain Height is still higher that ours. Previous light sync must be interrupted.\n\tWill keep syncing.' , self . syncInfo . syncedBlocks , self . syncInfo . blocksToSync , self . syncInfo . skipped _blocks ) ;
2014-01-16 11:11:20 -08:00
}
}
2014-01-21 08:28:01 -08:00
}
if ( blockEnd && blockEnd === blockHash ) {
self . status = 'finished' ;
p ( 'DONE. Found END block: ' , blockHash ) ;
2014-01-16 11:11:20 -08:00
return cb ( err ) ;
2014-01-21 08:28:01 -08:00
}
// Continue
if ( blockInfo && blockInfo . result ) {
if ( ! existed ) self . syncInfo . syncedBlocks ++ ;
if ( opts . prev && blockInfo . result . previousblockhash ) {
return self . getPrevNextBlock ( blockInfo . result . previousblockhash , blockEnd , opts , cb ) ;
}
if ( opts . next && blockInfo . result . nextblockhash ) return self . getPrevNextBlock ( blockInfo . result . nextblockhash , blockEnd , opts , cb ) ;
}
return cb ( err ) ;
2014-01-16 11:11:20 -08:00
} ) ;
} ;
2014-01-17 05:22:00 -08:00
HistoricSync . prototype . import _history = function ( opts , next ) {
2014-01-17 11:36:34 -08:00
var self = this ;
2014-01-16 11:11:20 -08:00
2014-01-21 08:28:01 -08:00
var retry _secs = 2 ;
2014-01-16 11:11:20 -08:00
2014-01-18 13:28:24 -08:00
var bestBlock ;
var blockChainHeight ;
2014-01-16 11:11:20 -08:00
async . series ( [
2014-01-21 08:28:01 -08:00
function ( cb ) {
if ( opts . destroy ) {
p ( 'Deleting DB...' ) ;
return self . sync . destroy ( cb ) ;
}
return cb ( ) ;
} ,
// We are not using getBestBlockHash, because is not available in all clients
function ( cb ) {
if ( ! opts . reverse ) return cb ( ) ;
self . rpc . getBlockCount ( function ( err , res ) {
if ( err ) return cb ( err ) ;
blockChainHeight = res . result ;
2014-01-19 07:33:39 -08:00
return cb ( ) ;
2014-01-21 08:28:01 -08:00
} ) ;
} ,
function ( cb ) {
if ( ! opts . reverse ) return cb ( ) ;
self . rpc . getBlockHash ( blockChainHeight , function ( err , res ) {
if ( err ) return cb ( err ) ;
2014-01-16 11:11:20 -08:00
2014-01-21 08:28:01 -08:00
bestBlock = res . result ;
return cb ( ) ;
} ) ;
} ,
function ( cb ) {
// This is only to inform progress.
if ( ! opts . upToExisting ) {
self . rpc . getInfo ( function ( err , res ) {
2014-01-17 05:44:14 -08:00
if ( err ) return cb ( err ) ;
2014-01-21 08:28:01 -08:00
self . syncInfo . blocksToSync = res . result . blocks ;
2014-01-16 11:11:20 -08:00
return cb ( ) ;
} ) ;
2014-01-21 08:28:01 -08:00
}
else {
// should be isOrphan = true or null to be more accurate.
Block . count ( {
isOrphan : null
} ,
function ( err , count ) {
2014-01-17 05:44:14 -08:00
if ( err ) return cb ( err ) ;
2014-01-16 11:11:20 -08:00
2014-01-21 08:28:01 -08:00
self . syncInfo . blocksToSync = blockChainHeight - count ;
if ( self . syncInfo . blocksToSync < 1 ) self . syncInfo . blocksToSync = 1 ;
2014-01-16 11:11:20 -08:00
return cb ( ) ;
} ) ;
2014-01-21 08:28:01 -08:00
}
} ,
] , function ( err ) {
var start , end ;
function sync ( ) {
if ( opts . reverse ) {
start = bestBlock ;
end = self . genesis ;
opts . prev = true ;
2014-01-18 13:28:24 -08:00
}
else {
2014-01-21 08:28:01 -08:00
start = self . genesis ;
end = null ;
opts . next = true ;
}
2014-01-16 11:11:20 -08:00
2014-01-21 08:28:01 -08:00
self . syncInfo = util . _extend ( self . syncInfo , {
start : start ,
isStartGenesis : start === self . genesis ,
end : end ,
isEndGenesis : end === self . genesis ,
scanningForward : opts . next ,
scanningBackward : opts . prev ,
upToExisting : opts . upToExisting ,
syncedBlocks : 0 ,
} ) ;
2014-01-18 13:28:24 -08:00
2014-01-21 08:28:01 -08:00
p ( 'Starting from: ' , start ) ;
p ( ' to : ' , end ) ;
p ( ' opts: ' , JSON . stringify ( opts ) ) ;
self . getPrevNextBlock ( start , end , opts , function ( err ) {
if ( err && err . message . match ( /ECONNREFUSED/ ) ) {
setTimeout ( function ( ) {
p ( 'Retrying in %d secs' , retry _secs ) ;
sync ( ) ;
} ,
retry _secs * 1000 ) ;
2014-01-16 11:11:20 -08:00
}
2014-01-21 08:28:01 -08:00
else return next ( err ) ;
} ) ;
}
2014-01-18 13:28:24 -08:00
2014-01-21 08:28:01 -08:00
if ( err ) {
self . syncInfo = util . _extend ( self . syncInfo , {
error : err . message
} ) ;
return next ( err , 0 ) ;
}
else {
sync ( ) ;
}
2014-01-16 11:11:20 -08:00
} ) ;
2014-01-16 18:23:10 -08:00
} ;
2014-01-16 11:11:20 -08:00
2014-01-17 11:36:34 -08:00
// upto if we have genesis block?
2014-01-17 05:22:00 -08:00
HistoricSync . prototype . smart _import = function ( next ) {
2014-01-17 11:36:34 -08:00
var self = this ;
2014-01-21 08:28:01 -08:00
Block . findOne ( {
hash : self . genesis
} ,
function ( err , b ) {
2014-01-18 13:28:24 -08:00
2014-01-17 11:36:34 -08:00
if ( err ) return next ( err ) ;
if ( ! b ) {
p ( 'Could not find Genesis block. Running FULL SYNC' ) ;
}
else {
2014-01-18 13:28:24 -08:00
p ( 'Genesis block found. Syncing upto known blocks.' ) ;
2014-01-17 11:36:34 -08:00
}
var opts = {
2014-01-21 08:28:01 -08:00
reverse : true ,
2014-01-19 07:33:39 -08:00
upToExisting : b ? true : false ,
2014-01-17 11:36:34 -08:00
} ;
return self . import _history ( opts , next ) ;
} ) ;
2014-01-16 11:11:20 -08:00
} ;
return HistoricSync ;
}
module . defineClass ( spec ) ;