Browse Source

server: Add getMasternodes method to XSNService

scalafmt-draft
Alexis Hernandez 7 years ago
parent
commit
acc4ddba2d
  1. 67
      server/app/com/xsn/explorer/models/rpc/Masternode.scala
  2. 57
      server/app/com/xsn/explorer/services/XSNService.scala
  3. 12
      server/test/com/xsn/explorer/helpers/DummyXSNService.scala
  4. 67
      server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala

67
server/app/com/xsn/explorer/models/rpc/Masternode.scala

@ -0,0 +1,67 @@
package com.xsn.explorer.models.rpc
import com.xsn.explorer.models.{Address, TransactionId}
import scala.util.Try
case class Masternode(
txid: TransactionId,
ip: String,
protocol: String,
status: String,
activeSeconds: Long,
lastSeen: Long,
payee: Address
)
object Masternode {
/**
* The RPC server give us a map like this one:
*
* ```
* {
* "47b46ba99c760eeb6f443e5b6228d5dfeeac1cd5eec5fb9a79471af14c4c4c00-1": " ENABLED 70208 Xo27xzC57FonGesBDqyoqoFZ9kLhy946Be 1524348946 1146950 1524252156 63199 45.77.63.186:62583"
* }
* ```
*
* The key is the transaction id used to send the funds to the masternode and the value a console formatted string with the values.
* Note that the transaction id ends with `-x` where x is a number.
*/
def fromMap(values: Map[String, String]): List[Masternode] = {
values
.map { case (key, value) =>
val list = value.split(" ").map(_.trim).filter(_.nonEmpty).toList
parseValues(key, list)
}
.toList
.flatten
}
private def parseTxid(key: String): Option[TransactionId] = {
key
.split("\\-")
.headOption
.flatMap(TransactionId.from)
}
private def parseValues(key: String, values: List[String]): Option[Masternode] = values match {
case status :: protocol :: payee :: lastPaid :: activeSeconds :: lastSeen :: lastPaidBlock :: ip :: _ =>
for {
txid <- parseTxid(key)
payee <- Address.from(payee)
lastSeen <- Try(lastSeen.toLong).toOption
activeSeconds <- Try(activeSeconds.toLong).toOption
} yield Masternode(
txid = txid,
status = status,
protocol = protocol,
payee = payee,
ip = ip,
lastSeen = lastSeen,
activeSeconds = activeSeconds
)
case _ => None
}
}

57
server/app/com/xsn/explorer/services/XSNService.scala

@ -9,7 +9,6 @@ import com.xsn.explorer.config.RPCConfig
import com.xsn.explorer.errors._
import com.xsn.explorer.executors.ExternalServiceExecutionContext
import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.{AddressBalance, Block, ServerStatistics, Transaction}
import org.scalactic.{Bad, Good}
import org.slf4j.LoggerFactory
import play.api.libs.json.{JsNull, JsValue, Reads}
@ -19,19 +18,21 @@ import scala.util.Try
trait XSNService {
def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction]
def getTransaction(txid: TransactionId): FutureApplicationResult[rpc.Transaction]
def getAddressBalance(address: Address): FutureApplicationResult[AddressBalance]
def getAddressBalance(address: Address): FutureApplicationResult[rpc.AddressBalance]
def getTransactions(address: Address): FutureApplicationResult[List[TransactionId]]
def getBlock(blockhash: Blockhash): FutureApplicationResult[Block]
def getBlock(blockhash: Blockhash): FutureApplicationResult[rpc.Block]
def getLatestBlock(): FutureApplicationResult[Block]
def getLatestBlock(): FutureApplicationResult[rpc.Block]
def getServerStatistics(): FutureApplicationResult[ServerStatistics]
def getServerStatistics(): FutureApplicationResult[rpc.ServerStatistics]
def getMasternodeCount(): FutureApplicationResult[Int]
def getMasternodes(): FutureApplicationResult[List[rpc.Masternode]]
}
class XSNServiceRPCImpl @Inject() (
@ -47,14 +48,14 @@ class XSNServiceRPCImpl @Inject() (
.withHttpHeaders("Content-Type" -> "text/plain")
override def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction] = {
override def getTransaction(txid: TransactionId): FutureApplicationResult[rpc.Transaction] = {
val errorCodeMapper = Map(-5 -> TransactionNotFoundError)
server
.post(s"""{ "jsonrpc": "1.0", "method": "getrawtransaction", "params": ["${txid.string}", 1] }""")
.map { response =>
val maybe = getResult[Transaction](response, errorCodeMapper)
val maybe = getResult[rpc.Transaction](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, txid = ${txid.string}, status = ${response.status}, response = ${response.body}")
@ -63,7 +64,7 @@ class XSNServiceRPCImpl @Inject() (
}
}
override def getAddressBalance(address: Address): FutureApplicationResult[AddressBalance] = {
override def getAddressBalance(address: Address): FutureApplicationResult[rpc.AddressBalance] = {
val body = s"""
|{
| "jsonrpc": "1.0",
@ -81,7 +82,7 @@ class XSNServiceRPCImpl @Inject() (
.post(body)
.map { response =>
val maybe = getResult[AddressBalance](response, errorCodeMapper)
val maybe = getResult[rpc.AddressBalance](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, address = ${address.string}, response = ${response.body}")
@ -117,7 +118,7 @@ class XSNServiceRPCImpl @Inject() (
}
}
override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = {
override def getBlock(blockhash: Blockhash): FutureApplicationResult[rpc.Block] = {
val errorCodeMapper = Map(-5 -> BlockNotFoundError)
val body = s"""{ "jsonrpc": "1.0", "method": "getblock", "params": ["${blockhash.string}"] }"""
@ -125,7 +126,7 @@ class XSNServiceRPCImpl @Inject() (
.post(body)
.map { response =>
val maybe = getResult[Block](response, errorCodeMapper)
val maybe = getResult[rpc.Block](response, errorCodeMapper)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, txid = ${blockhash.string}, status = ${response.status}, response = ${response.body}")
@ -134,7 +135,7 @@ class XSNServiceRPCImpl @Inject() (
}
}
override def getLatestBlock(): FutureApplicationResult[Block] = {
override def getLatestBlock(): FutureApplicationResult[rpc.Block] = {
val body = s"""
|{
| "jsonrpc": "1.0",
@ -163,7 +164,7 @@ class XSNServiceRPCImpl @Inject() (
}
}
override def getServerStatistics(): FutureApplicationResult[ServerStatistics] = {
override def getServerStatistics(): FutureApplicationResult[rpc.ServerStatistics] = {
val body = s"""
|{
| "jsonrpc": "1.0",
@ -175,7 +176,7 @@ class XSNServiceRPCImpl @Inject() (
server
.post(body)
.map { response =>
val maybe = getResult[ServerStatistics](response)
val maybe = getResult[rpc.ServerStatistics](response)
maybe.getOrElse {
logger.warn(s"Unexpected response from XSN Server, status = ${response.status}, response = ${response.body}")
@ -205,6 +206,32 @@ class XSNServiceRPCImpl @Inject() (
}
}
override def getMasternodes(): FutureApplicationResult[List[rpc.Masternode]] = {
val body = s"""
|{
| "jsonrpc": "1.0",
| "method": "masternode",
| "params": ["list", "full"]
|}
|""".stripMargin
server
.post(body)
.map { response =>
val maybe = getResult[Map[String, String]](response)
.map {
case Good(map) => Good(rpc.Masternode.fromMap(map))
case Bad(errors) => Bad(errors)
}
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]

12
server/test/com/xsn/explorer/helpers/DummyXSNService.scala

@ -2,16 +2,16 @@ package com.xsn.explorer.helpers
import com.alexitc.playsonify.core.FutureApplicationResult
import com.xsn.explorer.models._
import com.xsn.explorer.models.rpc.{AddressBalance, Block, ServerStatistics, Transaction}
import com.xsn.explorer.services.XSNService
class DummyXSNService extends XSNService {
override def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction] = ???
override def getAddressBalance(address: Address): FutureApplicationResult[AddressBalance] = ???
override def getTransaction(txid: TransactionId): FutureApplicationResult[rpc.Transaction] = ???
override def getAddressBalance(address: Address): FutureApplicationResult[rpc.AddressBalance] = ???
override def getTransactions(address: Address): FutureApplicationResult[List[TransactionId]] = ???
override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = ???
override def getLatestBlock(): FutureApplicationResult[Block] = ???
override def getServerStatistics(): FutureApplicationResult[ServerStatistics] = ???
override def getBlock(blockhash: Blockhash): FutureApplicationResult[rpc.Block] = ???
override def getLatestBlock(): FutureApplicationResult[rpc.Block] = ???
override def getServerStatistics(): FutureApplicationResult[rpc.ServerStatistics] = ???
override def getMasternodeCount(): FutureApplicationResult[Int] = ???
override def getMasternodes(): FutureApplicationResult[List[rpc.Masternode]] = ???
}

67
server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala

@ -3,10 +3,11 @@ package com.xsn.explorer.services
import com.xsn.explorer.config.RPCConfig
import com.xsn.explorer.errors._
import com.xsn.explorer.helpers.{BlockLoader, DataHelper, Executors, TransactionLoader}
import com.xsn.explorer.models.{Address, Blockhash, Height}
import com.xsn.explorer.models.rpc.Masternode
import com.xsn.explorer.models.{Address, Blockhash, Height, TransactionId}
import org.mockito.ArgumentMatchers._
import org.mockito.Mockito._
import org.scalactic.Bad
import org.scalactic.{Bad, Good}
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.mockito.MockitoSugar
import org.scalatest.{MustMatchers, OptionValues, WordSpec}
@ -345,4 +346,66 @@ class XSNServiceRPCImplSpec extends WordSpec with MustMatchers with ScalaFutures
}
}
}
"getMasternodeCount" should {
"return the count" in {
val content = "10"
val responseBody = createRPCSuccessfulResponse(Json.parse(content))
val json = Json.parse(responseBody)
when(response.status).thenReturn(200)
when(response.json).thenReturn(json)
when(request.post[String](anyString())(any())).thenReturn(Future.successful(response))
whenReady(service.getMasternodeCount()) { result =>
result mustEqual Good(10)
}
}
}
"getMasternodes" should {
"return the masternodes" in {
val content =
"""
|{
| "c3efb8b60bda863a3a963d340901dc2b870e6ea51a34276a8f306d47ffb94f01-0": " WATCHDOG_EXPIRED 70208 XqdmM7rop8Sdgn8UjyNh3Povc3rhNSXYw2 1524349009 513323 1524297814 63936 45.77.136.212:62583",
| "b02f99d87194c9400ab147c070bf621770684906dedfbbe9ba5f3a35c26b8d01-1": " ENABLED 70208 XdNDRAiMUC9KiVRzhCTg9w44jQRdCpCRe3 1524349028 777344 1524312645 64183 45.32.148.13:62583"
|}
""".stripMargin
val expected = List(
Masternode(
txid = TransactionId.from("c3efb8b60bda863a3a963d340901dc2b870e6ea51a34276a8f306d47ffb94f01").get,
ip = "45.77.136.212:62583",
protocol = "70208",
status = "WATCHDOG_EXPIRED",
activeSeconds = 513323,
lastSeen = 1524297814L,
Address.from("XqdmM7rop8Sdgn8UjyNh3Povc3rhNSXYw2").get),
Masternode(
txid = TransactionId.from("b02f99d87194c9400ab147c070bf621770684906dedfbbe9ba5f3a35c26b8d01").get,
ip = "45.32.148.13:62583",
protocol = "70208",
status = "ENABLED",
activeSeconds = 777344,
lastSeen = 1524312645L,
Address.from("XdNDRAiMUC9KiVRzhCTg9w44jQRdCpCRe3").get)
)
val responseBody = createRPCSuccessfulResponse(Json.parse(content))
val json = Json.parse(responseBody)
when(response.status).thenReturn(200)
when(response.json).thenReturn(json)
when(request.post[String](anyString())(any())).thenReturn(Future.successful(response))
whenReady(service.getMasternodes()) { result =>
result.isGood mustEqual true
val masternodes = result.get
masternodes mustEqual expected
}
}
}
}

Loading…
Cancel
Save