From 1972ae8ae872640aae7ef2648c864467d8fb5a4d Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Tue, 3 Apr 2018 00:24:49 -0500 Subject: [PATCH] server: Add support for TPoS blocks with coinsplit --- .../xsn/explorer/services/BlockService.scala | 36 +++++-- .../explorer/services/logic/BlockLogic.scala | 72 +++++++------- .../xsn/explorer/helpers/BlockLoader.scala | 10 +- .../explorer/helpers/TransactionLoader.scala | 10 +- .../services/XSNServiceRPCImplSpec.scala | 52 +++++----- .../controllers/BlocksControllerSpec.scala | 50 +++++++++- ...dc58ce14d541a65763834247ef958aa3b4d665ef9c | 21 +++++ ...207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8 | 69 ++++++++++++++ ...9e224e428d7904e54f4c695433829bbde6ef99ea44 | 94 +++++++++++++++++++ 9 files changed, 332 insertions(+), 82 deletions(-) create mode 100644 server/test/resources/blocks/a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c create mode 100644 server/test/resources/transactions/99c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8 create mode 100644 server/test/resources/transactions/9f2cef5c3f94197247d0109e224e428d7904e54f4c695433829bbde6ef99ea44 diff --git a/server/app/com/xsn/explorer/services/BlockService.scala b/server/app/com/xsn/explorer/services/BlockService.scala index bfdd4c8..3fcc55f 100644 --- a/server/app/com/xsn/explorer/services/BlockService.scala +++ b/server/app/com/xsn/explorer/services/BlockService.scala @@ -6,10 +6,11 @@ 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.models._ -import com.xsn.explorer.models.rpc.Block +import com.xsn.explorer.models.rpc.{Block, TransactionVIN} import com.xsn.explorer.services.logic.{BlockLogic, TransactionLogic} +import org.scalactic.Good -import scala.concurrent.ExecutionContext +import scala.concurrent.{ExecutionContext, Future} class BlockService @Inject() ( xsnService: XSNService, @@ -106,7 +107,6 @@ class BlockService @Inject() ( result.toFuture } - // TODO: Handle blocks with coin split private def getTPoSBlockRewards(block: Block): FutureApplicationResult[BlockRewards] = { val result = for { coinstakeTxId <- blockLogic @@ -119,12 +119,7 @@ class BlockService @Inject() ( .getVIN(coinstakeTx, BlockNotFoundError) .toFutureOr - previousToCoinstakeTx <- xsnService - .getTransaction(coinstakeTxVIN.txid) - .toFutureOr - previousToCoinstakeVOUT <- transactionLogic - .getVOUT(coinstakeTxVIN, previousToCoinstakeTx, BlockNotFoundError) - .toFutureOr + coinstakeInput <- getCoinstakeInput(coinstakeTxVIN).toFutureOr tposTxId <- blockLogic .getTPoSTransactionId(block) @@ -140,10 +135,31 @@ class BlockService @Inject() ( (ownerAddress, merchantAddress) = addresses rewards <- blockLogic - .getTPoSRewards(coinstakeTx, ownerAddress, merchantAddress, previousToCoinstakeVOUT.value) + .getTPoSRewards(coinstakeTx, ownerAddress, merchantAddress, coinstakeInput) .toFutureOr } yield rewards result.toFuture } + + private def getCoinstakeInput(coinstakeTxVIN: TransactionVIN): FutureApplicationResult[BigDecimal] = { + def loadFromTx = { + val result = for { + previousToCoinstakeTx <- xsnService + .getTransaction(coinstakeTxVIN.txid) + .toFutureOr + previousToCoinstakeVOUT <- transactionLogic + .getVOUT(coinstakeTxVIN, previousToCoinstakeTx, BlockNotFoundError) + .toFutureOr + } yield previousToCoinstakeVOUT.value + + result.toFuture + } + + coinstakeTxVIN + .value + .map(Good(_)) + .map(Future.successful) + .getOrElse(loadFromTx) + } } diff --git a/server/app/com/xsn/explorer/services/logic/BlockLogic.scala b/server/app/com/xsn/explorer/services/logic/BlockLogic.scala index 8e3f833..1b7ae01 100644 --- a/server/app/com/xsn/explorer/services/logic/BlockLogic.scala +++ b/server/app/com/xsn/explorer/services/logic/BlockLogic.scala @@ -98,47 +98,49 @@ class BlockLogic { } } - // TODO: Complete it for coin split def getTPoSRewards( coinstakeTx: Transaction, owner: Address, merchant: Address, coinstakeInput: BigDecimal): ApplicationResult[TPoSBlockRewards] = { - // first vout is empty, useless - val coinstakeVOUT = coinstakeTx.vout.drop(1) - - // TODO: We can probably have upto 4 outputs - if (coinstakeVOUT.size >= 1 && coinstakeVOUT.size <= 3) { - val ownerValue = coinstakeVOUT - .filter(_.address contains owner) - .map(_.value) - .sum - - val ownerReward = BlockReward( - owner, - (ownerValue - coinstakeInput) max 0) - - // merchant - val merchantValue = coinstakeVOUT.filter(_.address contains merchant).map(_.value).sum - val merchantReward = BlockReward(merchant, merchantValue) - - // master node - val masternodeRewardOUT = coinstakeVOUT.filterNot { out => - out.address.contains(owner) || - out.address.contains(merchant) - } - val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.address).headOption - val masternodeRewardMaybe = masternodeAddressMaybe.map { masternodeAddress => - BlockReward( - masternodeAddress, - masternodeRewardOUT.filter(_.address contains masternodeAddress).map(_.value).sum - ) - } - - Good(TPoSBlockRewards(ownerReward, merchantReward, masternodeRewardMaybe)) - } else { - Bad(BlockNotFoundError).accumulating + /** + * While we expected the following + * - 1st output is empty and it is removed. + * - 3 outputs, normal TPoS + * - 4 outputs, coin split TPoS + * + * In order to display partial solutions, we will just filter by the + * addresses to get the rewards. + */ + val coinstakeVOUT = coinstakeTx.vout + + val ownerValue = coinstakeVOUT + .filter(_.address contains owner) + .map(_.value) + .sum + + val ownerReward = BlockReward( + owner, + (ownerValue - coinstakeInput) max 0) + + // merchant + val merchantValue = coinstakeVOUT.filter(_.address contains merchant).map(_.value).sum + val merchantReward = BlockReward(merchant, merchantValue) + + // master node + val masternodeRewardOUT = coinstakeVOUT.filterNot { out => + out.address.contains(owner) || + out.address.contains(merchant) } + val masternodeAddressMaybe = masternodeRewardOUT.flatMap(_.address).headOption + val masternodeRewardMaybe = masternodeAddressMaybe.map { masternodeAddress => + BlockReward( + masternodeAddress, + masternodeRewardOUT.filter(_.address contains masternodeAddress).map(_.value).sum + ) + } + + Good(TPoSBlockRewards(ownerReward, merchantReward, masternodeRewardMaybe)) } } diff --git a/server/test/com/xsn/explorer/helpers/BlockLoader.scala b/server/test/com/xsn/explorer/helpers/BlockLoader.scala index 6608eff..80aa665 100644 --- a/server/test/com/xsn/explorer/helpers/BlockLoader.scala +++ b/server/test/com/xsn/explorer/helpers/BlockLoader.scala @@ -12,8 +12,12 @@ object BlockLoader { } def json(blockhash: String): JsValue = { - val resource = s"$BasePath/$blockhash" - val json = scala.io.Source.fromResource(resource).getLines().mkString("\n") - Json.parse(json) + try { + val resource = s"$BasePath/$blockhash" + val json = scala.io.Source.fromResource(resource).getLines().mkString("\n") + Json.parse(json) + } catch { + case _ => throw new RuntimeException(s"Block $blockhash not found") + } } } diff --git a/server/test/com/xsn/explorer/helpers/TransactionLoader.scala b/server/test/com/xsn/explorer/helpers/TransactionLoader.scala index e9d1b67..12e5dfe 100644 --- a/server/test/com/xsn/explorer/helpers/TransactionLoader.scala +++ b/server/test/com/xsn/explorer/helpers/TransactionLoader.scala @@ -12,8 +12,12 @@ object TransactionLoader { } def json(txid: String): JsValue = { - val resource = s"$BasePath/$txid" - val json = scala.io.Source.fromResource(resource).getLines().mkString("\n") - Json.parse(json) + try { + val resource = s"$BasePath/$txid" + val json = scala.io.Source.fromResource(resource).getLines().mkString("\n") + Json.parse(json) + } catch { + case _ => throw new RuntimeException(s"Transaction $txid not found") + } } } diff --git a/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala b/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala index f984ce8..5113661 100644 --- a/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala +++ b/server/test/com/xsn/explorer/services/XSNServiceRPCImplSpec.scala @@ -2,7 +2,7 @@ package com.xsn.explorer.services import com.xsn.explorer.config.RPCConfig import com.xsn.explorer.errors._ -import com.xsn.explorer.helpers.{DataHelper, Executors, TransactionLoader} +import com.xsn.explorer.helpers.{BlockLoader, DataHelper, Executors, TransactionLoader} import com.xsn.explorer.models.{Address, Blockhash} import org.mockito.ArgumentMatchers._ import org.mockito.Mockito._ @@ -257,34 +257,8 @@ class XSNServiceRPCImplSpec extends WordSpec with MustMatchers with ScalaFutures "getBlock" should { "return a block" in { - val responseBody = - """ - |{ - | "result": { - | "hash": "b72dd1655408e9307ef5874be20422ee71029333283e2360975bc6073bdb2b81", - | "confirmations": 11163, - | "size": 478, - | "height": 809, - | "version": 536870912, - | "merkleroot": "598cc6ba8238d87641b0dbd02485b7d635b5417429df3145c98c3ff8779ab4b8", - | "tx": [ - | "7f12adbb63d443502cf151c76946d5faa0b1c662a5d67afc7da085c74e06f1ce", - | "0834641a7d30d8a2d2b451617599670445ee94ed7736e146c13be260c576c641" - | ], - | "time": 1520318120, - | "mediantime": 1520318054, - | "nonce": 0, - | "bits": "1d011212", - | "difficulty": 0.9340526210769362, - | "chainwork": "00000000000000000000000000000000000000000000000000000084c71ff420", - | "previousblockhash": "9490ce5d14bb5e79a790ddede03fc3a9bde3f7156f34a57ae3ceb56ae7426c14", - | "nextblockhash": "f6e3199c241131e79640fe027a6ef993c02b3520c3d4ba08cd67abfbb98ec07e" - | }, - | "error": null, - | "id": null - |} - |""".stripMargin - + val block = BlockLoader.json("b72dd1655408e9307ef5874be20422ee71029333283e2360975bc6073bdb2b81") + val responseBody = createRPCSuccessfulResponse(block) val blockhash = Blockhash.from("b72dd1655408e9307ef5874be20422ee71029333283e2360975bc6073bdb2b81").get val json = Json.parse(responseBody) @@ -302,6 +276,26 @@ class XSNServiceRPCImplSpec extends WordSpec with MustMatchers with ScalaFutures } } + "return a TPoS block" in { + val block = BlockLoader.json("a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c") + val responseBody = createRPCSuccessfulResponse(block) + val blockhash = Blockhash.from("a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c").get + + val json = Json.parse(responseBody) + + when(response.status).thenReturn(200) + when(response.json).thenReturn(json) + when(request.post[String](anyString())(any())).thenReturn(Future.successful(response)) + + whenReady(service.getBlock(blockhash)) { result => + result.isGood mustEqual true + + val block = result.get + block.hash.string mustEqual blockhash.string + block.transactions.size mustEqual 2 + } + } + "fail on unknown block" in { val responseBody = """{"result":null,"error":{"code":-5,"message":"Block not found"},"id":null}""" diff --git a/server/test/controllers/BlocksControllerSpec.scala b/server/test/controllers/BlocksControllerSpec.scala index 1bb85d5..f497681 100644 --- a/server/test/controllers/BlocksControllerSpec.scala +++ b/server/test/controllers/BlocksControllerSpec.scala @@ -33,6 +33,10 @@ class BlocksControllerSpec extends MyAPISpec { val tposBlockCoinstakeTx = TransactionLoader.get("8c7feafc18576b89bf87faf8aa89feaac1a3fad7d5da77d1fe773219a0e9d864") val tposBlockCoinstakeTxInput = TransactionLoader.get("9ecf10916467dccc8c8f3a87d869dc5aceb57d5d1c2117036fe60f31369a284e") + val tposBlock2 = BlockLoader.get("a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c") + val tposBlock2ContractTx = TransactionLoader.get(tposBlock2.tposContract.get.string) + val tposBlock2CoinstakeTx = TransactionLoader.get(tposBlock2.transactions(1).string) + // PoW val powBlock = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") val powBlockPreviousTx = TransactionLoader.get("67aa0bd8b9297ca6ee25a1e5c2e3a8dbbcc1e20eab76b6d1bdf9d69f8a5356b8") @@ -42,6 +46,7 @@ class BlocksControllerSpec extends MyAPISpec { posBlock.hash -> posBlock, posBlockRoundingError.hash -> posBlockRoundingError, tposBlock.hash -> tposBlock, + tposBlock2.hash -> tposBlock2, powBlock.hash -> powBlock ) @@ -63,7 +68,9 @@ class BlocksControllerSpec extends MyAPISpec { powBlockPreviousTx.id -> powBlockPreviousTx, tposBlockContractTx.id -> tposBlockContractTx, tposBlockCoinstakeTx.id -> tposBlockCoinstakeTx, - tposBlockCoinstakeTxInput.id -> tposBlockCoinstakeTxInput + tposBlockCoinstakeTxInput.id -> tposBlockCoinstakeTxInput, + tposBlock2CoinstakeTx.id -> tposBlock2CoinstakeTx, + tposBlock2ContractTx.id -> tposBlock2ContractTx ) override def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction] = { @@ -183,7 +190,7 @@ class BlocksControllerSpec extends MyAPISpec { "retrieve TPoS block" in { val block = tposBlock - val response = GET(url("19f320185015d146237efe757852b21c5e08b88b2f4de9d3fa9517d8463e472b")) + val response = GET(url(block.hash.string)) status(response) mustEqual OK @@ -221,6 +228,45 @@ class BlocksControllerSpec extends MyAPISpec { } + "retrieve TPoS block with coinsplit" in { + val block = tposBlock2 + 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 + (jsonBlock \ "tposContract").as[String] mustEqual block.tposContract.get.string + + val jsonOwner = (jsonRewards \ "owner").as[JsValue] + (jsonOwner \ "address").as[String] mustEqual "Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge" + (jsonOwner \ "value").as[BigDecimal] mustEqual BigDecimal("22.275") + + val jsonMerchant = (jsonRewards \ "merchant").as[JsValue] + (jsonMerchant \ "address").as[String] mustEqual "XbGFpsuhv6AH3gp3dx5eQrAexP5kESh9bY" + (jsonMerchant \ "value").as[BigDecimal] mustEqual BigDecimal("0.225") + + val jsonMasternode = (jsonRewards \ "masternode").as[JsValue] + (jsonMasternode \ "address").as[String] mustEqual "Xc3bKuGzy9grJZxC2ieTgQjjgyTMKSLqSM" + (jsonMasternode \ "value").as[BigDecimal] mustEqual BigDecimal("22.5") + } + "fail on the wrong blockhash format" in { val response = GET(url("000125c06cedf38b07bff174bdb61027935dbcb34831d28cff40bedb519d5")) diff --git a/server/test/resources/blocks/a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c b/server/test/resources/blocks/a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c new file mode 100644 index 0000000..a5fbb04 --- /dev/null +++ b/server/test/resources/blocks/a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c @@ -0,0 +1,21 @@ +{ + "hash": "a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c", + "confirmations": 23, + "size": 441, + "height": 37588, + "version": 536870912, + "merkleroot": "13f3f7171cf38573e516afb77c78107859352cc55e6bfd3f6f9a127ef93221e5", + "tx": [ + "c5e1884d89050016a8471ee0239f63b22fc0153111427b17fc9e49b1684663e3", + "9f2cef5c3f94197247d0109e224e428d7904e54f4c695433829bbde6ef99ea44" + ], + "time": 1522677340, + "mediantime": 1522676874, + "nonce": 0, + "bits": "1c034b3c", + "difficulty": 77.71860581466451, + "chainwork": "000000000000000000000000000000000000000000000000000f8121abc522cc", + "tposcontract": "99c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8", + "previousblockhash": "be885c982c30a1362f92085c35b8249f516f3e907f17b4708d2f418bed8030a4", + "nextblockhash": "a5b60495db041167486f85b652da7312d41d68198b81ca282d4b7f0bca87bd97" +} \ No newline at end of file diff --git a/server/test/resources/transactions/99c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8 b/server/test/resources/transactions/99c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8 new file mode 100644 index 0000000..afc3e5d --- /dev/null +++ b/server/test/resources/transactions/99c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8 @@ -0,0 +1,69 @@ +{ + "hex": "0100000001e4351d959235468eb25b4c32b129e2d2a2fdf09fd72e7172f46822e6e21e6279000000006a4730440220717ffda2fc31e25849b7b6216d777c50135ae2c24f81ea74c63230c00b34772702203a35a8a3a15493b5de5d68d3586fb090f9bed63bb20078b2ee868a61d3c9cb9b012102cf95793cb0145551c9a6572e3eb2c3bcf524298b4f217065b771e06925e30291feffffff030000000000000000496a22587535556b67524c385952716f57367545573853784d4c446b4a77626a4656666765225862474670737568763641483367703364783565517241657850356b455368396259016300e1f505000000001976a914c9ba3c497107845ea80dcf64951ced030556384e88accd01164e020000001976a914690b8f4edf066edb2fa3385f0d3e39771d27b6d088ac12370000", + "txid": "99c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8", + "size": 307, + "version": 1, + "locktime": 14098, + "vin": [ + { + "txid": "79621ee2e62268f472712ed79ff0fda2d2e229b1324c5bb28e463592951d35e4", + "vout": 0, + "scriptSig": { + "asm": "30440220717ffda2fc31e25849b7b6216d777c50135ae2c24f81ea74c63230c00b34772702203a35a8a3a15493b5de5d68d3586fb090f9bed63bb20078b2ee868a61d3c9cb9b[ALL] 02cf95793cb0145551c9a6572e3eb2c3bcf524298b4f217065b771e06925e30291", + "hex": "4730440220717ffda2fc31e25849b7b6216d777c50135ae2c24f81ea74c63230c00b34772702203a35a8a3a15493b5de5d68d3586fb090f9bed63bb20078b2ee868a61d3c9cb9b012102cf95793cb0145551c9a6572e3eb2c3bcf524298b4f217065b771e06925e30291" + }, + "value": 100.00000000, + "valueSat": 10000000000, + "address": "Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge", + "sequence": 4294967294 + } + ], + "vout": [ + { + "value": 0.00000000, + "valueSat": 0, + "n": 0, + "scriptPubKey": { + "asm": "OP_RETURN 587535556b67524c385952716f57367545573853784d4c446b4a77626a4656666765 5862474670737568763641483367703364783565517241657850356b455368396259 99", + "hex": "6a22587535556b67524c385952716f57367545573853784d4c446b4a77626a4656666765225862474670737568763641483367703364783565517241657850356b4553683962590163", + "type": "nulldata" + } + }, + { + "value": 1.00000000, + "valueSat": 100000000, + "n": 1, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 c9ba3c497107845ea80dcf64951ced030556384e OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914c9ba3c497107845ea80dcf64951ced030556384e88ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": [ + "Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge" + ] + } + }, + { + "value": 98.99999693, + "valueSat": 9899999693, + "n": 2, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 690b8f4edf066edb2fa3385f0d3e39771d27b6d0 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914690b8f4edf066edb2fa3385f0d3e39771d27b6d088ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": [ + "XkGGdarLNe8CtrCLg15QkcrgSm4td8vPoz" + ] + }, + "spentTxId": "1183105915752a7559453775a1e663317bebb9fb35f0880129a78933f2fb2fa1", + "spentIndex": 0, + "spentHeight": 18357 + } + ], + "blockhash": "ad92f0dcea2fdaa357aac6eab00695cf07b487e34113598909f625c24629c981", + "height": 14099, + "confirmations": 23566, + "time": 1521230973, + "blocktime": 1521230973 +} \ No newline at end of file diff --git a/server/test/resources/transactions/9f2cef5c3f94197247d0109e224e428d7904e54f4c695433829bbde6ef99ea44 b/server/test/resources/transactions/9f2cef5c3f94197247d0109e224e428d7904e54f4c695433829bbde6ef99ea44 new file mode 100644 index 0000000..6d35cd6 --- /dev/null +++ b/server/test/resources/transactions/9f2cef5c3f94197247d0109e224e428d7904e54f4c695433829bbde6ef99ea44 @@ -0,0 +1,94 @@ +{ + "hex": "0100000001ad64f9096a74457e97ce20600bb0b80914326f6fdfa1df5a9ada54fdf4cd96f20200000000ffffffff05000000000000000000bcbfd4c81d0000001976a914c9ba3c497107845ea80dcf64951ced030556384e88acdccb0f441d0000001976a914c9ba3c497107845ea80dcf64951ced030556384e88aca0525701000000001976a9140651c16ab25fa5a6b8c0598b6fd18c493ad2570088ac80461c86000000001976a9140ee4c07ec4c237dbc8569d483eb8ab0c3246314188ac00000000", + "txid": "9f2cef5c3f94197247d0109e224e428d7904e54f4c695433829bbde6ef99ea44", + "size": 196, + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": "f296cdf4fd54da9a5adfa1df6f6f321409b8b00b6020ce977e45746a09f964ad", + "vout": 2, + "scriptSig": { + "asm": "", + "hex": "" + }, + "value": 2513.91875000, + "valueSat": 251391875000, + "address": "Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge", + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00000000, + "valueSat": 0, + "n": 0, + "scriptPubKey": { + "asm": "", + "hex": "", + "type": "nonstandard" + } + }, + { + "value": 1279.23437500, + "valueSat": 127923437500, + "n": 1, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 c9ba3c497107845ea80dcf64951ced030556384e OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914c9ba3c497107845ea80dcf64951ced030556384e88ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": [ + "Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge" + ] + } + }, + { + "value": 1256.95937500, + "valueSat": 125695937500, + "n": 2, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 c9ba3c497107845ea80dcf64951ced030556384e OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914c9ba3c497107845ea80dcf64951ced030556384e88ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": [ + "Xu5UkgRL8YRqoW6uEW8SxMLDkJwbjFVfge" + ] + } + }, + { + "value": 0.22500000, + "valueSat": 22500000, + "n": 3, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 0651c16ab25fa5a6b8c0598b6fd18c493ad25700 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9140651c16ab25fa5a6b8c0598b6fd18c493ad2570088ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": [ + "XbGFpsuhv6AH3gp3dx5eQrAexP5kESh9bY" + ] + } + }, + { + "value": 22.50000000, + "valueSat": 2250000000, + "n": 4, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 0ee4c07ec4c237dbc8569d483eb8ab0c32463141 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9140ee4c07ec4c237dbc8569d483eb8ab0c3246314188ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": [ + "Xc3bKuGzy9grJZxC2ieTgQjjgyTMKSLqSM" + ] + } + } + ], + "blockhash": "a3a9fb111a3f85c3d920c2dc58ce14d541a65763834247ef958aa3b4d665ef9c", + "height": 37588, + "confirmations": 48, + "time": 1522677340, + "blocktime": 1522677340 +} \ No newline at end of file