Skip to content

Commit

Permalink
server: Be resilient while loading a transaction
Browse files Browse the repository at this point in the history
For loading a transaction, we need to make several calls to
the RPC API for retrieving the inputs, when the server gets
overloaded by this, we try to get the inputs sequentially.
  • Loading branch information
AlexITC committed Dec 17, 2018
1 parent f348eaf commit b2aaae4
Showing 1 changed file with 40 additions and 11 deletions.
51 changes: 40 additions & 11 deletions server/app/com/xsn/explorer/services/TransactionService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory
import play.api.libs.json.{JsObject, JsString, JsValue}

import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal

class TransactionService @Inject() (
paginatedQueryValidator: PaginatedQueryValidator,
Expand Down Expand Up @@ -63,23 +64,46 @@ class TransactionService @Inject() (
def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction] = {
val result = for {
tx <- xsnService.getTransaction(txid).toFutureOr
transactionVIN <- tx.vin.map { vin =>
getTransactionValue(vin)
.map {
case Good(transactionValue) =>
val newVIN = vin.copy(address = Some(transactionValue.address), value = Some(transactionValue.value))
Good(newVIN)

case Bad(_) => Good(vin)
}
}.toFutureOr

transactionVIN <- getTransactionVIN(tx.vin).toFutureOr
rpcTransaction = tx.copy(vin = transactionVIN)
} yield Transaction.fromRPC(rpcTransaction)

result.toFuture
}

private def getTransactionVIN(list: List[TransactionVIN]): FutureApplicationResult[List[TransactionVIN]] = {
def getVIN(vin: TransactionVIN) = {
getTransactionValue(vin)
.map {
case Good(transactionValue) =>
val newVIN = vin.copy(address = Some(transactionValue.address), value = Some(transactionValue.value))
Good(newVIN)

case Bad(_) => Good(vin)
}
}

def loadVINSequentially(pending: List[TransactionVIN]): FutureOr[List[TransactionVIN]] = pending match {
case x :: xs =>
for {
tx <- getVIN(x).toFutureOr
next <- loadVINSequentially(xs)
} yield tx :: next

case _ => Future.successful(Good(List.empty)).toFutureOr
}

list
.map(getVIN)
.toFutureOr
.toFuture
.recoverWith {
case NonFatal(ex) =>
logger.warn(s"Failed to load VIN, trying sequentially, error = ${ex.getMessage}")
loadVINSequentially(list).toFuture
}
}

def getTransactions(ids: List[TransactionId]): FutureApplicationResult[List[Transaction]] = {
def loadTransactionsSlowly(pending: List[TransactionId]): FutureOr[List[Transaction]] = pending match {
case x :: xs =>
Expand All @@ -99,6 +123,11 @@ class TransactionService @Inject() (
loadTransactionsSlowly(ids)
}
.toFuture
.recoverWith {
case NonFatal(ex) =>
logger.warn(s"Unable to load transactions due to server error, loading them sequentially, error = ${ex.getMessage}")
loadTransactionsSlowly(ids).toFuture
}
}

def getTransactions(
Expand Down

0 comments on commit b2aaae4

Please sign in to comment.