diff --git a/server/app/com/xsn/explorer/models/Block.scala b/server/app/com/xsn/explorer/models/Block.scala index 14cb582..f3de5d4 100644 --- a/server/app/com/xsn/explorer/models/Block.scala +++ b/server/app/com/xsn/explorer/models/Block.scala @@ -25,7 +25,7 @@ case class Block( bits: String, chainwork: String, difficulty: BigDecimal, - tposContract: Option[String]) { + tposContract: Option[TransactionId]) { /** * Every block until 75 is PoW. @@ -60,7 +60,7 @@ object Block { (__ \ 'bits).read[String] and (__ \ 'chainwork).read[String] and (__ \ 'difficulty).read[BigDecimal] and - (__ \ 'tposcontract).readNullable[String] + (__ \ 'tposcontract).readNullable[TransactionId] builder.apply { (hash, previous, next, root, transactions, confirmations, size, height, version, time, diff --git a/server/app/com/xsn/explorer/models/TransactionVOUT.scala b/server/app/com/xsn/explorer/models/TransactionVOUT.scala index a3a5ae0..1de8850 100644 --- a/server/app/com/xsn/explorer/models/TransactionVOUT.scala +++ b/server/app/com/xsn/explorer/models/TransactionVOUT.scala @@ -1,32 +1,26 @@ package com.xsn.explorer.models +import com.xsn.explorer.models.rpc.ScriptPubKey import play.api.libs.functional.syntax._ -import play.api.libs.json.{JsObject, Reads, __} +import play.api.libs.json.{Reads, __} case class TransactionVOUT( value: BigDecimal, n: Int, - scriptPubKeyType: String, - address: Option[Address]) + scriptPubKey: Option[ScriptPubKey] = None) { + + val address: Option[Address] = scriptPubKey.flatMap(_.addresses.headOption) +} object TransactionVOUT { implicit val reads: Reads[TransactionVOUT] = { val builder = (__ \ 'value).read[BigDecimal] and (__ \ 'n).read[Int] and - (__ \ 'scriptPubKey).read[JsObject].map { json => - val t = (json \ "type").as[String] - val a = (json \ "addresses") - .asOpt[List[String]] - .flatMap(_.headOption) - .flatMap(Address.from) - - (t, a) - } + (__ \ 'scriptPubKey).readNullable[ScriptPubKey] - builder.apply { (value, n, tuple) => - val (scriptType, address) = tuple - TransactionVOUT(value, n, scriptType, address) + builder.apply { (value, n, script) => + TransactionVOUT(value, n, script) } } } diff --git a/server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala b/server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala new file mode 100644 index 0000000..9b7a641 --- /dev/null +++ b/server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala @@ -0,0 +1,24 @@ +package com.xsn.explorer.models.rpc + +import com.xsn.explorer.models.Address +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Reads, __} + +case class ScriptPubKey( + `type`: String, + asm: String, + addresses: List[Address] +) + +object ScriptPubKey { + + implicit val reads: Reads[ScriptPubKey] = { + val builder = (__ \ 'type).read[String] and + (__ \ 'asm).read[String] and + (__ \ 'addresses).readNullable[List[Address]].map(_ getOrElse List.empty) + + builder.apply { (t, asm, addresses) => + ScriptPubKey(t, asm, addresses) + } + } +} diff --git a/server/test/com/xsn/explorer/helpers/DataHelper.scala b/server/test/com/xsn/explorer/helpers/DataHelper.scala new file mode 100644 index 0000000..bb2a542 --- /dev/null +++ b/server/test/com/xsn/explorer/helpers/DataHelper.scala @@ -0,0 +1,23 @@ +package com.xsn.explorer.helpers + +import com.xsn.explorer.models.rpc.ScriptPubKey +import com.xsn.explorer.models.{Address, TransactionId, TransactionVOUT} + +object DataHelper { + + def createAddress(string: String) = Address.from(string).get + + def createTransactionId(string: String) = TransactionId.from(string).get + + def createTransactionVOUT(n: Int, value: BigDecimal, scriptPubKey: ScriptPubKey) = { + TransactionVOUT( + n = n, + value = value, + scriptPubKey = Some(scriptPubKey)) + } + + def createScriptPubKey(scriptType: String, address: Address) = { + ScriptPubKey(scriptType, "", List(address)) + } + +} diff --git a/server/test/controllers/BlocksControllerSpec.scala b/server/test/controllers/BlocksControllerSpec.scala index 15471a5..bf5bff4 100644 --- a/server/test/controllers/BlocksControllerSpec.scala +++ b/server/test/controllers/BlocksControllerSpec.scala @@ -3,7 +3,7 @@ package controllers import com.alexitc.playsonify.PublicErrorRenderer import com.alexitc.playsonify.core.FutureApplicationResult import com.xsn.explorer.errors.{BlockNotFoundError, TransactionNotFoundError} -import com.xsn.explorer.helpers.DummyXSNService +import com.xsn.explorer.helpers.{DataHelper, DummyXSNService} import com.xsn.explorer.models._ import com.xsn.explorer.services.XSNService import controllers.common.MyAPISpec @@ -16,6 +16,8 @@ import scala.concurrent.Future class BlocksControllerSpec extends MyAPISpec { + import DataHelper._ + // PoS block val posBlock = createBlock( hash = Blockhash.from("b72dd1655408e9307ef5874be20422ee71029333283e2360975bc6073bdb2b81").get, @@ -29,10 +31,10 @@ class BlocksControllerSpec extends MyAPISpec { id = TransactionId.from("0834641a7d30d8a2d2b451617599670445ee94ed7736e146c13be260c576c641").get, vin = TransactionVIN(TransactionId.from("585cec5009c8ca19e83e33d282a6a8de65eb2ca007b54d6572167703768967d9").get, 2), vout = List( - TransactionVOUT(BigDecimal("0"), 0, "nonstandard", None), - TransactionVOUT(n = 1, value = BigDecimal("600"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL")), - TransactionVOUT(n = 2, value = BigDecimal("600"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL")), - TransactionVOUT(BigDecimal("10"), 3, "pubkeyhash", Some(Address.from("XnH3bC9NruJ4wnu4Dgi8F3wemmJtcxpKp6").get)) + TransactionVOUT(n = 0, value = BigDecimal("0")), + createTransactionVOUT(1, BigDecimal(600), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))), + createTransactionVOUT(2, BigDecimal(600), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))), + createTransactionVOUT(3, BigDecimal(10), createScriptPubKey("pubkeyhash", createAddress("XnH3bC9NruJ4wnu4Dgi8F3wemmJtcxpKp6"))) ) ) @@ -40,9 +42,9 @@ class BlocksControllerSpec extends MyAPISpec { id = TransactionId.from("585cec5009c8ca19e83e33d282a6a8de65eb2ca007b54d6572167703768967d9").get, vin = TransactionVIN(TransactionId.from("fd74206866fc4ed986d39084eb9f20de6cb324b028693f33d60897ac995fff4f").get, 2), vout = List( - TransactionVOUT(BigDecimal("0"), 0, "nonstandard", None), - TransactionVOUT(BigDecimal("1"), 1, "pubkeyhash", Some(Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL").get)), - TransactionVOUT(BigDecimal("1000"), 2, "pubkeyhash", Some(Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL").get)) + TransactionVOUT(BigDecimal("0"), 0, None), + createTransactionVOUT(1, BigDecimal(1), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))), + createTransactionVOUT(2, BigDecimal(1000), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))) ) ) @@ -59,9 +61,9 @@ class BlocksControllerSpec extends MyAPISpec { id = TransactionId.from("0b761343c7be39116d5429953e0cfbf51bfe83400ab27d61084222451045116c").get, vin = TransactionVIN(TransactionId.from("1860288a5a87c79e617f743af44600e050c28ddb7d929d93d43a9148e2ba6638").get, 1), vout = List( - TransactionVOUT(BigDecimal("0"), 0, "nonstandard", None), - TransactionVOUT(n = 1, value = BigDecimal("292968.74570312"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL")), - TransactionVOUT(n = 2, value = BigDecimal("292968.74570312"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL")) + TransactionVOUT(BigDecimal("0"), 0, None), + createTransactionVOUT(1, BigDecimal("292968.74570312"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))), + createTransactionVOUT(2, BigDecimal("292968.74570312"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))) ) ) @@ -69,16 +71,16 @@ class BlocksControllerSpec extends MyAPISpec { id = TransactionId.from("1860288a5a87c79e617f743af44600e050c28ddb7d929d93d43a9148e2ba6638").get, vin = TransactionVIN(TransactionId.from("ef157f5ec0b3a6cdf669ff799988ee94d9fa2af8adaf2408ae9e34b47310831f").get, 2), vout = List( - TransactionVOUT(BigDecimal("0"), 0, "nonstandard", None), - TransactionVOUT(BigDecimal("585937.49140625"), 1, "pubkeyhash", Some(Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL").get)), - TransactionVOUT(BigDecimal("585937.49140625"), 2, "pubkeyhash", Some(Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL").get)) + TransactionVOUT(BigDecimal("0"), 0, None), + createTransactionVOUT(1, BigDecimal("585937.49140625"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))), + createTransactionVOUT(2, BigDecimal("585937.49140625"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))) ) ) // TPoS val tposBlock = posBlock.copy( hash = Blockhash.from("c6944a33e3e03eb0ccd350f1fc2d6e5f3bd1411e1efddc0990aa3243663b41b7").get, - tposContract = Some("7f2b5f25b0ae24a417633e4214827f930a69802c1c43d1fb2ff7b7075b2d1701")) + tposContract = Some(createTransactionId("7f2b5f25b0ae24a417633e4214827f930a69802c1c43d1fb2ff7b7075b2d1701"))) // PoW val powBlock = posBlock.copy( @@ -93,7 +95,7 @@ class BlocksControllerSpec extends MyAPISpec { id = TransactionId.from("67aa0bd8b9297ca6ee25a1e5c2e3a8dbbcc1e20eab76b6d1bdf9d69f8a5356b8").get, vin = None, vout = List( - TransactionVOUT(BigDecimal("76500000.00000000"), 0, "pubkey", Some(Address.from("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9").get)) + createTransactionVOUT(0, BigDecimal("76500000.00000000"), createScriptPubKey("pubkey", createAddress("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9"))) ) ) diff --git a/server/test/controllers/TransactionsControllerSpec.scala b/server/test/controllers/TransactionsControllerSpec.scala index 709f7c9..7fb214b 100644 --- a/server/test/controllers/TransactionsControllerSpec.scala +++ b/server/test/controllers/TransactionsControllerSpec.scala @@ -3,7 +3,7 @@ package controllers import com.alexitc.playsonify.PublicErrorRenderer import com.alexitc.playsonify.core.FutureApplicationResult import com.xsn.explorer.errors.TransactionNotFoundError -import com.xsn.explorer.helpers.DummyXSNService +import com.xsn.explorer.helpers.{DataHelper, DummyXSNService} import com.xsn.explorer.models._ import com.xsn.explorer.services.XSNService import controllers.common.MyAPISpec @@ -16,6 +16,8 @@ import scala.concurrent.Future class TransactionsControllerSpec extends MyAPISpec { + import DataHelper._ + val coinbaseTx: Transaction = Transaction( id = TransactionId.from("024aba1d535cfe5dd3ea465d46a828a57b00e1df012d7a2d158e0f7484173f7c").get, size = Size(98), @@ -25,7 +27,8 @@ class TransactionsControllerSpec extends MyAPISpec { confirmations = Confirmations(5347), vin = None, vout = List( - TransactionVOUT(n = 0, address = Address.from("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9"), value = 0, scriptPubKeyType = "pubkey")) + createTransactionVOUT(0, BigDecimal(0), createScriptPubKey("pubkey", createAddress("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9"))) + ) ) val nonCoinbaseTx: Transaction = Transaction( @@ -38,8 +41,9 @@ class TransactionsControllerSpec extends MyAPISpec { vin = Some( TransactionVIN(TransactionId.from("585cec5009c8ca19e83e33d282a6a8de65eb2ca007b54d6572167703768967d9").get, 2)), vout = List( - TransactionVOUT(n = 1, value = BigDecimal("1171874.98281250"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL")), - TransactionVOUT(n = 2, value = BigDecimal("1171874.98281250"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))) + createTransactionVOUT(1, BigDecimal("1171874.98281250"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))), + createTransactionVOUT(2, BigDecimal("1171874.98281250"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))) + ) ) val nonCoinbasePreviousTx: Transaction = Transaction( @@ -50,10 +54,11 @@ class TransactionsControllerSpec extends MyAPISpec { blocktime = 1520314409, confirmations = Confirmations(11239), vin = Some( - TransactionVIN(TransactionId.from("fd74206866fc4ed986d39084eb9f20de6cb324b028693f33d60897ac995fff4f").get, 2)), + TransactionVIN(createTransactionId("fd74206866fc4ed986d39084eb9f20de6cb324b028693f33d60897ac995fff4f"), 2)), vout = List( - TransactionVOUT(n = 1, value = BigDecimal("2343749.96562500"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL")), - TransactionVOUT(n = 2, value = BigDecimal("2343749.96562500"), scriptPubKeyType = "pubkeyhash", address = Address.from("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))) + createTransactionVOUT(1, BigDecimal("2343749.96562500"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))), + createTransactionVOUT(2, BigDecimal("2343749.96562500"), createScriptPubKey("pubkeyhash", createAddress("XgEGH3y7RfeKEdn2hkYEvBnrnmGBr7zvjL"))) + ) ) val customXSNService = new DummyXSNService {