Browse Source

server: Update "GET /blocks/:query"

Allows to find a block by blockhash or by block height.
scalafmt-draft
Alexis Hernandez 7 years ago
parent
commit
15d8d8992b
  1. 16
      server/app/com/xsn/explorer/services/BlockService.scala
  2. 15
      server/app/controllers/BlocksController.scala
  3. 2
      server/conf/routes
  4. 66
      server/test/controllers/BlocksControllerSpec.scala

16
server/app/com/xsn/explorer/services/BlockService.scala

@ -33,6 +33,22 @@ class BlockService @Inject() (
result.toFuture
}
def getDetails(height: Height): FutureApplicationResult[BlockDetails] = {
val result = for {
blockhash <- xsnService
.getBlockhash(height)
.toFutureOr
block <- xsnService
.getBlock(blockhash)
.toFutureOr
rewards <- getBlockRewards(block).toFutureOr
} yield BlockDetails(block, rewards)
result.toFuture
}
def getLatestBlocks(): FutureApplicationResult[List[Block]] = {
/**
* Temporal workaround to retrieve the latest blocks, they

15
server/app/controllers/BlocksController.scala

@ -2,9 +2,12 @@ package controllers
import javax.inject.Inject
import com.xsn.explorer.models.Height
import com.xsn.explorer.services.BlockService
import controllers.common.{MyJsonController, MyJsonControllerComponents}
import scala.util.Try
class BlocksController @Inject() (
blockService: BlockService,
cc: MyJsonControllerComponents)
@ -14,7 +17,15 @@ class BlocksController @Inject() (
blockService.getLatestBlocks()
}
def getDetails(blockhash: String) = publicNoInput { _ =>
blockService.getDetails(blockhash)
/**
* Try to retrieve a block by height, in case the query argument
* is not a valid height, we assume it might be a blockhash and try to
* retrieve the block by blockhash.
*/
def getDetails(query: String) = publicNoInput { _ =>
Try(query.toInt)
.map(Height.apply)
.map(blockService.getDetails)
.getOrElse(blockService.getDetails(query))
}
}

2
server/conf/routes

@ -10,7 +10,7 @@ GET /transactions/:txid controllers.TransactionsController.getTransaction(t
GET /addresses/:address controllers.AddressesController.getDetails(address: String)
GET /blocks controllers.BlocksController.getLatestBlocks()
GET /blocks/:blockhash controllers.BlocksController.getDetails(blockhash: String)
GET /blocks/:query controllers.BlocksController.getDetails(query: String)
GET /stats controllers.StatisticsController.getStatus()

66
server/test/controllers/BlocksControllerSpec.scala

@ -60,6 +60,17 @@ class BlocksControllerSpec extends MyAPISpec {
Future.successful(result)
}
override def getBlockhash(height: Height): FutureApplicationResult[Blockhash] = {
val result = blocks
.values
.find(_.height == height)
.map(_.hash)
.map(Good(_))
.getOrElse(Bad(BlockNotFoundError).accumulating)
Future.successful(result)
}
val txs = Map(
posBlockCoinstakeTx.id -> posBlockCoinstakeTx,
posBlockCoinstakeTxInput.id -> posBlockCoinstakeTxInput,
@ -88,8 +99,8 @@ class BlocksControllerSpec extends MyAPISpec {
.overrides(bind[XSNService].to(customXSNService))
.build()
"GET /blocks/:blockhash" should {
def url(blockhash: String) = s"/blocks/$blockhash"
"GET /blocks/:query" should {
def url(query: String) = s"/blocks/$query"
"retrieve a PoS block" in {
val block = posBlock
@ -225,7 +236,6 @@ class BlocksControllerSpec extends MyAPISpec {
val jsonMasternode = (jsonRewards \ "masternode").as[JsValue]
(jsonMasternode \ "address").as[String] mustEqual "XydZnssXHCxxRtB4rk7evfKT9XP7GqyA9N"
(jsonMasternode \ "value").as[BigDecimal] mustEqual BigDecimal("22.5")
}
"retrieve TPoS block with coinsplit" in {
@ -267,6 +277,40 @@ class BlocksControllerSpec extends MyAPISpec {
(jsonMasternode \ "value").as[BigDecimal] mustEqual BigDecimal("22.5")
}
"retrieve a block by height" in {
val block = posBlock
val response = GET(url(block.height.toString))
status(response) mustEqual OK
val json = contentAsJson(response)
val jsonBlock = (json \ "block").as[JsValue]
val jsonRewards = (json \ "rewards").as[JsValue]
(jsonBlock \ "hash").as[Blockhash] mustEqual block.hash
(jsonBlock \ "size").as[Size] mustEqual block.size
(jsonBlock \ "bits").as[String] mustEqual block.bits
(jsonBlock \ "chainwork").as[String] mustEqual block.chainwork
(jsonBlock \ "difficulty").as[BigDecimal] mustEqual block.difficulty
(jsonBlock \ "confirmations").as[Confirmations] mustEqual block.confirmations
(jsonBlock \ "height").as[Height] mustEqual block.height
(jsonBlock \ "medianTime").as[Long] mustEqual block.medianTime
(jsonBlock \ "time").as[Long] mustEqual block.time
(jsonBlock \ "merkleRoot").as[Blockhash] mustEqual block.merkleRoot
(jsonBlock \ "version").as[Long] mustEqual block.version
(jsonBlock \ "nonce").as[Int] mustEqual block.nonce
(jsonBlock \ "previousBlockhash").asOpt[Blockhash] mustEqual block.previousBlockhash
(jsonBlock \ "nextBlockhash").asOpt[Blockhash] mustEqual block.nextBlockhash
val jsonCoinstake = (jsonRewards \ "coinstake").as[JsValue]
(jsonCoinstake \ "address").as[String] mustEqual "XiHW7SR56UPHeXKwcpeVsE4nUfkHv5RqE3"
(jsonCoinstake \ "value").as[BigDecimal] mustEqual BigDecimal("22.49999999")
val jsonMasternode = (jsonRewards \ "masternode").as[JsValue]
(jsonMasternode \ "address").as[String] mustEqual "XjUDDq221NwqRtp85wfvoDrMaaxvUCDRrY"
(jsonMasternode \ "value").as[BigDecimal] mustEqual BigDecimal("22.5")
}
"fail on the wrong blockhash format" in {
val response = GET(url("000125c06cedf38b07bff174bdb61027935dbcb34831d28cff40bedb519d5"))
@ -283,6 +327,22 @@ class BlocksControllerSpec extends MyAPISpec {
(error \ "message").as[String].nonEmpty mustEqual true
}
"fail on unknown block height" in {
val response = GET(url("-1"))
status(response) mustEqual BAD_REQUEST
val json = contentAsJson(response)
val errorList = (json \ "errors").as[List[JsValue]]
errorList.size mustEqual 1
val error = errorList.head
(error \ "type").as[String] mustEqual PublicErrorRenderer.FieldValidationErrorType
(error \ "field").as[String] mustEqual "blockhash"
(error \ "message").as[String].nonEmpty mustEqual true
}
"fail on an unknown block" in {
val response = GET(url("000003dc4c2fc449dededaaad6efc33ce1b64b88a060652dc47edc63d6d6b524"))

Loading…
Cancel
Save