From 3f60fe761137a75f67495ad0d9f1b99aada46e61 Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Sat, 14 Apr 2018 00:00:16 -0500 Subject: [PATCH] server: Add the number of masternodes to the statistics endpoint --- .../explorer/models/StatisticsDetails.scala | 26 +++++++++++++++++++ .../explorer/services/StatisticsService.scala | 20 +++++++++++--- .../xsn/explorer/services/XSNService.scala | 23 ++++++++++++++++ .../explorer/helpers/DummyXSNService.scala | 1 + .../StatisticsControllerSpec.scala | 14 +++++++++- 5 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 server/app/com/xsn/explorer/models/StatisticsDetails.scala diff --git a/server/app/com/xsn/explorer/models/StatisticsDetails.scala b/server/app/com/xsn/explorer/models/StatisticsDetails.scala new file mode 100644 index 0000000..b3a8f44 --- /dev/null +++ b/server/app/com/xsn/explorer/models/StatisticsDetails.scala @@ -0,0 +1,26 @@ +package com.xsn.explorer.models + +import play.api.libs.json._ + +case class StatisticsDetails(statistics: Statistics, masternodes: Option[Int]) + +object StatisticsDetails { + + implicit val writes: Writes[StatisticsDetails] = Writes { obj => + val values = Map( + "blocks" -> JsNumber(obj.statistics.blocks), + "transactions" -> JsNumber(obj.statistics.transactions), + "totalSupply" -> JsNumber(obj.statistics.totalSupply), + "circulatingSupply" -> JsNumber(obj.statistics.circulatingSupply)) + + val result = obj.masternodes + .map { count => + values + ("masternodes" -> JsNumber(count)) + } + .getOrElse { + values + } + + JsObject.apply(result) + } +} diff --git a/server/app/com/xsn/explorer/services/StatisticsService.scala b/server/app/com/xsn/explorer/services/StatisticsService.scala index 548d594..7f6c65c 100644 --- a/server/app/com/xsn/explorer/services/StatisticsService.scala +++ b/server/app/com/xsn/explorer/services/StatisticsService.scala @@ -3,16 +3,30 @@ package com.xsn.explorer.services import javax.inject.Inject import com.alexitc.playsonify.core.FutureApplicationResult +import com.alexitc.playsonify.core.FutureOr.Implicits.FutureOps import com.xsn.explorer.data.async.StatisticsFutureDataHandler -import com.xsn.explorer.models.Statistics +import com.xsn.explorer.models.StatisticsDetails +import org.scalactic.{Bad, Good} import scala.concurrent.ExecutionContext class StatisticsService @Inject() ( + xsnService: XSNService, statisticsFutureDataHandler: StatisticsFutureDataHandler)( implicit ec: ExecutionContext) { - def getStatistics(): FutureApplicationResult[Statistics] = { - statisticsFutureDataHandler.getStatistics() + def getStatistics(): FutureApplicationResult[StatisticsDetails] = { + val dbStats = statisticsFutureDataHandler.getStatistics() + val rpcStats = xsnService.getMasternodeCount() + + val result = for { + stats <- dbStats.toFutureOr + count <- rpcStats.map { + case Good(count) => Good(Some(count)) + case Bad(_) => Good(None) + }.toFutureOr + } yield StatisticsDetails(stats, count) + + result.toFuture } } diff --git a/server/app/com/xsn/explorer/services/XSNService.scala b/server/app/com/xsn/explorer/services/XSNService.scala index fa9d9bb..51ee799 100644 --- a/server/app/com/xsn/explorer/services/XSNService.scala +++ b/server/app/com/xsn/explorer/services/XSNService.scala @@ -30,6 +30,8 @@ trait XSNService { def getLatestBlock(): FutureApplicationResult[Block] def getServerStatistics(): FutureApplicationResult[ServerStatistics] + + def getMasternodeCount(): FutureApplicationResult[Int] } class XSNServiceRPCImpl @Inject() ( @@ -182,6 +184,27 @@ class XSNServiceRPCImpl @Inject() ( } } + override def getMasternodeCount(): FutureApplicationResult[Int] = { + val body = s""" + |{ + | "jsonrpc": "1.0", + | "method": "masternode", + | "params": ["count"] + |} + |""".stripMargin + + server + .post(body) + .map { response => + val maybe = getResult[Int](response) + 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 04107b4..78db732 100644 --- a/server/test/com/xsn/explorer/helpers/DummyXSNService.scala +++ b/server/test/com/xsn/explorer/helpers/DummyXSNService.scala @@ -13,4 +13,5 @@ class DummyXSNService extends XSNService { override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = ??? override def getLatestBlock(): FutureApplicationResult[Block] = ??? override def getServerStatistics(): FutureApplicationResult[ServerStatistics] = ??? + override def getMasternodeCount(): FutureApplicationResult[Int] = ??? } diff --git a/server/test/controllers/StatisticsControllerSpec.scala b/server/test/controllers/StatisticsControllerSpec.scala index 79ee96d..13892c4 100644 --- a/server/test/controllers/StatisticsControllerSpec.scala +++ b/server/test/controllers/StatisticsControllerSpec.scala @@ -1,13 +1,17 @@ package controllers -import com.alexitc.playsonify.core.ApplicationResult +import com.alexitc.playsonify.core.{ApplicationResult, FutureApplicationResult} import com.xsn.explorer.data.StatisticsBlockingDataHandler +import com.xsn.explorer.helpers.DummyXSNService import com.xsn.explorer.models.Statistics +import com.xsn.explorer.services.XSNService import controllers.common.MyAPISpec import org.scalactic.Good import play.api.inject.bind import play.api.test.Helpers._ +import scala.concurrent.Future + class StatisticsControllerSpec extends MyAPISpec { val stats = Statistics( @@ -20,8 +24,15 @@ class StatisticsControllerSpec extends MyAPISpec { override def getStatistics(): ApplicationResult[Statistics] = Good(stats) } + val xsnService = new DummyXSNService { + override def getMasternodeCount(): FutureApplicationResult[Int] = { + Future.successful(Good(1000)) + } + } + override val application = guiceApplicationBuilder .overrides(bind[StatisticsBlockingDataHandler].to(dataHandler)) + .overrides(bind[XSNService].to(xsnService)) .build() "GET /stats" should { @@ -34,6 +45,7 @@ class StatisticsControllerSpec extends MyAPISpec { (json \ "transactions").as[Int] mustEqual stats.transactions (json \ "totalSupply").as[BigDecimal] mustEqual stats.totalSupply (json \ "circulatingSupply").as[BigDecimal] mustEqual stats.circulatingSupply + (json \ "masternodes").as[Int] mustEqual 1000 } } }