diff --git a/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala index 72ddd61e..0879f971 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/BlockPostgresDAO.scala @@ -161,7 +161,8 @@ class BlockPostgresDAO @Inject()( val headers = SQL( s""" - |SELECT blockhash, previous_blockhash, merkle_root, height, time + |SELECT blockhash, previous_blockhash, next_blockhash, tpos_contract, merkle_root, size, + | height, version, time, median_time, nonce, bits, chainwork, difficulty, extraction_method |FROM blocks |ORDER BY height $order |LIMIT {limit} @@ -195,7 +196,8 @@ class BlockPostgresDAO @Inject()( | FROM blocks | WHERE blockhash = {lastSeenHash} |) - |SELECT b.blockhash, b.previous_blockhash, b.merkle_root, b.height, b.time + |SELECT blockhash, previous_blockhash, next_blockhash, tpos_contract, merkle_root, size, + | height, version, time, median_time, nonce, bits, chainwork, difficulty, extraction_method |FROM CTE CROSS JOIN blocks b |WHERE b.height $comparator lastSeenHeight |ORDER BY height $order @@ -218,7 +220,8 @@ class BlockPostgresDAO @Inject()( def getHeader(blockhash: Blockhash, includeFilter: Boolean)(implicit conn: Connection): Option[BlockHeader] = { val blockMaybe = SQL( """ - |SELECT blockhash, previous_blockhash, merkle_root, height, time + |SELECT blockhash, previous_blockhash, next_blockhash, tpos_contract, merkle_root, size, + | height, version, time, median_time, nonce, bits, chainwork, difficulty, extraction_method |FROM blocks |WHERE blockhash = {blockhash} """.stripMargin @@ -237,7 +240,8 @@ class BlockPostgresDAO @Inject()( def getHeader(height: Height, includeFilter: Boolean)(implicit conn: Connection): Option[BlockHeader] = { val blockMaybe = SQL( """ - |SELECT blockhash, previous_blockhash, merkle_root, height, time + |SELECT blockhash, previous_blockhash, next_blockhash, tpos_contract, merkle_root, size, + | height, version, time, median_time, nonce, bits, chainwork, difficulty, extraction_method |FROM blocks |WHERE height = {height} """.stripMargin diff --git a/server/app/com/xsn/explorer/data/anorm/parsers/BlockParsers.scala b/server/app/com/xsn/explorer/data/anorm/parsers/BlockParsers.scala index 10cc9748..69819c55 100644 --- a/server/app/com/xsn/explorer/data/anorm/parsers/BlockParsers.scala +++ b/server/app/com/xsn/explorer/data/anorm/parsers/BlockParsers.scala @@ -77,8 +77,53 @@ object BlockParsers { ) } - val parseHeader = (parseBlockhash() ~ parsePreviousBlockhash.? ~ parseMerkleRoot ~ parseHeight ~ parseTime).map { - case blockhash ~ previousBlockhash ~ merkleRoot ~ height ~ time => - BlockHeader.Simple(blockhash, previousBlockhash, merkleRoot, height, time) + val parseHeader = (parseBlockhash() ~ + parseNextBlockhash.? ~ + parsePreviousBlockhash.? ~ + parseTposContract.? ~ + parseMerkleRoot ~ + parseSize ~ + parseHeight ~ + parseVersion ~ + parseTime ~ + parseMedianTime ~ + parseNonce ~ + parseBits ~ + parseChainwork ~ + parseDifficulty ~ + parseExtractionMethod).map { + + case hash ~ + nextBlockhash ~ + previousBlockhash ~ + tposContract ~ + merkleRoot ~ + size ~ + height ~ + version ~ + time ~ + medianTime ~ + nonce ~ + bits ~ + chainwork ~ + difficulty ~ + extractionMethod => + BlockHeader.Simple( + hash = hash, + previousBlockhash = previousBlockhash, + nextBlockhash = nextBlockhash, + tposContract = tposContract, + merkleRoot = merkleRoot, + size = size, + height = height, + time = time, + medianTime = medianTime, + nonce = nonce, + bits = bits, + chainwork = chainwork, + difficulty = difficulty, + version = version, + extractionMethod = extractionMethod + ) } } diff --git a/server/app/com/xsn/explorer/models/persisted/BlockHeader.scala b/server/app/com/xsn/explorer/models/persisted/BlockHeader.scala index 81e7c989..61f8b252 100644 --- a/server/app/com/xsn/explorer/models/persisted/BlockHeader.scala +++ b/server/app/com/xsn/explorer/models/persisted/BlockHeader.scala @@ -1,6 +1,7 @@ package com.xsn.explorer.models.persisted import com.xsn.explorer.gcs.GolombCodedSet +import com.xsn.explorer.models.BlockExtractionMethod import com.xsn.explorer.models.values._ import io.scalaland.chimney.dsl._ import play.api.libs.json.{Json, Writes} @@ -9,9 +10,19 @@ sealed trait BlockHeader extends Product with Serializable { def hash: Blockhash def previousBlockhash: Option[Blockhash] + def nextBlockhash: Option[Blockhash] + def tposContract: Option[TransactionId] def merkleRoot: Blockhash + def size: Size def height: Height + def version: Int def time: Long + def medianTime: Long + def nonce: Long + def bits: String + def chainwork: String + def difficulty: BigDecimal + def extractionMethod: BlockExtractionMethod def withFilter(filter: GolombCodedSet): BlockHeader.HasFilter = { this @@ -26,17 +37,37 @@ object BlockHeader { case class Simple( hash: Blockhash, previousBlockhash: Option[Blockhash], + nextBlockhash: Option[Blockhash], + tposContract: Option[TransactionId], merkleRoot: Blockhash, + size: Size, height: Height, - time: Long + version: Int, + time: Long, + medianTime: Long, + nonce: Long, + bits: String, + chainwork: String, + difficulty: BigDecimal, + extractionMethod: BlockExtractionMethod ) extends BlockHeader case class HasFilter( hash: Blockhash, previousBlockhash: Option[Blockhash], + nextBlockhash: Option[Blockhash], + tposContract: Option[TransactionId], merkleRoot: Blockhash, + size: Size, height: Height, + version: Int, time: Long, + medianTime: Long, + nonce: Long, + bits: String, + chainwork: String, + difficulty: BigDecimal, + extractionMethod: BlockExtractionMethod, filter: GolombCodedSet ) extends BlockHeader @@ -49,7 +80,23 @@ object BlockHeader { ) } - implicit val writes: Writes[BlockHeader] = (obj: BlockHeader) => { + val partialWrites: Writes[BlockHeader] = (obj: BlockHeader) => { + val filterMaybe = obj match { + case x: HasFilter => Some(x.filter) + case _ => Option.empty + } + + Json.obj( + "hash" -> obj.hash, + "previousBlockhash" -> obj.previousBlockhash, + "merkleRoot" -> obj.merkleRoot, + "height" -> obj.height, + "time" -> obj.time, + "filter" -> filterMaybe + ) + } + + val completeWrites: Writes[BlockHeader] = (obj: BlockHeader) => { val filterMaybe = obj match { case x: HasFilter => Some(x.filter) case _ => Option.empty @@ -58,9 +105,19 @@ object BlockHeader { Json.obj( "hash" -> obj.hash, "previousBlockhash" -> obj.previousBlockhash, + "nextBlockhash" -> obj.nextBlockhash, + "tposContract" -> obj.tposContract, "merkleRoot" -> obj.merkleRoot, + "size" -> obj.size, "height" -> obj.height, + "version" -> obj.version, "time" -> obj.time, + "medianTime" -> obj.medianTime, + "nonce" -> obj.nonce, + "bits" -> obj.bits, + "chainwork" -> obj.chainwork, + "difficulty" -> obj.difficulty, + "extractionMethod" -> obj.extractionMethod.entryName, "filter" -> filterMaybe ) } diff --git a/server/app/controllers/BlocksController.scala b/server/app/controllers/BlocksController.scala index 6cf8d9ea..5d661c36 100644 --- a/server/app/controllers/BlocksController.scala +++ b/server/app/controllers/BlocksController.scala @@ -4,6 +4,7 @@ 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.xsn.explorer.models.LightWalletTransaction +import com.xsn.explorer.models.persisted.BlockHeader import com.xsn.explorer.models.values.Height import com.xsn.explorer.services.{BlockService, TransactionService} import controllers.common.{Codecs, MyJsonController, MyJsonControllerComponents} @@ -26,6 +27,7 @@ class BlocksController @Inject()( } def getBlockHeaders(lastSeenHash: Option[String], limit: Int, orderingCondition: String) = public { _ => + implicit val codec: Writes[BlockHeader] = BlockHeader.partialWrites blockService .getBlockHeaders(Limit(limit), lastSeenHash, orderingCondition) .toFutureOr @@ -47,6 +49,7 @@ class BlocksController @Inject()( * retrieve the blockHeader by blockhash. */ def getBlockHeader(query: String, includeFilter: Boolean) = public { _ => + implicit val codec: Writes[BlockHeader] = BlockHeader.completeWrites val (cache, resultF) = Try(query.toInt) .map(Height.apply) .map { value => diff --git a/server/test/com/xsn/explorer/data/BlockPostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/BlockPostgresDataHandlerSpec.scala index d2465ce5..84b59525 100644 --- a/server/test/com/xsn/explorer/data/BlockPostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/BlockPostgresDataHandlerSpec.scala @@ -318,10 +318,19 @@ class BlockPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAn private def matches(expected: BlockHeader, result: BlockHeader) = { result.hash mustEqual expected.hash + result.tposContract mustEqual expected.tposContract + result.nextBlockhash mustEqual expected.nextBlockhash result.previousBlockhash mustEqual expected.previousBlockhash result.merkleRoot mustEqual expected.merkleRoot + result.size mustEqual expected.size result.height mustEqual expected.height + result.version mustEqual expected.version + result.medianTime mustEqual expected.medianTime result.time mustEqual expected.time + result.bits mustEqual expected.bits + result.chainwork mustEqual expected.chainwork + result.difficulty mustEqual expected.difficulty + result.nonce mustEqual expected.nonce (expected, result) match { case (e: BlockHeader.HasFilter, r: BlockHeader.HasFilter) => matchFilter(e.filter, r.filter) diff --git a/server/test/controllers/BlocksControllerSpec.scala b/server/test/controllers/BlocksControllerSpec.scala index 677c4ae7..4c9a4fef 100644 --- a/server/test/controllers/BlocksControllerSpec.scala +++ b/server/test/controllers/BlocksControllerSpec.scala @@ -508,13 +508,21 @@ class BlocksControllerSpec extends MyAPISpec { val block = expected (jsonBlockHeader \ "hash").as[Blockhash] mustEqual block.hash + (jsonBlockHeader \ "size").as[Size] mustEqual block.size + (jsonBlockHeader \ "bits").as[String] mustEqual block.bits + (jsonBlockHeader \ "chainwork").as[String] mustEqual block.chainwork + (jsonBlockHeader \ "difficulty").as[BigDecimal] mustEqual block.difficulty (jsonBlockHeader \ "height").as[Height] mustEqual block.height + (jsonBlockHeader \ "medianTime").as[Long] mustEqual block.medianTime (jsonBlockHeader \ "time").as[Long] mustEqual block.time (jsonBlockHeader \ "merkleRoot").as[Blockhash] mustEqual block.merkleRoot + (jsonBlockHeader \ "version").as[Long] mustEqual block.version + (jsonBlockHeader \ "nonce").as[Int] mustEqual block.nonce (jsonBlockHeader \ "previousBlockhash").asOpt[Blockhash] mustEqual block.previousBlockhash + (jsonBlockHeader \ "nextBlockhash").asOpt[Blockhash] mustEqual block.nextBlockhash block match { - case BlockHeader.HasFilter(_, _, _, _, _, filter) => { + case BlockHeader.HasFilter(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, filter) => { val jsonFilter = (jsonBlockHeader \ "filter").as[JsValue] (jsonFilter \ "n").as[Int] mustEqual filter.n @@ -522,7 +530,7 @@ class BlocksControllerSpec extends MyAPISpec { (jsonFilter \ "p").as[Int] mustEqual filter.p (jsonFilter \ "hex").as[String] mustEqual filter.hex.string } - case BlockHeader.Simple(_, _, _, _, _) => () + case BlockHeader.Simple(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => () } }