Skip to content

Commit

Permalink
server: Handle duplicate txid by excluding inputs referring them
Browse files Browse the repository at this point in the history
When syncing the ledger, the inputs that are spending outputs from
duplicated txids are excluded.
  • Loading branch information
AlexITC committed Apr 26, 2019
1 parent 6665611 commit 364fe6a
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 ()

Expand Down Expand Up @@ -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 ()
Expand Down Expand Up @@ -160,18 +160,30 @@ 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 ()

result.toFuture
}
}

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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
Expand Down

0 comments on commit 364fe6a

Please sign in to comment.