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] = {
val result = for {
data <- xsnService.getBlock(blockhash).toFutureOr
data <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(data).toFutureOr
} yield ()
@ -100,7 +100,7 @@ class LedgerSynchronizerService @Inject() (
logger.info(s"Reorganization to push block ${newBlock.height}, hash = ${newBlock.hash}")
val result = for {
blockhash <- newBlock.previousBlockhash.toFutureOr(BlockNotFoundError)
previousBlock <- xsnService.getBlock(blockhash).toFutureOr
previousBlock <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(previousBlock).toFutureOr
_ <- synchronize(newBlock).toFutureOr
} yield ()
@ -160,7 +160,7 @@ class LedgerSynchronizerService @Inject() (
val result = for {
_ <- previous.toFutureOr
blockhash <- xsnService.getBlockhash(Height(height)).toFutureOr
block <- xsnService.getBlock(blockhash).toFutureOr
block <- getRPCBlock(blockhash).toFutureOr
_ <- synchronize(block).toFutureOr
} 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] = {
val result = for {
extractionMethod <- blockService.extractionMethod(rpcBlock).toFutureOr
data <- transactionCollectorService.collect(rpcBlock.transactions).toFutureOr
data <- transactionCollectorService.collect(rpcBlock.transactions, ExcludedTransactions).toFutureOr
(transactions, contracts) = data
validContracts <- getValidContracts(contracts).toFutureOr
} 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,
* 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 {
rpcTransactions <- getRPCTransactions(txidList).toFutureOr
completeTransactions <- getRPCTransactionsWithValues(rpcTransactions).toFutureOr
completeTransactions <- getRPCTransactionsWithValues(rpcTransactions, excludedTransactions).toFutureOr
} yield {
val result = completeTransactions.map(persisted.Transaction.fromRPC)
val contracts = result.flatMap(_._2)
@ -50,12 +50,12 @@ class TransactionCollectorService @Inject() (
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 future = rpcTransactions.foldLeft(neutral) { case (acc, tx) =>
val result = for {
previous <- acc.toFutureOr
completeVIN <- getRPCTransactionVIN(tx.vin).toFutureOr
completeVIN <- getRPCTransactionVIN(tx.vin, excludedTransactions).toFutureOr
completeTX = tx.copy(vin = completeVIN)
} 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 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 {
vinList.map { vin =>
transactionDataHandler
.getOutput(vin.txid, vin.voutIndex)
.toFutureOr
.map { output =>
vin.withValues(value = output.value, address = output.address)
}
.recoverWith(TransactionNotFoundError) {
getRPCTransactionVINWithValues(vin).toFutureOr
}
.toFuture
.map(vin -> _)
}
vinList
.filterNot(excludedTransactions contains _.txid)
.map { vin =>
transactionDataHandler
.getOutput(vin.txid, vin.voutIndex)
.toFutureOr
.map { output =>
vin.withValues(value = output.value, address = output.address)
}
.recoverWith(TransactionNotFoundError) {
getRPCTransactionVINWithValues(vin).toFutureOr
}
.toFuture
.map(vin -> _)
}
}
val future = futureList

Loading…
Cancel
Save