From 6428091b01b4f2c9248f43aced6511a7d815ac7e Mon Sep 17 00:00:00 2001 From: Raul Lopez Date: Tue, 29 Oct 2019 22:28:20 -0600 Subject: [PATCH] server: Make block rewards optional and store masternode rewards --- .../xsn/explorer/data/LedgerDataHandler.scala | 2 +- .../anorm/LedgerPostgresDataHandler.scala | 6 +- .../anorm/dao/BlockRewardPostgresDAO.scala | 37 ++++---- .../data/async/LedgerFutureDataHandler.scala | 2 +- .../com/xsn/explorer/models/RewardType.scala | 1 + .../BlockParallelChunkSynchronizer.scala | 2 +- .../LedgerSynchronizationOps.scala | 12 ++- .../operations/BlockParallelChunkAddOps.scala | 20 ++--- .../services/synchronizer/package.scala | 2 +- .../BlockChunkPostgresRepository.scala | 4 +- .../repository/BlockChunkRepository.scala | 9 +- server/conf/evolutions/default/23.sql | 1 + .../data/LedgerPostgresDataHandlerSpec.scala | 82 +++++++++++++++-- .../TransactionPostgresDataHandlerSpec.scala | 6 +- .../xsn/explorer/helpers/LedgerHelper.scala | 9 +- .../BlockParallelChunkSynchronizerSpec.scala | 90 +++++++++++++------ 16 files changed, 210 insertions(+), 75 deletions(-) diff --git a/server/app/com/xsn/explorer/data/LedgerDataHandler.scala b/server/app/com/xsn/explorer/data/LedgerDataHandler.scala index 71b82b8d..1c835acf 100644 --- a/server/app/com/xsn/explorer/data/LedgerDataHandler.scala +++ b/server/app/com/xsn/explorer/data/LedgerDataHandler.scala @@ -22,7 +22,7 @@ trait LedgerDataHandler[F[_]] { block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): F[Unit] /** diff --git a/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala b/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala index 46d48ae6..395eb226 100644 --- a/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala +++ b/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala @@ -38,7 +38,7 @@ class LedgerPostgresDataHandler @Inject()( block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): ApplicationResult[Unit] = { // the filter is computed outside the transaction to avoid unnecessary locking @@ -80,7 +80,7 @@ class LedgerPostgresDataHandler @Inject()( block: Block.HasTransactions, filter: GolombCodedSet, tposContracts: List[TPoSContract], - rewards: BlockRewards + rewards: Option[BlockRewards] )(implicit conn: Connection): Option[Unit] = { val result = for { @@ -88,7 +88,7 @@ class LedgerPostgresDataHandler @Inject()( _ <- deleteBlockCascade(block.block).orElse(Some(())) _ <- blockPostgresDAO.insert(block.block) _ = blockFilterPostgresDAO.insert(block.hash, filter) - _ = blockRewardDAO.upsert(block.hash, rewards) + _ = rewards.foreach(blockRewardDAO.upsert(block.hash, _)) // batch insert _ <- transactionPostgresDAO.insert(block.transactions, tposContracts) diff --git a/server/app/com/xsn/explorer/data/anorm/dao/BlockRewardPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/BlockRewardPostgresDAO.scala index 67342f02..cbffa7f9 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/BlockRewardPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/BlockRewardPostgresDAO.scala @@ -20,17 +20,20 @@ class BlockRewardPostgresDAO { getRewards(reward).foreach(r => upsert(blockhash, r._1, r._2)) } - def getBy(blockhash: Blockhash)(implicit conn: Connection): BlockRewards = { + def getBy(blockhash: Blockhash)(implicit conn: Connection): Option[BlockRewards] = { val rewards = getRewards(blockhash) rewards match { - case (r: BlockReward, RewardType.PoW) :: Nil => - PoWBlockRewards(r) - case (r: BlockReward, RewardType.PoS) :: Nil => - PoSBlockRewards(r, None) - case (ownerReward: BlockReward, RewardType.TPoSOwner) :: (merchantReward: BlockReward, RewardType.TPoSMerchant) :: Nil => - TPoSBlockRewards(ownerReward, merchantReward, None) - case (merchantReward: BlockReward, RewardType.TPoSMerchant) :: (ownerReward: BlockReward, RewardType.TPoSOwner) :: Nil => - TPoSBlockRewards(ownerReward, merchantReward, None) + case (r, RewardType.PoW) :: Nil => + Some(PoWBlockRewards(r)) + case (r, RewardType.PoS) :: Nil => + Some(PoSBlockRewards(r, None)) + case (r, RewardType.PoS) :: (masternode, RewardType.Masternode) :: Nil => + Some(PoSBlockRewards(r, Some(masternode))) + case (owner, RewardType.TPoSOwner) :: (merchant, RewardType.TPoSMerchant) :: Nil => + Some(TPoSBlockRewards(owner, merchant, None)) + case (masternode, RewardType.Masternode) :: (owner, RewardType.TPoSOwner) :: (merchant, RewardType.TPoSMerchant) :: Nil => + Some(TPoSBlockRewards(owner, merchant, Some(masternode))) + case Nil => None case _ => throw new RuntimeException("Unknown reward type") } @@ -79,6 +82,7 @@ class BlockRewardPostgresDAO { |SELECT address, value, type |FROM block_rewards |WHERE blockhash = {blockhash} + |ORDER BY type """.stripMargin ).on( "blockhash" -> blockhash.toBytesBE.toArray @@ -88,13 +92,16 @@ class BlockRewardPostgresDAO { private def getRewards(reward: BlockRewards): List[(BlockReward, RewardType)] = { reward match { - case r: PoWBlockRewards => List((r.reward, RewardType.PoW)) - case r: PoSBlockRewards => List((r.coinstake, RewardType.PoS)) + case r: PoWBlockRewards => + List((r.reward, RewardType.PoW)) + case r: PoSBlockRewards => + val rewards = List((r.coinstake, RewardType.PoS)) + + rewards ++ r.masternode.map((_, RewardType.Masternode)) case r: TPoSBlockRewards => - List( - (r.owner, RewardType.TPoSOwner), - (r.merchant, RewardType.TPoSMerchant) - ) + val rewards = List((r.owner, RewardType.TPoSOwner), (r.merchant, RewardType.TPoSMerchant)) + + rewards ++ r.masternode.map((_, RewardType.Masternode)) } } } diff --git a/server/app/com/xsn/explorer/data/async/LedgerFutureDataHandler.scala b/server/app/com/xsn/explorer/data/async/LedgerFutureDataHandler.scala index a836b177..16a8d43b 100644 --- a/server/app/com/xsn/explorer/data/async/LedgerFutureDataHandler.scala +++ b/server/app/com/xsn/explorer/data/async/LedgerFutureDataHandler.scala @@ -21,7 +21,7 @@ class LedgerFutureDataHandler @Inject()( block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { retryableFutureDataHandler.retrying { Future { diff --git a/server/app/com/xsn/explorer/models/RewardType.scala b/server/app/com/xsn/explorer/models/RewardType.scala index bbff2d0a..16336b3b 100644 --- a/server/app/com/xsn/explorer/models/RewardType.scala +++ b/server/app/com/xsn/explorer/models/RewardType.scala @@ -10,6 +10,7 @@ object RewardType extends Enum[RewardType] { final case object PoW extends RewardType("PoW") final case object PoS extends RewardType("PoS") + final case object Masternode extends RewardType("MASTERNODE") final case object TPoSOwner extends RewardType("TPoS_OWNER") final case object TPoSMerchant extends RewardType("TPoS_MERCHANT") } diff --git a/server/app/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizer.scala b/server/app/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizer.scala index 4facfe62..657ec53a 100644 --- a/server/app/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizer.scala +++ b/server/app/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizer.scala @@ -30,7 +30,7 @@ class BlockParallelChunkSynchronizer @Inject()( block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val start = System.currentTimeMillis() val result = for { diff --git a/server/app/com/xsn/explorer/services/synchronizer/LedgerSynchronizationOps.scala b/server/app/com/xsn/explorer/services/synchronizer/LedgerSynchronizationOps.scala index 8b3d5e53..f520ccfe 100644 --- a/server/app/com/xsn/explorer/services/synchronizer/LedgerSynchronizationOps.scala +++ b/server/app/com/xsn/explorer/services/synchronizer/LedgerSynchronizationOps.scala @@ -80,7 +80,7 @@ private[synchronizer] class LedgerSynchronizationOps @Inject()( def getBlockData(rpcBlock: rpc.Block[_]): FutureApplicationResult[BlockData] = { val result = for { extractionMethod <- blockService.extractionMethod(rpcBlock).toFutureOr - rewards <- blockService.getBlockRewards(rpcBlock, extractionMethod).toFutureOr + rewards <- getBlockRewards(rpcBlock, extractionMethod).toFutureOr data <- transactionCollectorService.collect(rpcBlock).toFutureOr (transactions, contracts, filterFactory) = data validContracts <- getValidContracts(contracts).toFutureOr @@ -99,6 +99,16 @@ private[synchronizer] class LedgerSynchronizationOps @Inject()( result.toFuture } + private def getBlockRewards( + rpcBlock: rpc.Block[_], + extractionMethod: BlockExtractionMethod + ): FutureApplicationResult[Option[BlockRewards]] = { + blockService.getBlockRewards(rpcBlock, extractionMethod).map { + case Good(reward) => Good(Some(reward)) + case Bad(_) => Good(None) + } + } + private def getValidContracts(contracts: List[TPoSContract]): FutureApplicationResult[List[TPoSContract]] = { val listF = contracts .map { contract => diff --git a/server/app/com/xsn/explorer/services/synchronizer/operations/BlockParallelChunkAddOps.scala b/server/app/com/xsn/explorer/services/synchronizer/operations/BlockParallelChunkAddOps.scala index 2585223c..2f2d0642 100644 --- a/server/app/com/xsn/explorer/services/synchronizer/operations/BlockParallelChunkAddOps.scala +++ b/server/app/com/xsn/explorer/services/synchronizer/operations/BlockParallelChunkAddOps.scala @@ -23,7 +23,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = state match { case BlockSynchronizationState.StoringBlock => storeBlock(block, tposContracts, filterFactory, rewards) @@ -56,7 +56,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val initialState = BlockSynchronizationState.StoringBlock val nextState = BlockSynchronizationState.StoringBlockData @@ -82,7 +82,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.StoringTransactions val filterF = Future { filterFactory() } @@ -112,7 +112,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.StoringOutputs val storeTransactionsF = block.transactions.zipWithIndex.map { @@ -138,7 +138,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.StoringInputs val storeOutputsF = @@ -163,7 +163,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.SpendingOutputs val storeInputsF = @@ -188,7 +188,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.StoringAddressTransactionDetails val spendOutputsF = @@ -213,7 +213,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.UpdatingTPoSContracts val storeDetailsF = @@ -238,7 +238,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.StoringRewards @@ -273,7 +273,7 @@ class BlockParallelChunkAddOps @Inject()(blockChunkRepository: BlockChunkReposit block: Block.HasTransactions, tposContracts: List[TPoSContract], filterFactory: () => GolombCodedSet, - rewards: BlockRewards + rewards: Option[BlockRewards] ): FutureApplicationResult[Unit] = { val nextState = BlockSynchronizationState.UpdatingBalances diff --git a/server/app/com/xsn/explorer/services/synchronizer/package.scala b/server/app/com/xsn/explorer/services/synchronizer/package.scala index d678ac26..0182a3fd 100644 --- a/server/app/com/xsn/explorer/services/synchronizer/package.scala +++ b/server/app/com/xsn/explorer/services/synchronizer/package.scala @@ -8,7 +8,7 @@ import com.xsn.explorer.models.values.TransactionId package object synchronizer { private[synchronizer] type BlockData = - (Block.HasTransactions, List[TPoSContract], () => GolombCodedSet, BlockRewards) + (Block.HasTransactions, List[TPoSContract], () => GolombCodedSet, Option[BlockRewards]) private[synchronizer] val ExcludedTransactions: List[TransactionId] = List("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468").flatMap(TransactionId.from) diff --git a/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkPostgresRepository.scala b/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkPostgresRepository.scala index 1614c132..4959840b 100644 --- a/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkPostgresRepository.scala +++ b/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkPostgresRepository.scala @@ -170,9 +170,9 @@ class BlockChunkPostgresRepository @Inject()( maybe.map(x => Good(x)).getOrElse(throw new RuntimeException(s"Failed to rollback block $blockhash")) } - override def upsertBlockReward(blockhash: Blockhash, reward: BlockRewards): ApplicationResult[Unit] = { + override def upsertBlockReward(blockhash: Blockhash, reward: Option[BlockRewards]): ApplicationResult[Unit] = { withConnection { implicit conn => - blockRewardDAO.upsert(blockhash, reward) + reward.foreach(blockRewardDAO.upsert(blockhash, _)) Good(()) } } diff --git a/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkRepository.scala b/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkRepository.scala index afa737d4..aef9ad5e 100644 --- a/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkRepository.scala +++ b/server/app/com/xsn/explorer/services/synchronizer/repository/BlockChunkRepository.scala @@ -55,7 +55,7 @@ trait BlockChunkRepository[F[_]] { */ def atomicRollback(blockhash: Blockhash): F[Unit] - def upsertBlockReward(blockhash: Blockhash, reward: BlockRewards): F[Unit] + def upsertBlockReward(blockhash: Blockhash, reward: Option[BlockRewards]): F[Unit] } object BlockChunkRepository { @@ -138,8 +138,9 @@ object BlockChunkRepository { blocking.atomicRollback(blockhash) } - override def upsertBlockReward(blockhash: Blockhash, reward: BlockRewards): FutureApplicationResult[Unit] = Future { - blocking.upsertBlockReward(blockhash, reward) - } + override def upsertBlockReward(blockhash: Blockhash, reward: Option[BlockRewards]): FutureApplicationResult[Unit] = + Future { + blocking.upsertBlockReward(blockhash, reward) + } } } diff --git a/server/conf/evolutions/default/23.sql b/server/conf/evolutions/default/23.sql index 64ab8528..084d6aa1 100644 --- a/server/conf/evolutions/default/23.sql +++ b/server/conf/evolutions/default/23.sql @@ -5,6 +5,7 @@ ALTER TYPE BLOCK_SYNCHRONIZATION_STATE ADD VALUE 'STORING_REWARDS'; CREATE TYPE REWARD_TYPE AS ENUM ( 'PoW', 'PoS', + 'MASTERNODE', 'TPoS_OWNER', 'TPoS_MERCHANT' ); diff --git a/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala index 4d61010f..82933ed3 100644 --- a/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala @@ -13,7 +13,7 @@ import org.scalatest.BeforeAndAfter class LedgerPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeAndAfter { private val emptyFilterFactory = () => GolombCodedSet(1, 2, 3, List(new UnsignedByte(0.toByte))) - private val reward = getPoWReward(blockList.head) + private val reward = Some(getPoWReward(blockList.head)) lazy val dataHandler = createLedgerDataHandler(database) before { @@ -64,6 +64,19 @@ class LedgerPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeA ).accumulating } + "store block without reward" in { + val block = blockList.head + + dataHandler.push(block.withTransactions(getTransactions(block)), List.empty, emptyFilterFactory, None) mustEqual Good( + () + ) + + database.withConnection { implicit conn => + val reward = blockRewardPostgresDAO.getBy(block.hash) + reward mustEqual None + } + } + "store PoW block rewards" in { val block = blockList.head val powReward = getPoWReward(block) @@ -71,12 +84,12 @@ class LedgerPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeA .copy(extractionMethod = BlockExtractionMethod.ProofOfWork) .withTransactions(getTransactions(block)) - dataHandler.push(powBlock, List.empty, emptyFilterFactory, powReward) mustEqual Good(()) + dataHandler.push(powBlock, List.empty, emptyFilterFactory, Some(powReward)) mustEqual Good(()) database.withConnection { implicit conn => val reward = blockRewardPostgresDAO.getBy(block.hash) reward match { - case r: PoWBlockRewards => { + case Some(r: PoWBlockRewards) => { r.reward.address mustEqual powReward.reward.address r.reward.value mustEqual powReward.reward.value } @@ -92,14 +105,40 @@ class LedgerPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeA .copy(extractionMethod = BlockExtractionMethod.ProofOfStake) .withTransactions(getTransactions(block)) - dataHandler.push(posBlock, List.empty, emptyFilterFactory, posReward) mustEqual Good(()) + dataHandler.push(posBlock, List.empty, emptyFilterFactory, Some(posReward)) mustEqual Good(()) database.withConnection { implicit conn => val reward = blockRewardPostgresDAO.getBy(block.hash) reward match { - case r: PoSBlockRewards => { + case Some(r: PoSBlockRewards) => { r.coinstake.address mustEqual posReward.coinstake.address r.coinstake.value mustEqual posReward.coinstake.value + + r.masternode.get.address mustEqual posReward.masternode.get.address + r.masternode.get.value mustEqual posReward.masternode.get.value + } + case _ => fail + } + } + } + + "store PoS block rewards without masternode" in { + val block = blockList.head + val posReward = getPoSReward(block).copy(masternode = None) + val posBlock = toPersistedBlock(block) + .copy(extractionMethod = BlockExtractionMethod.ProofOfStake) + .withTransactions(getTransactions(block)) + + dataHandler.push(posBlock, List.empty, emptyFilterFactory, Some(posReward)) mustEqual Good(()) + + database.withConnection { implicit conn => + val reward = blockRewardPostgresDAO.getBy(block.hash) + reward match { + case Some(r: PoSBlockRewards) => { + r.coinstake.address mustEqual posReward.coinstake.address + r.coinstake.value mustEqual posReward.coinstake.value + + r.masternode mustBe None } case _ => fail } @@ -113,17 +152,46 @@ class LedgerPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeA .copy(extractionMethod = BlockExtractionMethod.TrustlessProofOfStake) .withTransactions(getTransactions(block)) - dataHandler.push(tposBlock, List.empty, emptyFilterFactory, tposReward) mustEqual Good(()) + dataHandler.push(tposBlock, List.empty, emptyFilterFactory, Some(tposReward)) mustEqual Good(()) database.withConnection { implicit conn => val reward = blockRewardPostgresDAO.getBy(block.hash) reward match { - case r: TPoSBlockRewards => { + case Some(r: TPoSBlockRewards) => { r.owner.address mustEqual tposReward.owner.address r.owner.value mustEqual tposReward.owner.value r.merchant.address mustEqual tposReward.merchant.address r.merchant.value mustEqual tposReward.merchant.value + + r.masternode.get.address mustEqual tposReward.masternode.get.address + r.masternode.get.value mustEqual tposReward.masternode.get.value + } + case _ => fail + } + } + } + + "store TPoS block rewards without masternode" in { + val block = blockList.head + val tposReward = getTPoSReward(block).copy(masternode = None) + val tposBlock = toPersistedBlock(block) + .copy(extractionMethod = BlockExtractionMethod.TrustlessProofOfStake) + .withTransactions(getTransactions(block)) + + dataHandler.push(tposBlock, List.empty, emptyFilterFactory, Some(tposReward)) mustEqual Good(()) + + database.withConnection { implicit conn => + val reward = blockRewardPostgresDAO.getBy(block.hash) + reward match { + case Some(r: TPoSBlockRewards) => { + r.owner.address mustEqual tposReward.owner.address + r.owner.value mustEqual tposReward.owner.value + + r.merchant.address mustEqual tposReward.merchant.address + r.merchant.value mustEqual tposReward.merchant.value + + r.masternode mustBe None } case _ => fail } diff --git a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala index d9cd212d..0b9725df 100644 --- a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala @@ -293,14 +293,16 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be .map(_._1) val reward = PoWBlockRewards(BlockReward(DataGenerator.randomAddress, 100)) - val result = ledgerDataHandler.push(block.withTransactions(transactions), List.empty, emptyFilterFactory, reward) + val result = + ledgerDataHandler.push(block.withTransactions(transactions), List.empty, emptyFilterFactory, Some(reward)) result.isGood mustEqual true } private def createBlock(block: Block.Canonical, transactions: List[Transaction.HasIO]) = { val reward = PoWBlockRewards(BlockReward(DataGenerator.randomAddress, 100)) - val result = ledgerDataHandler.push(block.withTransactions(transactions), List.empty, emptyFilterFactory, reward) + val result = + ledgerDataHandler.push(block.withTransactions(transactions), List.empty, emptyFilterFactory, Some(reward)) result.isGood mustEqual true } diff --git a/server/test/com/xsn/explorer/helpers/LedgerHelper.scala b/server/test/com/xsn/explorer/helpers/LedgerHelper.scala index a3a8a256..f21d1c43 100644 --- a/server/test/com/xsn/explorer/helpers/LedgerHelper.scala +++ b/server/test/com/xsn/explorer/helpers/LedgerHelper.scala @@ -35,12 +35,17 @@ object LedgerHelper { } def getPoSReward(block: rpc.Block.Canonical): PoSBlockRewards = { - PoSBlockRewards(BlockReward(Address.from(list.head).get, 1000), None) + val reward = BlockReward(Address.from(list.head).get, 1000) + val masternodeReward = BlockReward(Address.from(list.head).get, 250) + + PoSBlockRewards(reward, Some(masternodeReward)) } def getTPoSReward(block: rpc.Block.Canonical): TPoSBlockRewards = { val ownerReward = BlockReward(Address.from(list.head).get, 1000) val merchantReward = BlockReward(Address.from(list(1)).get, 100) - TPoSBlockRewards(ownerReward, merchantReward, None) + val masternodeReward = BlockReward(Address.from(list.head).get, 250) + + TPoSBlockRewards(ownerReward, merchantReward, Some(masternodeReward)) } } diff --git a/server/test/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizerSpec.scala b/server/test/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizerSpec.scala index 59639a36..16d3d49b 100644 --- a/server/test/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizerSpec.scala +++ b/server/test/com/xsn/explorer/services/synchronizer/BlockParallelChunkSynchronizerSpec.scala @@ -76,8 +76,6 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl val blockWithTransactions = Block.HasTransactions(block, List(tx1, tx2, tx3)) - val reward = PoWBlockRewards(BlockReward(DataGenerator.randomAddress, 100)) - val tposContracts = List( DataGenerator.randomTPoSContract(tx1.id, 0), DataGenerator.randomTPoSContract(tx2.id, 0), @@ -87,7 +85,7 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl "sync" should { "sync a block" in { val synchronizer = createSynchronizer() - whenReady(synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, reward)) { result => + whenReady(synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, None)) { result => result must be(Good(())) verifyDatabase(blockWithTransactions, tposContracts, None) } @@ -99,25 +97,54 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl ) val powReward = PoWBlockRewards(BlockReward(DataGenerator.randomAddress, 1000)) val synchronizer = createSynchronizer() - whenReady(synchronizer.sync(powBlock, tposContracts, emptyFilterFactory, powReward)) { result => + whenReady(synchronizer.sync(powBlock, tposContracts, emptyFilterFactory, Some(powReward))) { result => result must be(Good(())) verifyDatabase(powBlock, tposContracts, Some(powReward)) } } "store PoS reward" in { + val posBlock = blockWithTransactions.copy( + block = blockWithTransactions.block.copy(extractionMethod = BlockExtractionMethod.ProofOfStake) + ) + val reward = BlockReward(DataGenerator.randomAddress, 1000) + val masternodeReward = BlockReward(DataGenerator.randomAddress, 250) + val posReward = PoSBlockRewards(reward, Some(masternodeReward)) + val synchronizer = createSynchronizer() + whenReady(synchronizer.sync(posBlock, tposContracts, emptyFilterFactory, Some(posReward))) { result => + result must be(Good(())) + verifyDatabase(posBlock, tposContracts, Some(posReward)) + } + } + + "store PoS reward without masternode" in { val posBlock = blockWithTransactions.copy( block = blockWithTransactions.block.copy(extractionMethod = BlockExtractionMethod.ProofOfStake) ) val posReward = PoSBlockRewards(BlockReward(DataGenerator.randomAddress, 1000), None) val synchronizer = createSynchronizer() - whenReady(synchronizer.sync(posBlock, tposContracts, emptyFilterFactory, posReward)) { result => + whenReady(synchronizer.sync(posBlock, tposContracts, emptyFilterFactory, Some(posReward))) { result => result must be(Good(())) verifyDatabase(posBlock, tposContracts, Some(posReward)) } } "store TPoS reward" in { + val tposBlock = blockWithTransactions.copy( + block = blockWithTransactions.block.copy(extractionMethod = BlockExtractionMethod.TrustlessProofOfStake) + ) + val ownerReward = BlockReward(DataGenerator.randomAddress, 1000) + val merchantReward = BlockReward(DataGenerator.randomAddress, 100) + val masternodeReward = BlockReward(DataGenerator.randomAddress, 250) + val tposReward = TPoSBlockRewards(ownerReward, merchantReward, Some(masternodeReward)) + val synchronizer = createSynchronizer() + whenReady(synchronizer.sync(tposBlock, tposContracts, emptyFilterFactory, Some(tposReward))) { result => + result must be(Good(())) + verifyDatabase(tposBlock, tposContracts, Some(tposReward)) + } + } + + "store TPoS reward without masternode" in { val tposBlock = blockWithTransactions.copy( block = blockWithTransactions.block.copy(extractionMethod = BlockExtractionMethod.TrustlessProofOfStake) ) @@ -125,7 +152,7 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl val merchantReward = BlockReward(DataGenerator.randomAddress, 100) val tposReward = TPoSBlockRewards(ownerReward, merchantReward, None) val synchronizer = createSynchronizer() - whenReady(synchronizer.sync(tposBlock, tposContracts, emptyFilterFactory, tposReward)) { result => + whenReady(synchronizer.sync(tposBlock, tposContracts, emptyFilterFactory, Some(tposReward))) { result => result must be(Good(())) verifyDatabase(tposBlock, tposContracts, Some(tposReward)) } @@ -136,10 +163,10 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl val dao = daoFailingOnceAt(state) val synchronizer = createSynchronizer(dao) intercept[RuntimeException] { - synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, reward).futureValue + synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, None).futureValue } - whenReady(synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, reward)) { result => + whenReady(synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, None)) { result => result must be(Good(())) verifyDatabase(blockWithTransactions, tposContracts, None) } @@ -158,7 +185,7 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl val dao = daoFailingOnceAt(state) val synchronizer = createSynchronizer(dao) intercept[RuntimeException] { - synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, reward).futureValue + synchronizer.sync(blockWithTransactions, tposContracts, emptyFilterFactory, None).futureValue } whenReady(synchronizer.rollback(block.hash)) { result => @@ -210,7 +237,7 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl case Some(r: PoWBlockRewards) => verifyPoWReward(block.hash, r) case Some(r: PoSBlockRewards) => verifyPoSReward(block.hash, r) case Some(r: TPoSBlockRewards) => verifyTPoSReward(block.hash, r) - case _ => succeed + case _ => verifyNoReward(block.hash) } } @@ -218,10 +245,7 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl database.withConnection { implicit conn => val reward = blockRewardPostgresDAO.getBy(blockhash) reward match { - case r: PoWBlockRewards => { - r.reward.address mustEqual powReward.reward.address - r.reward.value mustEqual powReward.reward.value - } + case Some(r: PoWBlockRewards) => verifyReward(r.reward, powReward.reward) case _ => fail } } @@ -231,10 +255,9 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl database.withConnection { implicit conn => val reward = blockRewardPostgresDAO.getBy(blockhash) reward match { - case r: PoSBlockRewards => { - r.coinstake.address mustEqual posReward.coinstake.address - r.coinstake.value mustEqual posReward.coinstake.value - } + case Some(r: PoSBlockRewards) => + verifyReward(r.coinstake, posReward.coinstake) + verifyMasterNodeReward(r.masternode, posReward.masternode) case _ => fail } } @@ -244,15 +267,32 @@ class BlockParallelChunkSynchronizerSpec extends WordSpec with PostgresDataHandl database.withConnection { implicit conn => val reward = blockRewardPostgresDAO.getBy(blockhash) reward match { - case r: TPoSBlockRewards => { - r.owner.address mustEqual tposReward.owner.address - r.owner.value mustEqual tposReward.owner.value - - r.merchant.address mustEqual tposReward.merchant.address - r.merchant.value mustEqual tposReward.merchant.value - } + case Some(r: TPoSBlockRewards) => + verifyReward(r.owner, tposReward.owner) + verifyReward(r.merchant, tposReward.merchant) + verifyMasterNodeReward(r.masternode, tposReward.masternode) case _ => fail } } } + + private def verifyReward(reward: BlockReward, expectedReward: BlockReward) = { + reward.address mustEqual expectedReward.address + reward.value mustEqual expectedReward.value + } + + private def verifyMasterNodeReward(masternode: Option[BlockReward], expectedMasternode: Option[BlockReward]) = { + (masternode, expectedMasternode) match { + case (Some(r1), Some(r2)) => verifyReward(r1, r2) + case (None, None) => succeed + case _ => fail + } + } + + private def verifyNoReward(blockhash: Blockhash) = { + database.withConnection { implicit conn => + val reward = blockRewardPostgresDAO.getBy(blockhash) + reward mustEqual None + } + } }