From bdbaa415b5f48d394da946b6c8113f33119d643d Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Sat, 30 Mar 2019 08:56:33 -0700 Subject: [PATCH] server: Add isTPoSContract method to the XSNService The method verifies whether a transaction is a TPoS contract. --- .../xsn/explorer/services/XSNService.scala | 30 ++++++++++++- .../explorer/helpers/DummyXSNService.scala | 1 + .../services/XSNServiceRPCImplSpec.scala | 42 +++++++++++++++---- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/server/app/com/xsn/explorer/services/XSNService.scala b/server/app/com/xsn/explorer/services/XSNService.scala index 03474d9..a85fa1b 100644 --- a/server/app/com/xsn/explorer/services/XSNService.scala +++ b/server/app/com/xsn/explorer/services/XSNService.scala @@ -11,7 +11,7 @@ import com.xsn.explorer.models.values._ import javax.inject.Inject import org.scalactic.{Bad, Good} import org.slf4j.LoggerFactory -import play.api.libs.json.{JsNull, JsValue, Reads} +import play.api.libs.json._ import play.api.libs.ws.{WSAuthScheme, WSClient, WSResponse} import scala.util.Try @@ -48,6 +48,8 @@ trait XSNService { def sendRawTransaction(hex: HexString): FutureApplicationResult[String] + def isTPoSContract(txid: TransactionId): FutureApplicationResult[Boolean] + def cleanGenesisBlock(block: rpc.Block): rpc.Block = { Option(block) .filter(_.hash == genesisBlockhash) @@ -421,6 +423,32 @@ class XSNServiceRPCImpl @Inject() ( } } + override def isTPoSContract(txid: TransactionId): FutureApplicationResult[Boolean] = { + val innerBody = Json.obj("txid" -> txid.string, "check_spent" -> 0) + val body = Json.obj( + "jsonrpc" -> "1.0", + "method" -> "tposcontract", + "params" -> List( + JsString("validate"), + JsString(innerBody.toString()) + ) + ) + + server + .post(body) + .map { response => + val maybe = getResult[String](response) + .map { _.map(_ == "Contract is valid") } + + maybe.getOrElse { + logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}") + + Bad(XSNUnexpectedResponseError).accumulating + } + } + } + + private def mapError(json: JsValue, errorCodeMapper: Map[Int, ApplicationError]): Option[ApplicationError] = { val jsonErrorMaybe = (json \ "error") .asOpt[JsValue] diff --git a/server/test/com/xsn/explorer/helpers/DummyXSNService.scala b/server/test/com/xsn/explorer/helpers/DummyXSNService.scala index 27a0db8..93c90ff 100644 --- a/server/test/com/xsn/explorer/helpers/DummyXSNService.scala +++ b/server/test/com/xsn/explorer/helpers/DummyXSNService.scala @@ -25,4 +25,5 @@ class DummyXSNService extends XSNService { override def getMasternode(ipAddress: IPAddress): FutureApplicationResult[Masternode] = ??? override def getUnspentOutputs(address: Address): FutureApplicationResult[JsValue] = ??? override def sendRawTransaction(hex: HexString): FutureApplicationResult[String] = ??? + override def isTPoSContract(txid: TransactionId): FutureApplicationResult[Boolean] = ??? } diff --git a/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala b/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala index 6fc3f75..e40aa29 100644 --- a/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala +++ b/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala @@ -3,23 +3,23 @@ package com.xsn.explorer.services import com.xsn.explorer.config.{ExplorerConfig, RPCConfig} import com.xsn.explorer.errors._ import com.xsn.explorer.helpers.{BlockLoader, DataHelper, Executors, TransactionLoader} -import com.xsn.explorer.models._ import com.xsn.explorer.models.rpc.Masternode import com.xsn.explorer.models.values._ import org.mockito.ArgumentMatchers._ -import org.mockito.Mockito._ +import org.mockito.Mockito.{mock => _, _} import org.scalactic.{Bad, Good} +import org.scalatest.MustMatchers._ +import org.scalatest.WordSpec import org.scalatest.concurrent.PatienceConfiguration.Timeout -import org.scalatest.concurrent.ScalaFutures -import org.scalatest.mockito.MockitoSugar +import org.scalatest.concurrent.ScalaFutures._ +import org.scalatest.mockito.MockitoSugar._ import org.scalatest.time.{Seconds, Span} -import org.scalatest.{MustMatchers, OptionValues, WordSpec} import play.api.libs.json.{JsNull, JsString, JsValue, Json} import play.api.libs.ws.{WSClient, WSRequest, WSResponse} import scala.concurrent.Future -class XSNServiceRPCImplSpec extends WordSpec with MustMatchers with ScalaFutures with MockitoSugar with OptionValues { +class XSNServiceRPCImplSpec extends WordSpec { import DataHelper._ @@ -601,11 +601,39 @@ class XSNServiceRPCImplSpec extends WordSpec with MustMatchers with ScalaFutures } } + "isTPoSContract" should { + "return true when the contract is valid" in { + val txid = createTransactionId("b02f99d87194c9400ab147c070bf621770684906dedfbbe9ba5f3a35c26b8d01") + val content = "Contract is valid" + val responseBody = createRPCSuccessfulResponse(JsString(content)) + val json = Json.parse(responseBody) + + mockRequest(request, response)(200, json) + + whenReady(service.isTPoSContract(txid)) { result => + result mustEqual Good(true) + } + } + + "return false when the contract is not valid" in { + val txid = createTransactionId("b02f99d87194c9400ab147c070bf621770684906dedfbbe9ba5f3a35c26b8d01") + val content = "Contract invalid, error: Signature invalid" + val responseBody = createRPCSuccessfulResponse(JsString(content)) + val json = Json.parse(responseBody) + + mockRequest(request, response)(200, json) + + whenReady(service.isTPoSContract(txid)) { result => + result mustEqual Good(false) + } + } + } + private def mockRequest(request: WSRequest, response: WSResponse)(status: Int, body: JsValue) = { when(response.status).thenReturn(status) when(response.json).thenReturn(body) when(response.body).thenReturn(body.toString()) - when(request.post[String](anyString())(any())).thenReturn(Future.successful(response)) + when(request.post[AnyRef](any())(any())).thenReturn(Future.successful(response)) } private def mockRequestString(request: WSRequest, response: WSResponse)(status: Int, body: String) = {