Skip to content

Commit

Permalink
server: update endpoint "GET /block-headers/:query?includeFilter=true"
Browse files Browse the repository at this point in the history
The endpoint for retrieving the block header allows specifying whether it needs the filter
  • Loading branch information
adinael committed Jun 7, 2019
1 parent 1cdb010 commit 1daf6f0
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 44 deletions.
4 changes: 2 additions & 2 deletions server/app/com/xsn/explorer/data/BlockDataHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ trait BlockDataHandler[F[_]] {
lastSeenHash: Option[Blockhash]
): F[List[BlockHeader]]

def getHeader(blockhash: Blockhash): F[BlockHeader]
def getHeader(blockhash: Blockhash, includeFilter: Boolean): F[BlockHeader]

def getHeader(height: Height): F[BlockHeader]
def getHeader(height: Height, includeFilter: Boolean): F[BlockHeader]
}

trait BlockBlockingDataHandler extends BlockDataHandler[ApplicationResult]
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ class BlockPostgresDataHandler @Inject()(override val database: Database, blockP
Good(result)
}

override def getHeader(blockhash: Blockhash): ApplicationResult[BlockHeader] =
override def getHeader(blockhash: Blockhash, includeFilter: Boolean): ApplicationResult[BlockHeader] =
withConnection { implicit conn =>
val maybe = blockPostgresDAO.getHeader(blockhash)
val maybe = blockPostgresDAO.getHeader(blockhash, includeFilter)
Or.from(maybe, One(BlockNotFoundError))
}

override def getHeader(height: Height): ApplicationResult[BlockHeader] =
override def getHeader(height: Height, includeFilter: Boolean): ApplicationResult[BlockHeader] =
withConnection { implicit conn =>
val maybe = blockPostgresDAO.getHeader(height)
val maybe = blockPostgresDAO.getHeader(height, includeFilter)
Or.from(maybe, One(BlockNotFoundError))
}

Expand Down
34 changes: 19 additions & 15 deletions server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ class BlockPostgresDAO @Inject()(
.getOrElse(header)
}

def getHeader(blockhash: Blockhash)(implicit conn: Connection): Option[BlockHeader] = {
def getHeader(blockhash: Blockhash, includeFilter: Boolean)(implicit conn: Connection): Option[BlockHeader] = {
val blockMaybe = SQL(
"""
|SELECT blockhash, previous_blockhash, merkle_root, height, time
Expand All @@ -227,16 +227,14 @@ class BlockPostgresDAO @Inject()(
)
.as(parseHeader.singleOpt)

for {
header <- blockMaybe
filterMaybe = blockFilterPostgresDAO.getBy(header.hash)
} yield filterMaybe
.map(header.withFilter)
.getOrElse(header)

if (includeFilter) {
blockMaybe.map(attachFilter)
} else {
blockMaybe
}
}

def getHeader(height: Height)(implicit conn: Connection): Option[BlockHeader] = {
def getHeader(height: Height, includeFilter: Boolean)(implicit conn: Connection): Option[BlockHeader] = {
val blockMaybe = SQL(
"""
|SELECT blockhash, previous_blockhash, merkle_root, height, time
Expand All @@ -248,13 +246,19 @@ class BlockPostgresDAO @Inject()(
)
.as(parseHeader.singleOpt)

for {
header <- blockMaybe
filterMaybe = blockFilterPostgresDAO.getBy(header.hash)
} yield filterMaybe
.map(header.withFilter)
.getOrElse(header)
if (includeFilter) {
blockMaybe.map(attachFilter)
} else {
blockMaybe
}
}

private def attachFilter(blockheader: BlockHeader)(implicit conn: Connection): BlockHeader = {
val filterMaybe = blockFilterPostgresDAO.getBy(blockheader.hash)

filterMaybe
.map(blockheader.withFilter)
.getOrElse(blockheader)
}

private def toSQL(condition: OrderingCondition): String = condition match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ class BlockFutureDataHandler @Inject()(blockBlockingDataHandler: BlockBlockingDa
blockBlockingDataHandler.getHeaders(limit, orderingCondition, lastSeenHash)
}

override def getHeader(blockhash: Blockhash): FutureApplicationResult[BlockHeader] = Future {
blockBlockingDataHandler.getHeader(blockhash)
override def getHeader(blockhash: Blockhash, includeFilter: Boolean): FutureApplicationResult[BlockHeader] = Future {
blockBlockingDataHandler.getHeader(blockhash, includeFilter)
}

override def getHeader(height: Height): FutureApplicationResult[BlockHeader] = Future {
blockBlockingDataHandler.getHeader(height)
override def getHeader(height: Height, includeFilter: Boolean): FutureApplicationResult[BlockHeader] = Future {
blockBlockingDataHandler.getHeader(height, includeFilter)
}
}
8 changes: 4 additions & 4 deletions server/app/com/xsn/explorer/services/BlockService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ class BlockService @Inject()(
result.toFuture
}

def getBlockHeader(blockhashString: String): FutureApplicationResult[BlockHeader] = {
def getBlockHeader(blockhashString: String, includeFilter: Boolean): FutureApplicationResult[BlockHeader] = {
val result = for {
blockhash <- blockhashValidator.validate(blockhashString).toFutureOr
header <- blockDataHandler.getHeader(blockhash).toFutureOr
header <- blockDataHandler.getHeader(blockhash, includeFilter).toFutureOr
} yield header

result.toFuture
}

def getBlockHeader(height: Height): FutureApplicationResult[BlockHeader] = {
blockDataHandler.getHeader(height)
def getBlockHeader(height: Height, includeFilter: Boolean): FutureApplicationResult[BlockHeader] = {
blockDataHandler.getHeader(height, includeFilter)
}

private def canCacheResult(
Expand Down
6 changes: 3 additions & 3 deletions server/app/controllers/BlocksController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ class BlocksController @Inject()(
* is not a valid height, we assume it might be a blockhash and try to
* retrieve the blockHeader by blockhash.
*/
def getBlockHeader(query: String) = public { _ =>
def getBlockHeader(query: String, includeFilter: Boolean) = public { _ =>
val (cache, resultF) = Try(query.toInt)
.map(Height.apply)
.map { value =>
"no-store" -> blockService.getBlockHeader(value)
"no-store" -> blockService.getBlockHeader(value, includeFilter)
}
.getOrElse {
"public, max-age=31536000" -> blockService.getBlockHeader(query)
"public, max-age=31536000" -> blockService.getBlockHeader(query, includeFilter)
}

resultF.toFutureOr.map { value =>
Expand Down
2 changes: 1 addition & 1 deletion server/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ GET /addresses/:address/utxos controllers.AddressesContro

GET /blocks controllers.BlocksController.getLatestBlocks()
GET /blocks/headers controllers.BlocksController.getBlockHeaders(lastSeenHash: Option[String], limit: Int ?= 10, order: String ?= "asc")
GET /block-headers/:query controllers.BlocksController.getBlockHeader(query: String)
GET /block-headers/:query controllers.BlocksController.getBlockHeader(query: String, includeFilter: Boolean ?= false)
GET /blocks/estimate-fee controllers.BlocksController.estimateFee(nBlocks: Int ?= 1)

GET /blocks/:query controllers.BlocksController.getDetails(query: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.alexitc.playsonify.models.pagination.{Count, Limit, Offset, Paginated
import com.xsn.explorer.data.anorm.BlockPostgresDataHandler
import com.xsn.explorer.data.common.PostgresDataHandlerSpec
import com.xsn.explorer.errors.BlockNotFoundError
import com.xsn.explorer.gcs.GolombCodedSet
import com.xsn.explorer.gcs.{GolombCodedSet, UnsignedByte}
import com.xsn.explorer.helpers.{BlockLoader, DataHandlerObjects}
import com.xsn.explorer.models.fields.BlockField
import com.xsn.explorer.models.persisted.{Block, BlockHeader}
Expand All @@ -23,6 +23,7 @@ class BlockPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAn
}

val dao = blockPostgresDAO
val blockFilterDAO = blockFilterPostgresDAO
val defaultOrdering = FieldOrdering(BlockField.Height, OrderingCondition.AscendingOrder)
lazy val dataHandler = new BlockPostgresDataHandler(database, dao)

Expand All @@ -33,6 +34,14 @@ class BlockPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAn
}
}

def insert(block: Block, filter: GolombCodedSet) = {
database.withConnection { implicit conn =>
val maybe = dao.insert(block)
blockFilterDAO.insert(block.hash, filter)
Or.from(maybe, One(BlockNotFoundError))
}
}

"getBy blockhash" should {
"return a block" in {
val block = BlockLoader
Expand Down Expand Up @@ -219,27 +228,63 @@ class BlockPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAn

"getHeader" should {

"return the blockHeader by blockhash" in {
"return the blockHeader by blockhash no filter" in {
val block = BlockLoader
.get("1ca318b7a26ed67ca7c8c9b5069d653ba224bf86989125d1dfbb0973b7d6a5e0")
.copy(previousBlockhash = None, nextBlockhash = None)
insert(block).isGood mustEqual true

val header = block.into[BlockHeader.Simple].transform
val result = dataHandler.getHeader(block.hash)
val result = dataHandler.getHeader(block.hash, false)

result.isGood mustEqual true
matches(header, result.get)
}

"return the blockHeader by height" in {
"return the blockHeader by blockhash with filter" in {
val block = BlockLoader
.get("00000c822abdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34")
.get("1ca318b7a26ed67ca7c8c9b5069d653ba224bf86989125d1dfbb0973b7d6a5e0")
.copy(previousBlockhash = None, nextBlockhash = None)
insert(block).isGood mustEqual true

val blockFilter = GolombCodedSet(1, 1, 1, List(new UnsignedByte(10.toByte)))
insert(block, blockFilter).isGood mustEqual true

val header =
block.into[BlockHeader.Simple].transform.withFilter(blockFilter)
val result = dataHandler.getHeader(block.hash, true)

result.isGood mustEqual true
matches(header, result.get)
}

"return the blockHeader by height no filter" in {
val block = BlockLoader
.get("1ca318b7a26ed67ca7c8c9b5069d653ba224bf86989125d1dfbb0973b7d6a5e0")
.copy(previousBlockhash = None, nextBlockhash = None)

val blockFilter = GolombCodedSet(1, 1, 1, List(new UnsignedByte(10.toByte)))
insert(block, blockFilter).isGood mustEqual true

val header = block.into[BlockHeader.Simple].transform
val result = dataHandler.getHeader(block.height)

val result = dataHandler.getHeader(block.height, false)

result.isGood mustEqual true
matches(header, result.get)
}

"return the blockHeader by height with filter" in {
val block = BlockLoader
.get("00000267225f7dba55d9a3493740e7f0dde0f28a371d2c3b42e7676b5728d020")
.copy(previousBlockhash = None, nextBlockhash = None)

val blockFilter = GolombCodedSet(1, 1, 1, List(new UnsignedByte(10.toByte)))
insert(block, blockFilter).isGood mustEqual true

val header =
block.into[BlockHeader.Simple].transform.withFilter(blockFilter)

val result = dataHandler.getHeader(block.height, true)

result.isGood mustEqual true
matches(header, result.get)
Expand All @@ -248,7 +293,7 @@ class BlockPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAn
"fail on block not found" in {
val blockhash = Blockhash.from("b858d38a3552c83aea58f66fe00ae220352a235e33fcf1f3af04507a61a9dc32").get

val result = dataHandler.getHeader(blockhash)
val result = dataHandler.getHeader(blockhash, true)
result mustEqual Bad(BlockNotFoundError).accumulating
}
}
Expand Down
55 changes: 52 additions & 3 deletions server/test/controllers/BlocksControllerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,10 @@ class BlocksControllerSpec extends MyAPISpec {

when(
blockBlockingDataHandlerMock
.getHeader(eqTo(block.hash))
.getHeader(eqTo(block.hash), eqTo(true))
).thenReturn(result)

val response = GET(s"/block-headers/${block.hash}")
val response = GET(s"/block-headers/${block.hash}?includeFilter=true")
status(response) mustEqual OK
val json = contentAsJson(response)
matchBlockHeader(blockheader, json)
Expand All @@ -421,7 +421,55 @@ class BlocksControllerSpec extends MyAPISpec {

when(
blockBlockingDataHandlerMock
.getHeader(eqTo(block.height))
.getHeader(eqTo(block.height), eqTo(true))
).thenReturn(result)

val response = GET(s"/block-headers/${block.height}?includeFilter=true")
status(response) mustEqual OK
val json = contentAsJson(response)
matchBlockHeader(blockheader, json)

val cacheHeader = header("Cache-Control", response)
cacheHeader.value mustEqual "no-store"
}

"return the blockHeader for the given blockhash no filter" in {

val block = BlockLoader.get("1ca318b7a26ed67ca7c8c9b5069d653ba224bf86989125d1dfbb0973b7d6a5e0")

val blockheader =
block
.into[BlockHeader.Simple]
.transform
val result = Good(blockheader)

when(
blockBlockingDataHandlerMock
.getHeader(eqTo(block.hash), eqTo(false))
).thenReturn(result)

val response = GET(s"/block-headers/${block.hash}?includeFilter=false")
status(response) mustEqual OK
val json = contentAsJson(response)
matchBlockHeader(blockheader, json)

val cacheHeader = header("Cache-Control", response)
cacheHeader.value mustEqual "public, max-age=31536000"
}

"return the blockHeader for the given height no filter" in {

val block = BlockLoader.get("1ca318b7a26ed67ca7c8c9b5069d653ba224bf86989125d1dfbb0973b7d6a5e0")

val blockheader =
block
.into[BlockHeader.Simple]
.transform
val result = Good(blockheader)

when(
blockBlockingDataHandlerMock
.getHeader(eqTo(block.height), eqTo(false))
).thenReturn(result)

val response = GET(s"/block-headers/${block.height}")
Expand All @@ -432,6 +480,7 @@ class BlocksControllerSpec extends MyAPISpec {
val cacheHeader = header("Cache-Control", response)
cacheHeader.value mustEqual "no-store"
}

}

private def matchBlock(expected: Block, actual: JsValue) = {
Expand Down

0 comments on commit 1daf6f0

Please sign in to comment.