@ -1,15 +1,16 @@
package com.xsn.explorer.services
import com.alexitc.playsonify.core.FutureApplicationResult
import com.alexitc.playsonify.core.FutureOr.Implicits. { FutureOps , OptionOps }
import com.alexitc.playsonify.core. { ApplicationResult , FutureApplicationResult }
import com.xsn.explorer.data.async. { BlockFutureDataHandler , LedgerFutureDataHandler }
import com.xsn.explorer.errors.BlockNotFoundError
import com.xsn.explorer.models.TPoSContract
import com.xsn.explorer.models.persisted.Block
import com.xsn.explorer.models.transformers._
import com.xsn.explorer.models.values. { Blockhash , Height }
import com.xsn.explorer.util.Extensions.FutureOrExt
import javax.inject.Inject
import org.scalactic.Good
import org.scalactic. { Bad , Good }
import org.slf4j.LoggerFactory
import scala.concurrent. { ExecutionContext , Future }
@ -23,6 +24,8 @@ class LedgerSynchronizerService @Inject() (
blockDataHandler : BlockFutureDataHandler ) (
implicit ec : ExecutionContext ) {
import LedgerSynchronizerService._
private val logger = LoggerFactory . getLogger ( this . getClass )
/* *
@ -34,14 +37,15 @@ class LedgerSynchronizerService @Inject() (
*/
def synchronize ( blockhash : Blockhash ) : FutureApplicationResult [ Unit ] = {
val result = for {
block <- getRPCBlock ( blockhash ) . toFutureOr
_ <- synchronize ( block ) . toFutureOr
data <- getRPCBlock ( blockhash ) . toFutureOr
_ <- synchronize ( data ) . toFutureOr
} yield ( )
result . toFuture
}
private def synchronize ( block : Block . HasTransactions ) : FutureApplicationResult [ Unit ] = {
private def synchronize ( data : BlockData ) : FutureApplicationResult [ Unit ] = {
val block = data . _1
logger . info ( s" Synchronize block ${ block . height } , hash = ${ block . hash } " )
val result = for {
@ -52,8 +56,8 @@ class LedgerSynchronizerService @Inject() (
. recoverFrom ( BlockNotFoundError ) ( None )
_ <- latestBlockMaybe
. map { latestBlock => onLatestBlock ( latestBlock , block ) }
. getOrElse { onEmptyLedger ( block ) }
. map { latestBlock => onLatestBlock ( latestBlock , data ) }
. getOrElse { onEmptyLedger ( data ) }
. toFutureOr
} yield ( )
@ -65,15 +69,16 @@ class LedgerSynchronizerService @Inject() (
* 1.1 . the given block is the genensis block , it is added .
* 1.2 . the given block is not the genesis block , sync everything until the given block .
*/
private def onEmptyLedger ( block : Block . HasTransactions ) : FutureApplicationResult [ Unit ] = {
private def onEmptyLedger ( data : BlockData ) : FutureApplicationResult [ Unit ] = {
val block = data . _1
if ( block . height . int == 0 ) {
logger . info ( s" Synchronize genesis block on empty ledger, hash = ${ block . hash } " )
ledgerDataHandler . push ( block )
ledgerDataHandler . push ( block , data . _2 )
} else {
logger . info ( s" Synchronize block ${ block . height } on empty ledger, hash = ${ block . hash } " )
val result = for {
_ <- sync ( 0 until block . height . int ) . toFutureOr
_ <- synchronize ( block ) . toFutureOr
_ <- synchronize ( data ) . toFutureOr
} yield ( )
result . toFuture
@ -88,19 +93,20 @@ class LedgerSynchronizerService @Inject() (
* 2.4 . if H <= N , if the hash already exists , it is ignored .
* 2.5 . if H <= N , if the hash doesn 't exists , remove blocks from N to H ( included ) , then , add the new H .
*/
private def onLatestBlock ( ledgerBlock : Block , newBlock : Block . HasTransactions ) : FutureApplicationResult [ Unit ] = {
private def onLatestBlock ( ledgerBlock : Block , newData : BlockData ) : FutureApplicationResult [ Unit ] = {
val newBlock = newData . _1
if ( ledgerBlock . height . int + 1 == newBlock . height . int &&
newBlock . previousBlockhash . contains ( ledgerBlock . hash ) ) {
logger . info ( s" Appending block ${ newBlock . height } , hash = ${ newBlock . hash } " )
ledgerDataHandler . push ( newBlock )
ledgerDataHandler . push ( newBlock , newData . _2 )
} else if ( ledgerBlock . height . int + 1 == newBlock . height . int ) {
logger . info ( s" Reorganization to push block ${ newBlock . height } , hash = ${ newBlock . hash } " )
val result = for {
blockhash <- newBlock . previousBlockhash . toFutureOr ( BlockNotFoundError )
previousBlock <- getRPCBlock ( blockhash ) . toFutureOr
_ <- synchronize ( previousBlock ) . toFutureOr
_ <- synchronize ( newBlock ) . toFutureOr
_ <- synchronize ( newData ) . toFutureOr
} yield ( )
result . toFuture
@ -108,7 +114,7 @@ class LedgerSynchronizerService @Inject() (
logger . info ( s" Filling holes to push block ${ newBlock . height } , hash = ${ newBlock . hash } " )
val result = for {
_ <- sync ( ledgerBlock . height . int + 1 until newBlock . height . int ) . toFutureOr
_ <- synchronize ( newBlock ) . toFutureOr
_ <- synchronize ( newData ) . toFutureOr
} yield ( )
result . toFuture
@ -126,7 +132,7 @@ class LedgerSynchronizerService @Inject() (
. getOrElse {
val x = for {
_ <- trimTo ( newBlock . height ) . toFutureOr
_ <- synchronize ( newBlock ) . toFutureOr
_ <- synchronize ( newData ) . toFutureOr
} yield ( )
x . toFuture
}
@ -148,24 +154,59 @@ class LedgerSynchronizerService @Inject() (
val result = for {
_ <- previous . toFutureOr
blockhash <- xsnService . getBlockhash ( Height ( height ) ) . toFutureOr
block <- getRPCBlock ( blockhash ) . toFutureOr
_ <- synchronize ( block ) . toFutureOr
data <- getRPCBlock ( blockhash ) . toFutureOr
_ <- synchronize ( data ) . toFutureOr
} yield ( )
result . toFuture
}
}
private def getRPCBlock ( blockhash : Blockhash ) : FutureApplicationResult [ Block . HasTransactions ] = {
private def getRPCBlock ( blockhash : Blockhash ) : FutureApplicationResult [ BlockData ] = {
val result = for {
rpcBlock <- xsnService . getBlock ( blockhash ) . toFutureOr
extractionMethod <- blockService . extractionMethod ( rpcBlock ) . toFutureOr
transactions <- transactionRPCService . getTransactions ( rpcBlock . transactions ) . toFutureOr
} yield toPersistedBlock ( rpcBlock , extractionMethod ) . withTransactions ( transactions )
data <- transactionRPCService . getTransactions ( rpcBlock . transactions ) . toFutureOr
( transactions , contracts ) = data
validContracts <- getValidContracts ( contracts ) . toFutureOr
} yield {
val block = toPersistedBlock ( rpcBlock , extractionMethod ) . withTransactions ( transactions )
( block , validContracts )
}
result . toFuture
}
private def getValidContracts ( contracts : List [ TPoSContract ] ) : FutureApplicationResult [ List [ TPoSContract ] ] = {
val listF = contracts
. map { contract =>
xsnService
. isTPoSContract ( contract . txid )
. toFutureOr
. map { valid =>
if ( valid ) Some ( contract )
else None
}
. toFuture
}
val futureList = Future . sequence ( listF )
futureList . map { list =>
val x = list . flatMap {
case Good ( a ) => a . map ( Good ( _ ) )
case Bad ( e ) => Some ( Bad ( e ) )
}
val initial : ApplicationResult [ List [ TPoSContract ] ] = Good ( List . empty )
x . foldLeft ( initial ) { case ( acc , cur ) =>
cur match {
case Good ( contract ) => acc . map ( contract : : _ )
case Bad ( e ) => acc . badMap ( prev => prev ++ e )
}
}
}
}
/* *
* Trim the ledger until the given block height , if the height is 4 ,
* the last stored block will be 3.
@ -188,3 +229,8 @@ class LedgerSynchronizerService @Inject() (
result . toFuture
}
}
object LedgerSynchronizerService {
type BlockData = ( Block . HasTransactions , List [ TPoSContract ] )
}