Skip to content

Commit

Permalink
server: Add GET /blocks/:blockhash/lite endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan authored and Jonathan committed Sep 21, 2019
1 parent 829cfab commit 250326d
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 6 deletions.
53 changes: 49 additions & 4 deletions server/app/com/xsn/explorer/services/BlockService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import com.alexitc.playsonify.models.pagination.{Limit, Offset, PaginatedQuery}
import com.alexitc.playsonify.validators.PaginatedQueryValidator
import com.xsn.explorer.cache.BlockHeaderCache
import com.xsn.explorer.data.async.BlockFutureDataHandler
import com.xsn.explorer.errors.{BlockRewardsNotFoundError, XSNMessageError}
import com.xsn.explorer.errors.{BlockNotFoundError, BlockRewardsNotFoundError, XSNMessageError}
import com.xsn.explorer.models._
import com.xsn.explorer.models.persisted.BlockHeader
import com.xsn.explorer.models.rpc.{Block, TransactionVIN}
import com.xsn.explorer.models.values.{Blockhash, Height}
import com.xsn.explorer.models.values.{Blockhash, Height, Size}
import com.xsn.explorer.parsers.OrderingConditionParser
import com.xsn.explorer.services.logic.{BlockLogic, TransactionLogic}
import com.xsn.explorer.services.validators._
import javax.inject.Inject
import org.scalactic.{Bad, Good}
import play.api.libs.json.JsValue
import org.scalactic.{Bad, Good, One, Or}
import play.api.libs.json.{JsValue, Json}

import scala.concurrent.{ExecutionContext, Future}

Expand Down Expand Up @@ -182,6 +182,51 @@ class BlockService @Inject()(
}
}

def getBlockLite(blockhashString: String): FutureApplicationResult[(JsValue, Boolean)] = {
val result = for {
blockhash <- blockhashValidator.validate(blockhashString).toFutureOr
json <- xsnService.getFullRawBlock(blockhash).toFutureOr
size <- Or.from((json \ "size").asOpt[Size], One(BlockNotFoundError)).toFutureOr
height <- Or.from((json \ "height").asOpt[Height], One(BlockNotFoundError)).toFutureOr
version <- Or.from((json \ "version").asOpt[Int], One(BlockNotFoundError)).toFutureOr
merkleRoot <- Or.from((json \ "merkleroot").asOpt[Blockhash], One(BlockNotFoundError)).toFutureOr
time <- Or.from((json \ "time").asOpt[Long], One(BlockNotFoundError)).toFutureOr
medianTime <- Or.from((json \ "mediantime").asOpt[Long], One(BlockNotFoundError)).toFutureOr
nonce <- Or.from((json \ "nonce").asOpt[Long], One(BlockNotFoundError)).toFutureOr
bits <- Or.from((json \ "bits").asOpt[String], One(BlockNotFoundError)).toFutureOr
chainwork <- Or.from((json \ "chainwork").asOpt[String], One(BlockNotFoundError)).toFutureOr
difficulty <- Or.from((json \ "difficulty").asOpt[BigDecimal], One(BlockNotFoundError)).toFutureOr
latestBlock <- xsnService.getLatestBlock().toFutureOr
previousBlockhash = (json \ "previousblockhash").asOpt[Blockhash]
nextBlockhash = (json \ "nextblockhash").asOpt[Blockhash]
hexList = getHexFromTransactions((json \ "tx").as[List[JsValue]])
} yield (
Json.obj(
"hash" -> blockhashString,
"size" -> size,
"height" -> height,
"version" -> version,
"merkleRoot" -> merkleRoot,
"time" -> time,
"medianTime" -> medianTime,
"nonce" -> nonce,
"bits" -> bits,
"chainwork" -> chainwork,
"difficulty" -> difficulty,
"previousBlockhash" -> previousBlockhash,
"nextBlockhash" -> nextBlockhash,
"transactions" -> hexList
),
height.int + 20 < latestBlock.height.int
)

result.toFuture
}

private def getHexFromTransactions(list: List[JsValue]): List[String] = list.map { tx =>
(tx \ "hex").as[String]
}

private def isPoS(block: rpc.Block[_]): FutureApplicationResult[Boolean] = {
val result = for {
coinbase <- getCoinbase(block).toFutureOr
Expand Down
29 changes: 29 additions & 0 deletions server/app/com/xsn/explorer/services/XSNService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ trait XSNService {

def getRawBlock(blockhash: Blockhash): FutureApplicationResult[JsValue]

def getFullRawBlock(blockhash: Blockhash): FutureApplicationResult[JsValue]

def getBlockhash(height: Height): FutureApplicationResult[Blockhash]

def getLatestBlock(): FutureApplicationResult[rpc.Block.Canonical]
Expand Down Expand Up @@ -248,6 +250,33 @@ class XSNServiceRPCImpl @Inject()(
result
}

override def getFullRawBlock(blockhash: Blockhash): FutureApplicationResult[JsValue] = {
val errorCodeMapper = Map(-5 -> BlockNotFoundError)
val body = s"""{ "jsonrpc": "1.0", "method": "getblock", "params": ["${blockhash.string}", 2] }"""

val result = retrying {
server
.post(body)
.map { response =>
val maybe = getResult[JsValue](response, errorCodeMapper)
maybe.getOrElse {
logger.debug(
s"Unexpected response from XSN Server, blockhash = ${blockhash.string}, status = ${response.status}, response = ${response.body}"
)

Bad(XSNUnexpectedResponseError).accumulating
}
}
}

result.foreach {
case Bad(errors) => logger.warn(s"Failed to get full raw block $blockhash, errors = $errors")
case _ => ()
}

result
}

override def getBlockhash(height: Height): FutureApplicationResult[Blockhash] = {
val errorCodeMapper = Map(-8 -> BlockNotFoundError)
val body = s"""{ "jsonrpc": "1.0", "method": "getblockhash", "params": [${height.int}] }"""
Expand Down
20 changes: 18 additions & 2 deletions server/app/controllers/BlocksController.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package controllers

import com.alexitc.playsonify.core.FutureOr.Implicits.FutureOps
import com.alexitc.playsonify.models.ordering.OrderingQuery
import com.alexitc.playsonify.models.pagination.{Limit, Offset, PaginatedQuery}
import com.alexitc.playsonify.models.pagination.Limit
import com.xsn.explorer.models.LightWalletTransaction
import com.xsn.explorer.models.persisted.BlockHeader
import com.xsn.explorer.models.values.Height
Expand Down Expand Up @@ -95,6 +94,23 @@ class BlocksController @Inject()(
def estimateFee(nBlocks: Int) = public { _ =>
blockService.estimateFee(nBlocks)
}

def getBlockLite(blockhash: String) = public { _ =>
blockService
.getBlockLite(blockhash)
.toFutureOr
.map {
case (value, cacheable) => {
val response = Ok(Json.toJson(value))
if (cacheable) {
response.withHeaders("Cache-Control" -> "public, max-age=31536000")
} else {
response.withHeaders("Cache-Control" -> "no-store")
}
}
}
.toFuture
}
}

object BlocksController {
Expand Down
1 change: 1 addition & 0 deletions server/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ GET /blocks controllers.BlocksController.getLatestB
GET /block-headers controllers.BlocksController.getBlockHeaders(lastSeenHash: Option[String], limit: Int ?= 10, order: String ?= "asc")
GET /block-headers/:query controllers.BlocksController.getBlockHeader(query: String, includeFilter: Boolean ?= false)
GET /blocks/estimate-fee controllers.BlocksController.estimateFee(nBlocks: Int ?= 1)
GET /blocks/:blockhash/lite controllers.BlocksController.getBlockLite(blockhash: String)

GET /blocks/:query controllers.BlocksController.getDetails(query: String)
GET /blocks/:query/raw controllers.BlocksController.getRawBlock(query: String)
Expand Down
1 change: 1 addition & 0 deletions server/test/com/xsn/explorer/helpers/DummyXSNService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ class DummyXSNService extends XSNService {
override def estimateSmartFee(confirmationsTarget: Int): FutureApplicationResult[JsValue] = ???
override def getTxOut(txid: TransactionId, index: Int, includeMempool: Boolean): FutureApplicationResult[JsValue] =
???
override def getFullRawBlock(blockhash: Blockhash): FutureApplicationResult[JsValue] = ???
}

0 comments on commit 250326d

Please sign in to comment.