From 50759b2fc494b7e59e4dbeb3b5445c8606aaa29a Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 18 Sep 2019 19:43:20 -0600 Subject: [PATCH] server: Add GET /transactions/:txid/utxos/:index endpoint --- .../services/TransactionRPCService.scala | 20 ++++++++++- .../xsn/explorer/services/XSNService.scala | 34 +++++++++++++++++++ .../controllers/TransactionsController.scala | 4 +++ server/conf/routes | 1 + .../explorer/helpers/DummyXSNService.scala | 2 ++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/server/app/com/xsn/explorer/services/TransactionRPCService.scala b/server/app/com/xsn/explorer/services/TransactionRPCService.scala index c46ba663..70445c8f 100644 --- a/server/app/com/xsn/explorer/services/TransactionRPCService.scala +++ b/server/app/com/xsn/explorer/services/TransactionRPCService.scala @@ -7,10 +7,11 @@ import com.xsn.explorer.models.TransactionDetails import com.xsn.explorer.models.rpc.Block import com.xsn.explorer.models.values._ import com.xsn.explorer.services.validators.TransactionIdValidator +import com.xsn.explorer.util.Extensions.BigDecimalExt import javax.inject.Inject import org.scalactic.{One, Or} import org.slf4j.LoggerFactory -import play.api.libs.json.{JsString, JsValue, Json} +import play.api.libs.json._ import scala.concurrent.ExecutionContext @@ -73,4 +74,21 @@ class TransactionRPCService @Inject()( result.toFuture } + + def getTransactionUtxoByIndex( + txidString: String, + index: Int, + includeMempool: Boolean + ): FutureApplicationResult[JsValue] = { + val result = for { + txid <- transactionIdValidator.validate(txidString).toFutureOr + jsvalue <- xsnService.getTxOut(txid, index, includeMempool).toFutureOr + value <- Or.from((jsvalue \ "value").asOpt[BigDecimal], One(TransactionError.NotFound(txid))).toFutureOr + jsonScriptHex <- Or + .from((jsvalue \ "scriptPubKey" \ "hex").asOpt[String], One(TransactionError.NotFound(txid))) + .toFutureOr + } yield Json.obj("value" -> value.toSatoshis.toString, "script" -> jsonScriptHex) + + result.toFuture + } } diff --git a/server/app/com/xsn/explorer/services/XSNService.scala b/server/app/com/xsn/explorer/services/XSNService.scala index 56943ac5..93487af7 100644 --- a/server/app/com/xsn/explorer/services/XSNService.scala +++ b/server/app/com/xsn/explorer/services/XSNService.scala @@ -55,6 +55,8 @@ trait XSNService { def estimateSmartFee(confirmationsTarget: Int): FutureApplicationResult[JsValue] + def getTxOut(txid: TransactionId, index: Int, includeMempool: Boolean): FutureApplicationResult[JsValue] + def cleanGenesisBlock(block: rpc.Block.Canonical): rpc.Block.Canonical = { Option(block) .filter(_.hash == genesisBlockhash) @@ -637,6 +639,38 @@ class XSNServiceRPCImpl @Inject()( result } + override def getTxOut(txid: TransactionId, index: Int, includeMempool: Boolean): FutureApplicationResult[JsValue] = { + val body = s""" + |{ + | "jsonrpc": "1.0", + | "method": "gettxout", + | "params": ["${txid.string}", $index, $includeMempool] + |} + |""".stripMargin + + val result = retrying { + server + .post(body) + .map { response => + val maybe = getResult[JsValue](response) + maybe.getOrElse { + logger.debug( + s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}" + ) + + Bad(XSNUnexpectedResponseError).accumulating + } + } + } + + result.foreach { + case Bad(errors) => logger.warn(s"Failed to get TxOut, errors = $errors") + case _ => () + } + + result + } + private def mapError(json: JsValue, errorCodeMapper: Map[Int, ApplicationError]): Option[ApplicationError] = { val jsonErrorMaybe = (json \ "error") .asOpt[JsValue] diff --git a/server/app/controllers/TransactionsController.scala b/server/app/controllers/TransactionsController.scala index 91bda255..f48c3204 100644 --- a/server/app/controllers/TransactionsController.scala +++ b/server/app/controllers/TransactionsController.scala @@ -39,4 +39,8 @@ class TransactionsController @Inject()(transactionRPCService: TransactionRPCServ } .toFuture } + + def getTransactionUtxoByIndex(txid: String, index: Int, includeMempool: Boolean) = public { _ => + transactionRPCService.getTransactionUtxoByIndex(txid, index, includeMempool) + } } diff --git a/server/conf/routes b/server/conf/routes index f0d26031..820f13a6 100644 --- a/server/conf/routes +++ b/server/conf/routes @@ -10,6 +10,7 @@ GET /transactions/:txid controllers.TransactionsController.getTransacti GET /transactions/:txid/raw controllers.TransactionsController.getRawTransaction(txid: String) POST /transactions controllers.TransactionsController.sendRawTransaction() GET /transactions/:txid/lite controllers.TransactionsController.getTransactionLite(txid: String) +GET /transactions/:txid/utxos/:index controllers.TransactionsController.getTransactionUtxoByIndex(txid: String, index: Int, includeMempool: Boolean ?= true) GET /addresses/:address controllers.AddressesController.getBy(address: String) GET /addresses/:address/tposcontracts controllers.AddressesController.getTPoSContracts(address: String) diff --git a/server/test/com/xsn/explorer/helpers/DummyXSNService.scala b/server/test/com/xsn/explorer/helpers/DummyXSNService.scala index 5b1ff1bd..4c0a6112 100644 --- a/server/test/com/xsn/explorer/helpers/DummyXSNService.scala +++ b/server/test/com/xsn/explorer/helpers/DummyXSNService.scala @@ -27,4 +27,6 @@ class DummyXSNService extends XSNService { override def sendRawTransaction(hex: HexString): FutureApplicationResult[String] = ??? override def isTPoSContract(txid: TransactionId): FutureApplicationResult[Boolean] = ??? override def estimateSmartFee(confirmationsTarget: Int): FutureApplicationResult[JsValue] = ??? + override def getTxOut(txid: TransactionId, index: Int, includeMempool: Boolean): FutureApplicationResult[JsValue] = + ??? }