Browse Source

server: Add support for retrieving PoW blocks

scalafmt-draft
Alexis Hernandez 7 years ago
parent
commit
8ecc447f22
  1. 9
      server/app/com/xsn/explorer/errors/blockErrors.scala
  2. 9
      server/app/com/xsn/explorer/models/BlockRewards.scala
  3. 15
      server/app/com/xsn/explorer/models/blockRewards.scala
  4. 45
      server/app/com/xsn/explorer/services/BlockService.scala
  5. 12
      server/app/com/xsn/explorer/services/logic/BlockLogic.scala
  6. 6
      server/app/com/xsn/explorer/services/logic/TransactionLogic.scala
  7. 1
      server/conf/messages
  8. 82
      server/test/controllers/BlocksControllerSpec.scala

9
server/app/com/xsn/explorer/errors/blockErrors.scala

@ -22,3 +22,12 @@ case object BlockNotFoundError extends BlockError with InputValidationError {
List(error)
}
}
case object TPoSBlockNotSupportedError extends BlockError with InputValidationError {
override def toPublicErrorList(messagesApi: MessagesApi)(implicit lang: Lang): List[PublicError] = {
val message = messagesApi("error.block.tposUnsupported")
val error = FieldValidationError("blockhash", message)
List(error)
}
}

9
server/app/com/xsn/explorer/models/BlockRewards.scala

@ -1,9 +0,0 @@
package com.xsn.explorer.models
import play.api.libs.json.{Json, Writes}
case class BlockRewards(coinstake: BlockReward, masternode: Option[BlockReward])
object BlockRewards {
implicit val writes: Writes[BlockRewards] = Json.writes[BlockRewards]
}

15
server/app/com/xsn/explorer/models/blockRewards.scala

@ -0,0 +1,15 @@
package com.xsn.explorer.models
import play.api.libs.json.{Json, Writes}
sealed trait BlockRewards
object BlockRewards {
implicit val writes: Writes[BlockRewards] = Writes[BlockRewards] {
case r: PoWBlockRewards => Json.writes[PoWBlockRewards].writes(r)
case r: PoSBlockRewards => Json.writes[PoSBlockRewards].writes(r)
}
}
case class PoWBlockRewards(reward: BlockReward) extends BlockRewards
case class PoSBlockRewards(coinstake: BlockReward, masternode: Option[BlockReward]) extends BlockRewards

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

@ -4,11 +4,12 @@ import javax.inject.Inject
import com.alexitc.playsonify.core.FutureApplicationResult
import com.alexitc.playsonify.core.FutureOr.Implicits.{FutureOps, OrOps}
import com.xsn.explorer.errors.BlockNotFoundError
import com.xsn.explorer.errors.{BlockNotFoundError, TPoSBlockNotSupportedError}
import com.xsn.explorer.models._
import com.xsn.explorer.services.logic.{BlockLogic, TransactionLogic}
import org.scalactic.Bad
import scala.concurrent.ExecutionContext
import scala.concurrent.{ExecutionContext, Future}
class BlockService @Inject() (
xsnService: XSNService,
@ -25,6 +26,36 @@ class BlockService @Inject() (
.getBlock(blockhash)
.toFutureOr
rewards <- getBlockRewards(block).toFutureOr
} yield BlockDetails(block, rewards)
result.toFuture
}
private def getBlockRewards(block: Block): FutureApplicationResult[BlockRewards] = {
if (block.isPoW) {
getPoWBlockRewards(block)
} else if (block.isPoS) {
getPoSBlockRewards(block)
} else {
getTPoSBlockRewards(block)
}
}
private def getPoWBlockRewards(block: Block): FutureApplicationResult[PoWBlockRewards] = {
val result = for {
txid <- blockLogic.getPoWTransactionId(block).toFutureOr
// TODO: handle tx not found
tx <- xsnService.getTransaction(txid).toFutureOr
vout <- transactionLogic.getVOUT(0, tx, BlockNotFoundError).toFutureOr
address <- transactionLogic.getAddress(vout, BlockNotFoundError).toFutureOr
} yield PoWBlockRewards(BlockReward(address, vout.value))
result.toFuture
}
private def getPoSBlockRewards(block: Block): FutureApplicationResult[PoSBlockRewards] = {
val result = for {
coinstakeTxId <- blockLogic
.getCoinstakeTransactionId(block)
.toFutureOr
@ -47,10 +78,16 @@ class BlockService @Inject() (
.toFutureOr
rewards <- blockLogic
.getRewards(coinstakeTx, coinstakeAddress, previousToCoinstakeVOUT.value)
.getPoSRewards(coinstakeTx, coinstakeAddress, previousToCoinstakeVOUT.value)
.toFutureOr
} yield BlockDetails(block, rewards)
} yield rewards
result.toFuture
}
// TODO: Complete it
private def getTPoSBlockRewards(block: Block): FutureApplicationResult[BlockRewards] = {
val result = Bad(TPoSBlockNotSupportedError).accumulating
Future.successful(result)
}
}

12
server/app/com/xsn/explorer/services/logic/BlockLogic.scala

@ -12,6 +12,12 @@ class BlockLogic {
Or.from(maybe, One(BlockhashFormatError))
}
def getPoWTransactionId(block: Block): ApplicationResult[TransactionId] = {
val maybe = block.transactions.headOption
Or.from(maybe, One(BlockNotFoundError))
}
/**
* Get the coinstake transaction id for the given block.
*
@ -43,10 +49,10 @@ class BlockLogic {
* Sometimes there could be rounding errors, for example, when the input is not exactly divisible by 2,
* we return 0 in that case because the reward could be negative.
*/
def getRewards(
def getPoSRewards(
coinstakeTx: Transaction,
coinstakeAddress: Address,
coinstakeInput: BigDecimal): ApplicationResult[BlockRewards] = {
coinstakeInput: BigDecimal): ApplicationResult[PoSBlockRewards] = {
// first vout is empty, useless
val coinstakeVOUT = coinstakeTx.vout.drop(1)
@ -69,7 +75,7 @@ class BlockLogic {
)
}
Good(BlockRewards(coinstakeReward, masternodeRewardMaybe))
Good(PoSBlockRewards(coinstakeReward, masternodeRewardMaybe))
} else {
Bad(BlockNotFoundError).accumulating
}

6
server/app/com/xsn/explorer/services/logic/TransactionLogic.scala

@ -18,7 +18,11 @@ class TransactionLogic {
}
def getVOUT(vin: TransactionVIN, previousTX: Transaction, error: ApplicationError): ApplicationResult[TransactionVOUT] = {
val maybe = previousTX.vout.find(_.n == vin.voutIndex)
getVOUT(vin.voutIndex, previousTX, error)
}
def getVOUT(index: Int, previousTX: Transaction, error: ApplicationError): ApplicationResult[TransactionVOUT] = {
val maybe = previousTX.vout.find(_.n == index)
Or.from(maybe, One(error))
}
}

1
server/conf/messages

@ -9,3 +9,4 @@ error.address.format=Invalid address format
error.block.format=Invalid blockhash format
error.block.notFound=Block not found
error.block.tposUnsupported=TPoS Blocks are not supported at the moment

82
server/test/controllers/BlocksControllerSpec.scala

@ -75,10 +75,34 @@ class BlocksControllerSpec extends MyAPISpec {
)
)
// TPoS
val tposBlock = posBlock.copy(
hash = Blockhash.from("c6944a33e3e03eb0ccd350f1fc2d6e5f3bd1411e1efddc0990aa3243663b41b7").get,
tposContract = Some("7f2b5f25b0ae24a417633e4214827f930a69802c1c43d1fb2ff7b7075b2d1701"))
// PoW
val powBlock = posBlock.copy(
hash = Blockhash.from("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8").get,
transactions = List(
TransactionId.from("67aa0bd8b9297ca6ee25a1e5c2e3a8dbbcc1e20eab76b6d1bdf9d69f8a5356b8").get
),
height = Height(2)
)
val powBlockPreviousTx = createTx(
id = TransactionId.from("67aa0bd8b9297ca6ee25a1e5c2e3a8dbbcc1e20eab76b6d1bdf9d69f8a5356b8").get,
vin = None,
vout = List(
TransactionVOUT(BigDecimal("76500000.00000000"), 0, "pubkey", Some(Address.from("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9").get))
)
)
val customXSNService = new DummyXSNService {
val blocks = Map(
posBlock.hash -> posBlock,
posBlockRoundingError.hash -> posBlockRoundingError
posBlockRoundingError.hash -> posBlockRoundingError,
tposBlock.hash -> tposBlock,
powBlock.hash -> powBlock
)
override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = {
@ -91,12 +115,12 @@ class BlocksControllerSpec extends MyAPISpec {
Future.successful(result)
}
val txs = Map(
posBlockCoinstakeTx.id -> posBlockCoinstakeTx,
posBlockCoinstakeTxInput.id -> posBlockCoinstakeTxInput,
posBlockRoundingErrorCoinstakeTx.id -> posBlockRoundingErrorCoinstakeTx,
posBlockRoundingErrorCoinstakeTxInput.id -> posBlockRoundingErrorCoinstakeTxInput
posBlockRoundingErrorCoinstakeTxInput.id -> posBlockRoundingErrorCoinstakeTxInput,
powBlockPreviousTx.id -> powBlockPreviousTx
)
override def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction] = {
@ -184,6 +208,36 @@ class BlocksControllerSpec extends MyAPISpec {
jsonMasternode.isEmpty mustEqual true
}
"retrieve a PoW block" in {
val block = powBlock
val response = GET(url(block.hash.string))
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 jsonReward = (jsonRewards \ "reward").as[JsValue]
(jsonReward \ "address").as[String] mustEqual "XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9"
(jsonReward \ "value").as[BigDecimal] mustEqual BigDecimal("76500000")
}
"fail on the wrong blockhash format" in {
val response = GET(url("000125c06cedf38b07bff174bdb61027935dbcb34831d28cff40bedb519d5"))
@ -215,9 +269,29 @@ class BlocksControllerSpec extends MyAPISpec {
(error \ "field").as[String] mustEqual "blockhash"
(error \ "message").as[String].nonEmpty mustEqual true
}
"fail on TPoS block" in {
val response = GET(url("c6944a33e3e03eb0ccd350f1fc2d6e5f3bd1411e1efddc0990aa3243663b41b7"))
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
}
}
private def createTx(id: TransactionId, vin: TransactionVIN, vout: List[TransactionVOUT]): Transaction = {
createTx(id, Some(vin), vout)
}
private def createTx(id: TransactionId, vin: Option[TransactionVIN], vout: List[TransactionVOUT]): Transaction = {
Transaction(
id = id,
size = Size(234),
@ -225,7 +299,7 @@ class BlocksControllerSpec extends MyAPISpec {
time = 1520318120,
blocktime = 1520318120,
confirmations = Confirmations(1950),
vin = Some(vin),
vin = vin,
vout = vout
)
}

Loading…
Cancel
Save