diff --git a/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala b/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala index 9342786..bf08e96 100644 --- a/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala +++ b/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala @@ -81,13 +81,12 @@ class DatabasePostgresSeeder @Inject() ( } private def deleteBlockCascade(block: Block)(implicit conn: Connection): Option[Unit] = { + // transactions + val deletedTransactions = transactionPostgresDAO.deleteBy(block.hash) for { // block _ <- blockPostgresDAO.delete(block.hash) - // transactions - deletedTransactions = transactionPostgresDAO.deleteBy(block.hash) - // balances _ <- balances(deletedTransactions) .map { b => b.copy(spent = -b.spent, received = -b.received) } diff --git a/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala b/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala index 3e54af9..7f5936b 100644 --- a/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala +++ b/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala @@ -30,99 +30,86 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures new TransactionPostgresDAO(new FieldOrderingSQLInterpreter)) lazy val xsnService = new FileBasedXSNService - lazy val processor = blockEventsProcessor() + lazy val processor = blockEventsProcessor(xsnService) + + val blockList = List( + BlockLoader.get("00000c822abdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34"), + BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7"), + BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8"), + BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") + ) before { clearDatabase() } "newLatestBlock" should { - "fail on genesis block due to the missing transaction" in { - // see https://github.com/X9Developers/XSN/issues/32 - val block0 = BlockLoader.get("00000c822abdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34") - - whenReady(processor.processBlock(block0.hash)) { result => - result mustEqual Good(MissingBlockIgnored) - } - } - - "process first block" in { - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - - whenReady(processor.processBlock(block1.hash)) { result => - result.isGood mustEqual true + "process genesis block" in { + val block = blockList.head + whenReady(processor.processBlock(block.hash)) { result => + result mustEqual Good(FirstBlockCreated(block)) } } "process a new block" in { - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) - List(block1, block2).map(dataHandler.insert).foreach(_.isGood mustEqual true) + createBlocks(processor, block0, block1) - whenReady(processor.processBlock(block3.hash)) { result => - result mustEqual Good(NewBlockAppended(block3)) - val blocks = List(block1, block2, block3) + whenReady(processor.processBlock(block2.hash)) { result => + result mustEqual Good(NewBlockAppended(block2)) + val blocks = List(block0, block1, block2) verifyBlockchain(blocks) } } "process a rechain" in { - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) - List(block1, block2, block3).map(dataHandler.insert).foreach(_.isGood mustEqual true) + createBlocks(processor, block0, block1, block2) - whenReady(processor.processBlock(block2.hash)) { + whenReady(processor.processBlock(block1.hash)) { case Good(RechainDone(orphanBlock, newBlock)) => - orphanBlock.hash mustEqual block3.hash - newBlock.hash mustEqual block2.hash + orphanBlock.hash mustEqual block2.hash + newBlock.hash mustEqual block1.hash - val blocks = List(block1, block2) + val blocks = List(block0, block1) verifyBlockchain(blocks) - case _ => fail() + case e => fail() } } - "process a missing block" in { - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") + "process a repeated block without affecting the balances" in { + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) - List(block2, block3).map(dataHandler.insert).foreach(_.isGood mustEqual true) + createBlocks(processor, block0, block1, block2) - whenReady(processor.processBlock(block1.hash)) { result => - result.isGood mustEqual true - val blocks = List(block1, block2, block3) - verifyBlockchain(blocks) - } - } - - "processing a repeated missing block doesn't affect the balance" in { - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") - - List(block1, block2, block3).map(dataHandler.insert).foreach(_.isGood mustEqual true) + val statsDataHandler = new StatisticsPostgresDataHandler(database, new StatisticsPostgresDAO) + val expectedStats = statsDataHandler.getStatistics().get - whenReady(processor.processBlock(block1.hash)) { result => + whenReady(processor.processBlock(block0.hash)) { result => result.isGood mustEqual true - val blocks = List(block1, block2, block3) + val blocks = List(block0, block1, block2) verifyBlockchain(blocks) - val statsDataHandler = new StatisticsPostgresDataHandler(database, new StatisticsPostgresDAO) val stats = statsDataHandler.getStatistics().get - stats.totalSupply.isEmpty mustEqual true - stats.circulatingSupply.isEmpty mustEqual true + stats.totalSupply mustEqual expectedStats.totalSupply + stats.circulatingSupply mustEqual expectedStats.circulatingSupply } } "process a block without spent index on transactions" in { val block = BlockLoader.get("000001ff95f22b8d82db14a5c5e9f725e8239e548be43c668766e7ddaee81924") + .copy(previousBlockhash = None) + val processor = blockEventsProcessor(block) whenReady(processor.processBlock(block.hash)) { result => result.isGood mustEqual true @@ -140,17 +127,15 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } "process a rechain without corrupting the balances table" in { - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) - List(block1, block2) - .map(_.hash) - .map(processor.processBlock) - .foreach { whenReady(_) { _.isGood mustEqual true } } + createBlocks(processor, block0, block1, block2) whenReady(processor.processBlock(block1.hash)) { result => result.isGood mustEqual true - val blocks = List(block1) + val blocks = List(block0, block1) verifyBlockchain(blocks) val balanceDataHandler = new BalancePostgresDataHandler(database, new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) @@ -169,12 +154,12 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } } - // TODO: failed once on travis, might not be stable "ignore orphan block on rare rechain events when the rpc server doesn't have the block anymore" in { // see https://github.com/X9Developers/block-explorer/issues/6 - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) + val block3 = blockList(3) val xsnService = new FileBasedXSNService { override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = { @@ -187,11 +172,8 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } val processor = blockEventsProcessor(xsn = xsnService) - - List(block1, block2) - .map(_.hash) - .map(processor.processBlock) - .foreach { whenReady(_) { _.isGood mustEqual true } } + + createBlocks(processor, block0, block1, block2) /** * When processing the latest block, a rechain event has occurred in the rpc server which leads to @@ -199,16 +181,17 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures */ whenReady(processor.processBlock(block3.hash)) { result => result mustEqual Good(MissingBlockIgnored) - val blocks = List(block1, block2) + val blocks = List(block0, block1, block2) verifyBlockchain(blocks) } } "ignore orphan block on rare rechain events when the rpc server doesn't have the a transaction from the block anymore" in { // see https://github.com/X9Developers/block-explorer/issues/6 - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) + val block3 = blockList(3) val xsnService = new FileBasedXSNService { override def getTransaction(txid: TransactionId): FutureApplicationResult[Transaction] = { @@ -222,10 +205,7 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures val processor = blockEventsProcessor(xsn = xsnService) - List(block1, block2) - .map(_.hash) - .map(processor.processBlock) - .foreach { whenReady(_) { _.isGood mustEqual true } } + createBlocks(processor, block0, block1, block2) /** * When processing the latest block, a rechain event has occurred in the rpc server which leads to @@ -233,39 +213,27 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures */ whenReady(processor.processBlock(block3.hash)) { result => result mustEqual Good(MissingBlockIgnored) - val blocks = List(block1, block2) + val blocks = List(block0, block1, block2) verifyBlockchain(blocks) } } "remove orphan block on rare rechain events when the block height already exists" in { // see https://github.com/X9Developers/block-explorer/issues/6 - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) + val block3 = blockList(3) .copy(height = block2.height) .copy(previousBlockhash = block2.previousBlockhash) - val xsnService = new FileBasedXSNService { - override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = { - if (blockhash == block3.hash) { - Future.successful(Good(block3)) - } else { - super.getBlock(blockhash) - } - } - } - - val processor = blockEventsProcessor(xsn = xsnService) + val processor = blockEventsProcessor(block3) - List(block1, block2) - .map(_.hash) - .map(processor.processBlock) - .foreach { whenReady(_) { _.isGood mustEqual true } } + createBlocks(processor, block0, block1, block2) whenReady(processor.processBlock(block3.hash)) { result => result mustEqual Good(ReplacedByBlockHeight(block3)) - val blocks = List(block1.copy(nextBlockhash = Some(block3.hash)), block3) + val blocks = List(block0, block1.copy(nextBlockhash = Some(block3.hash)), block3) verifyBlockchain(blocks) // ensure block2 has been removed @@ -275,31 +243,19 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures "keep the correct previous_blockhash on rare events" in { // see https://github.com/X9Developers/block-explorer/issues/12 - val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") - val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") - val block3 = BlockLoader.get("00000766115b26ecbc09cd3a3db6870fdaf2f049d65a910eb2f2b48b566ca7bd") - val rareBlock3 = block3.copy(previousBlockhash = Some(block1.hash)) - - val xsnService = new FileBasedXSNService { - override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = { - if (blockhash == block3.hash) { - Future.successful(Good(rareBlock3)) - } else { - super.getBlock(blockhash) - } - } - } + val block0 = blockList(0) + val block1 = blockList(1) + val block2 = blockList(2) + val block3 = blockList(3) + val rareBlock3 = block3.copy(previousBlockhash = block2.previousBlockhash) - val processor = blockEventsProcessor(xsn = xsnService) + val processor = blockEventsProcessor(rareBlock3) - List(block1, rareBlock3) - .map(_.hash) - .map(processor.processBlock) - .foreach { whenReady(_) { _.isGood mustEqual true } } + createBlocks(processor, block0, block1, rareBlock3) whenReady(processor.processBlock(block2.hash)) { result => result mustEqual Good(MissingBlockProcessed(block2)) - val blocks = List(block1, block2, block3) + val blocks = List(block0, block1, block2, block3) verifyBlockchain(blocks) } } @@ -320,7 +276,30 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures } } - private def blockEventsProcessor(xsn: XSNService = xsnService) = { + private def createBlocks(processor: BlockEventsProcessor, blocks: Block*) = { + blocks + .map(_.hash) + .foreach { block => + whenReady(processor.processBlock(block)) { result => + result.isGood mustEqual true + } + } + } + + private def blockEventsProcessor(blocks: Block*): BlockEventsProcessor = { + val xsn = new FileBasedXSNService { + override def getBlock(blockhash: Blockhash): FutureApplicationResult[Block] = { + blocks + .find(_.hash == blockhash) + .map { block => Future.successful(Good(block)) } + .getOrElse(super.getBlock(blockhash)) + } + } + + blockEventsProcessor(xsn) + } + + private def blockEventsProcessor(xsn: XSNService): BlockEventsProcessor = { val dataSeeder = new DatabasePostgresSeeder( database, new BlockPostgresDAO,