diff --git a/server/app/com/xsn/explorer/data/DatabaseSeeder.scala b/server/app/com/xsn/explorer/data/DatabaseSeeder.scala index 1ca1b50..26415b2 100644 --- a/server/app/com/xsn/explorer/data/DatabaseSeeder.scala +++ b/server/app/com/xsn/explorer/data/DatabaseSeeder.scala @@ -37,9 +37,8 @@ trait DatabaseSeeder[F[_]] { object DatabaseSeeder { case class CreateBlockCommand(block: Block, transactions: List[Transaction]) - case class DeleteBlockCommand(block: Block, transactions: List[Transaction]) case class ReplaceBlockCommand( - orphanBlock: Block, orphanTransactions: List[Transaction], + orphanBlock: Block, newBlock: Block, newTransactions: List[Transaction]) } diff --git a/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala b/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala index fb64878..947c01d 100644 --- a/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala +++ b/server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala @@ -7,6 +7,7 @@ import com.alexitc.playsonify.core.ApplicationResult import com.xsn.explorer.data.DatabaseBlockingSeeder import com.xsn.explorer.data.DatabaseSeeder._ import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} +import com.xsn.explorer.models.rpc.Block import com.xsn.explorer.models.{Address, Balance, Transaction} import com.xsn.explorer.util.Extensions.ListOptionExt import org.scalactic.Good @@ -45,11 +46,10 @@ class DatabasePostgresSeeder @Inject() ( } override def replaceLatestBlock(command: ReplaceBlockCommand): ApplicationResult[Unit] = withTransaction { implicit conn => - val deleteCommand = DeleteBlockCommand(command.orphanBlock, command.orphanTransactions) val createCommand = CreateBlockCommand(command.newBlock, command.newTransactions) val result = for { - _ <- deleteBlockCascade(deleteCommand) + _ <- deleteBlockCascade(command.orphanBlock) _ <- upsertBlockCascade(createCommand) } yield () @@ -95,16 +95,16 @@ class DatabasePostgresSeeder @Inject() ( } yield () } - private def deleteBlockCascade(command: DeleteBlockCommand)(implicit conn: Connection): Option[Unit] = { + private def deleteBlockCascade(block: Block)(implicit conn: Connection): Option[Unit] = { for { // block - _ <- blockPostgresDAO.delete(command.block.hash) + _ <- blockPostgresDAO.delete(block.hash) // transactions - _ = command.transactions.foreach(tx => transactionPostgresDAO.delete(tx.id)) + deletedTransactions = transactionPostgresDAO.deleteBy(block.hash) // balances - _ <- spendMap(command.transactions) + _ <- spendMap(deletedTransactions) .map { case (address, value) => val balance = Balance(address, spent = -value) addressPostgresDAO.upsert(balance) @@ -112,7 +112,7 @@ class DatabasePostgresSeeder @Inject() ( .toList .everything - _ <- receiveMap(command.transactions) + _ <- receiveMap(deletedTransactions) .map { case (address, value) => val balance = Balance(address, received = -value) addressPostgresDAO.upsert(balance) diff --git a/server/app/com/xsn/explorer/models/base/WrappedString.scala b/server/app/com/xsn/explorer/models/base/WrappedString.scala index 5ac6839..0540f9b 100644 --- a/server/app/com/xsn/explorer/models/base/WrappedString.scala +++ b/server/app/com/xsn/explorer/models/base/WrappedString.scala @@ -4,6 +4,8 @@ import play.api.libs.json.{JsString, Writes} trait WrappedString extends Any { def string: String + + override def toString: String = string } object WrappedString { diff --git a/server/app/com/xsn/explorer/processors/BlockEventsProcessor.scala b/server/app/com/xsn/explorer/processors/BlockEventsProcessor.scala index b0baa93..a9ac460 100644 --- a/server/app/com/xsn/explorer/processors/BlockEventsProcessor.scala +++ b/server/app/com/xsn/explorer/processors/BlockEventsProcessor.scala @@ -58,14 +58,12 @@ class BlockEventsProcessor @Inject() ( private def newLatestBlock(newBlock: Block, newTransactions: List[Transaction]): FutureApplicationResult[Result] = { def onRechain(orphanBlock: Block): FutureApplicationResult[Result] = { - val result = for { - orphanTransactions <- orphanBlock.transactions.map(transactionService.getTransaction).toFutureOr + val command = DatabaseSeeder.ReplaceBlockCommand( + orphanBlock = orphanBlock, + newBlock = newBlock, + newTransactions = newTransactions) - command = DatabaseSeeder.ReplaceBlockCommand( - orphanBlock = orphanBlock, - orphanTransactions = orphanTransactions, - newBlock = newBlock, - newTransactions = newTransactions) + val result = for { _ <- databaseSeeder.replaceLatestBlock(command).toFutureOr } yield RechainDone(orphanBlock, newBlock) diff --git a/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala b/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala index 7e0e67a..28a19d8 100644 --- a/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala +++ b/server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala @@ -130,6 +130,36 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures balance.spent mustEqual BigDecimal("76500000.000000000000000") } } + + "process a rechain without corrupting the balances table" in { + val block1 = BlockLoader.get("000003fb382f6892ae96594b81aa916a8923c70701de4e7054aac556c7271ef7") + val block2 = BlockLoader.get("000004645e2717b556682e3c642a4c6e473bf25c653ff8e8c114a3006040ffb8") + + List(block1, block2) + .map(_.hash) + .map(processor.newLatestBlock) + .foreach { whenReady(_) { _.isGood mustEqual true } } + + whenReady(processor.newLatestBlock(block1.hash)) { result => + result.isGood mustEqual true + val blocks = List(block1) + verifyBlockchain(blocks) + + val balanceDataHandler = new BalancePostgresDataHandler(database, new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) + val balances = balanceDataHandler.get( + PaginatedQuery(Offset(0), Limit(100)), + FieldOrdering(BalanceField.Available, OrderingCondition.DescendingOrder)) + .get + .data + + val balance = balances + .find(_.address.string == "XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9") + .get + + balance.received mustEqual BigDecimal(0) + balance.spent mustEqual BigDecimal(0) + } + } } private def verifyBlockchain(blocks: List[Block]) = {