Browse Source

server: Handle duplicate txid by excluding inputs referring them

When syncing the ledger, the inputs that are spending outputs from
duplicated txids are excluded.
bitcoin
Alexis Hernandez 6 years ago
parent
commit
364fe6a997
  1. 20
      server/app/com/xsn/explorer/services/LedgerSynchronizerService.scala
  2. 38
      server/app/com/xsn/explorer/services/TransactionCollectorService.scala

20
server/app/com/xsn/explorer/services/LedgerSynchronizerService.scala

@ -36,7 +36,7 @@ class LedgerSynchronizerService @Inject() (
*/ */
def synchronize(blockhash: Blockhash): FutureApplicationResult[Unit] = { def synchronize(blockhash: Blockhash): FutureApplicationResult[Unit] = {
val result = for { val result = for {
data <- xsnService.getBlock(blockhash).toFutureOr data <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(data).toFutureOr _ <- synchronize(data).toFutureOr
} yield () } yield ()
@ -100,7 +100,7 @@ class LedgerSynchronizerService @Inject() (
logger.info(s"Reorganization to push block ${newBlock.height}, hash = ${newBlock.hash}") logger.info(s"Reorganization to push block ${newBlock.height}, hash = ${newBlock.hash}")
val result = for { val result = for {
blockhash <- newBlock.previousBlockhash.toFutureOr(BlockNotFoundError) blockhash <- newBlock.previousBlockhash.toFutureOr(BlockNotFoundError)
previousBlock <- xsnService.getBlock(blockhash).toFutureOr previousBlock <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(previousBlock).toFutureOr _ <- synchronize(previousBlock).toFutureOr
_ <- synchronize(newBlock).toFutureOr _ <- synchronize(newBlock).toFutureOr
} yield () } yield ()
@ -160,7 +160,7 @@ class LedgerSynchronizerService @Inject() (
val result = for { val result = for {
_ <- previous.toFutureOr _ <- previous.toFutureOr
blockhash <- xsnService.getBlockhash(Height(height)).toFutureOr blockhash <- xsnService.getBlockhash(Height(height)).toFutureOr
block <- xsnService.getBlock(blockhash).toFutureOr block <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(block).toFutureOr _ <- synchronize(block).toFutureOr
} yield () } yield ()
@ -168,10 +168,22 @@ class LedgerSynchronizerService @Inject() (
} }
} }
private def getRPCBlock(blockhash: Blockhash): FutureApplicationResult[rpc.Block] = {
val result = for {
rpcBlock <- xsnService.getBlock(blockhash).toFutureOr
} yield {
rpcBlock
}
result.toFuture
}
private def ExcludedTransactions = List("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468").flatMap(TransactionId.from)
private def getBlockData(rpcBlock: rpc.Block): FutureApplicationResult[BlockData] = { private def getBlockData(rpcBlock: rpc.Block): FutureApplicationResult[BlockData] = {
val result = for { val result = for {
extractionMethod <- blockService.extractionMethod(rpcBlock).toFutureOr extractionMethod <- blockService.extractionMethod(rpcBlock).toFutureOr
data <- transactionCollectorService.collect(rpcBlock.transactions).toFutureOr data <- transactionCollectorService.collect(rpcBlock.transactions, ExcludedTransactions).toFutureOr
(transactions, contracts) = data (transactions, contracts) = data
validContracts <- getValidContracts(contracts).toFutureOr validContracts <- getValidContracts(contracts).toFutureOr
} yield { } yield {

38
server/app/com/xsn/explorer/services/TransactionCollectorService.scala

@ -36,10 +36,10 @@ class TransactionCollectorService @Inject() (
* When dealing with the RPC API, we try to get the details in parallel, if the server gets overloaded, * When dealing with the RPC API, we try to get the details in parallel, if the server gets overloaded,
* we'll try to load the data sequentially. * we'll try to load the data sequentially.
*/ */
def collect(txidList: List[TransactionId]): FutureApplicationResult[Result] = { def collect(txidList: List[TransactionId], excludedTransactions: List[TransactionId]): FutureApplicationResult[Result] = {
val futureOr = for { val futureOr = for {
rpcTransactions <- getRPCTransactions(txidList).toFutureOr rpcTransactions <- getRPCTransactions(txidList).toFutureOr
completeTransactions <- getRPCTransactionsWithValues(rpcTransactions).toFutureOr completeTransactions <- getRPCTransactionsWithValues(rpcTransactions, excludedTransactions).toFutureOr
} yield { } yield {
val result = completeTransactions.map(persisted.Transaction.fromRPC) val result = completeTransactions.map(persisted.Transaction.fromRPC)
val contracts = result.flatMap(_._2) val contracts = result.flatMap(_._2)
@ -50,12 +50,12 @@ class TransactionCollectorService @Inject() (
futureOr.toFuture futureOr.toFuture
} }
private[services] def getRPCTransactionsWithValues(rpcTransactions: RPCTransactionList): FutureApplicationResult[RPCTransactionListWithValues] = { private[services] def getRPCTransactionsWithValues(rpcTransactions: RPCTransactionList, excludedTransactions: List[TransactionId]): FutureApplicationResult[RPCTransactionListWithValues] = {
val neutral: FutureApplicationResult[List[rpc.Transaction[rpc.TransactionVIN.HasValues]]] = Future.successful(Good(List.empty)) val neutral: FutureApplicationResult[List[rpc.Transaction[rpc.TransactionVIN.HasValues]]] = Future.successful(Good(List.empty))
val future = rpcTransactions.foldLeft(neutral) { case (acc, tx) => val future = rpcTransactions.foldLeft(neutral) { case (acc, tx) =>
val result = for { val result = for {
previous <- acc.toFutureOr previous <- acc.toFutureOr
completeVIN <- getRPCTransactionVIN(tx.vin).toFutureOr completeVIN <- getRPCTransactionVIN(tx.vin, excludedTransactions).toFutureOr
completeTX = tx.copy(vin = completeVIN) completeTX = tx.copy(vin = completeVIN)
} yield completeTX :: previous } yield completeTX :: previous
@ -75,21 +75,23 @@ class TransactionCollectorService @Inject() (
* - The inputs that aren't present in the database are retrieved from the RPC API. * - The inputs that aren't present in the database are retrieved from the RPC API.
* - The ones that weren't retrieved are retried sequentially using the RPC API. * - The ones that weren't retrieved are retried sequentially using the RPC API.
*/ */
private[services] def getRPCTransactionVIN(vinList: List[rpc.TransactionVIN]): FutureApplicationResult[List[rpc.TransactionVIN.HasValues]] = { private[services] def getRPCTransactionVIN(vinList: List[rpc.TransactionVIN], excludedTransactions: List[TransactionId]): FutureApplicationResult[List[rpc.TransactionVIN.HasValues]] = {
val futureList = Future.sequence { val futureList = Future.sequence {
vinList.map { vin => vinList
transactionDataHandler .filterNot(excludedTransactions contains _.txid)
.getOutput(vin.txid, vin.voutIndex) .map { vin =>
.toFutureOr transactionDataHandler
.map { output => .getOutput(vin.txid, vin.voutIndex)
vin.withValues(value = output.value, address = output.address) .toFutureOr
} .map { output =>
.recoverWith(TransactionNotFoundError) { vin.withValues(value = output.value, address = output.address)
getRPCTransactionVINWithValues(vin).toFutureOr }
} .recoverWith(TransactionNotFoundError) {
.toFuture getRPCTransactionVINWithValues(vin).toFutureOr
.map(vin -> _) }
} .toFuture
.map(vin -> _)
}
} }
val future = futureList val future = futureList

Loading…
Cancel
Save