Browse Source

server: Delete block transactions on rechain events

This fixes a bug that corrupted the balances table because the
transactions related to a block weren't being deleted, hence, causing
wrong balances.
scalafmt-draft
Alexis Hernandez 7 years ago
parent
commit
7fdf67afb0
  1. 3
      server/app/com/xsn/explorer/data/DatabaseSeeder.scala
  2. 14
      server/app/com/xsn/explorer/data/anorm/DatabasePostgresSeeder.scala
  3. 2
      server/app/com/xsn/explorer/models/base/WrappedString.scala
  4. 12
      server/app/com/xsn/explorer/processors/BlockEventsProcessor.scala
  5. 30
      server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala

3
server/app/com/xsn/explorer/data/DatabaseSeeder.scala

@ -37,9 +37,8 @@ trait DatabaseSeeder[F[_]] {
object DatabaseSeeder { object DatabaseSeeder {
case class CreateBlockCommand(block: Block, transactions: List[Transaction]) case class CreateBlockCommand(block: Block, transactions: List[Transaction])
case class DeleteBlockCommand(block: Block, transactions: List[Transaction])
case class ReplaceBlockCommand( case class ReplaceBlockCommand(
orphanBlock: Block, orphanTransactions: List[Transaction], orphanBlock: Block,
newBlock: Block, newTransactions: List[Transaction]) newBlock: Block, newTransactions: List[Transaction])
} }

14
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.DatabaseBlockingSeeder
import com.xsn.explorer.data.DatabaseSeeder._ import com.xsn.explorer.data.DatabaseSeeder._
import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} 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.models.{Address, Balance, Transaction}
import com.xsn.explorer.util.Extensions.ListOptionExt import com.xsn.explorer.util.Extensions.ListOptionExt
import org.scalactic.Good import org.scalactic.Good
@ -45,11 +46,10 @@ class DatabasePostgresSeeder @Inject() (
} }
override def replaceLatestBlock(command: ReplaceBlockCommand): ApplicationResult[Unit] = withTransaction { implicit conn => 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 createCommand = CreateBlockCommand(command.newBlock, command.newTransactions)
val result = for { val result = for {
_ <- deleteBlockCascade(deleteCommand) _ <- deleteBlockCascade(command.orphanBlock)
_ <- upsertBlockCascade(createCommand) _ <- upsertBlockCascade(createCommand)
} yield () } yield ()
@ -95,16 +95,16 @@ class DatabasePostgresSeeder @Inject() (
} yield () } yield ()
} }
private def deleteBlockCascade(command: DeleteBlockCommand)(implicit conn: Connection): Option[Unit] = { private def deleteBlockCascade(block: Block)(implicit conn: Connection): Option[Unit] = {
for { for {
// block // block
_ <- blockPostgresDAO.delete(command.block.hash) _ <- blockPostgresDAO.delete(block.hash)
// transactions // transactions
_ = command.transactions.foreach(tx => transactionPostgresDAO.delete(tx.id)) deletedTransactions = transactionPostgresDAO.deleteBy(block.hash)
// balances // balances
_ <- spendMap(command.transactions) _ <- spendMap(deletedTransactions)
.map { case (address, value) => .map { case (address, value) =>
val balance = Balance(address, spent = -value) val balance = Balance(address, spent = -value)
addressPostgresDAO.upsert(balance) addressPostgresDAO.upsert(balance)
@ -112,7 +112,7 @@ class DatabasePostgresSeeder @Inject() (
.toList .toList
.everything .everything
_ <- receiveMap(command.transactions) _ <- receiveMap(deletedTransactions)
.map { case (address, value) => .map { case (address, value) =>
val balance = Balance(address, received = -value) val balance = Balance(address, received = -value)
addressPostgresDAO.upsert(balance) addressPostgresDAO.upsert(balance)

2
server/app/com/xsn/explorer/models/base/WrappedString.scala

@ -4,6 +4,8 @@ import play.api.libs.json.{JsString, Writes}
trait WrappedString extends Any { trait WrappedString extends Any {
def string: String def string: String
override def toString: String = string
} }
object WrappedString { object WrappedString {

12
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] = { private def newLatestBlock(newBlock: Block, newTransactions: List[Transaction]): FutureApplicationResult[Result] = {
def onRechain(orphanBlock: Block): FutureApplicationResult[Result] = { def onRechain(orphanBlock: Block): FutureApplicationResult[Result] = {
val result = for { val command = DatabaseSeeder.ReplaceBlockCommand(
orphanTransactions <- orphanBlock.transactions.map(transactionService.getTransaction).toFutureOr orphanBlock = orphanBlock,
newBlock = newBlock,
newTransactions = newTransactions)
command = DatabaseSeeder.ReplaceBlockCommand( val result = for {
orphanBlock = orphanBlock,
orphanTransactions = orphanTransactions,
newBlock = newBlock,
newTransactions = newTransactions)
_ <- databaseSeeder.replaceLatestBlock(command).toFutureOr _ <- databaseSeeder.replaceLatestBlock(command).toFutureOr
} yield RechainDone(orphanBlock, newBlock) } yield RechainDone(orphanBlock, newBlock)

30
server/test/com/xsn/explorer/processors/BlockEventsProcessorSpec.scala

@ -130,6 +130,36 @@ class BlockEventsProcessorSpec extends PostgresDataHandlerSpec with ScalaFutures
balance.spent mustEqual BigDecimal("76500000.000000000000000") 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]) = { private def verifyBlockchain(blocks: List[Block]) = {

Loading…
Cancel
Save