Browse Source

server: Add getByBlockhash method to the TransactionDataHandler (#19)

prometheus-integration
Alexis Hernandez 7 years ago
parent
commit
11e25da4a4
  1. 5
      server/app/com/xsn/explorer/data/TransactionDataHandler.scala
  2. 14
      server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala
  3. 45
      server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala
  4. 10
      server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala
  5. 106
      server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala
  6. 2
      server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala

5
server/app/com/xsn/explorer/data/TransactionDataHandler.scala

@ -15,6 +15,11 @@ trait TransactionDataHandler[F[_]] {
ordering: FieldOrdering[TransactionField]): F[PaginatedResult[TransactionWithValues]]
def getUnspentOutputs(address: Address): F[List[Transaction.Output]]
def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): F[PaginatedResult[TransactionWithValues]]
}
trait TransactionBlockingDataHandler extends TransactionDataHandler[ApplicationResult]

14
server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala

@ -29,8 +29,20 @@ class TransactionPostgresDataHandler @Inject() (
Good(result)
}
def getUnspentOutputs(address: Address): ApplicationResult[List[Transaction.Output]] = withConnection { implicit conn =>
override def getUnspentOutputs(address: Address): ApplicationResult[List[Transaction.Output]] = withConnection { implicit conn =>
val result = transactionPostgresDAO.getUnspentOutputs(address)
Good(result)
}
override def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = withConnection { implicit conn =>
val transactions = transactionPostgresDAO.getByBlockhash(blockhash, paginatedQuery, ordering)
val total = transactionPostgresDAO.countByBlockhash(blockhash)
val result = PaginatedResult(paginatedQuery.offset, paginatedQuery.limit, total, transactions)
Good(result)
}
}

45
server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala

@ -145,6 +145,51 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi
Count(result)
}
def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField])(
implicit conn: Connection): List[TransactionWithValues] = {
val orderBy = fieldOrderingSQLInterpreter.toOrderByClause(ordering)
/**
* TODO: The query is very slow while aggregating the spent and received values,
* it might be worth creating an index-like table to get the accumulated
* values directly.
*/
SQL(
s"""
|SELECT t.txid, blockhash, t.time, t.size,
| (SELECT COALESCE(SUM(value), 0) FROM transaction_inputs WHERE txid = t.txid) AS sent,
| (SELECT COALESCE(SUM(value), 0) FROM transaction_outputs WHERE txid = t.txid) AS received
|FROM transactions t JOIN blocks USING (blockhash)
|WHERE blockhash = {blockhash}
|$orderBy
|OFFSET {offset}
|LIMIT {limit}
""".stripMargin
).on(
'blockhash -> blockhash.string,
'offset -> paginatedQuery.offset.int,
'limit -> paginatedQuery.limit.int
).as(parseTransactionWithValues.*).flatten
}
def countByBlockhash(blockhash: Blockhash)(implicit conn: Connection): Count = {
val result = SQL(
"""
|SELECT COUNT(*)
|FROM blocks JOIN transactions USING (blockhash)
|WHERE blockhash = {blockhash}
""".stripMargin
).on(
'blockhash -> blockhash.string
).as(SqlParser.scalar[Int].single)
Count(result)
}
def getUnspentOutputs(address: Address)(implicit conn: Connection): List[Transaction.Output] = {
SQL(
"""

10
server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala

@ -3,7 +3,7 @@ package com.xsn.explorer.data.async
import javax.inject.Inject
import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult}
import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery}
import com.alexitc.playsonify.models.{FieldOrdering, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.{TransactionBlockingDataHandler, TransactionDataHandler}
import com.xsn.explorer.executors.DatabaseExecutionContext
import com.xsn.explorer.models._
@ -27,4 +27,12 @@ class TransactionFutureDataHandler @Inject() (
override def getUnspentOutputs(address: Address): FutureApplicationResult[List[Transaction.Output]] = Future {
blockingDataHandler.getUnspentOutputs(address)
}
override def getByBlockhash(
blockhash: Blockhash,
paginatedQuery: PaginatedQuery,
ordering: FieldOrdering[TransactionField]): FutureApplicationResult[PaginatedResult[TransactionWithValues]] = Future {
blockingDataHandler.getByBlockhash(blockhash, paginatedQuery, ordering)
}
}

106
server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala

@ -196,6 +196,106 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be
}
}
"getByBlockhash" should {
val blockhash = createBlockhash("0000000000bdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34")
val inputs = List(
Transaction.Input(dummyTransaction.id, 0, 1, 100, createAddress("XxQ7j37LfuXgsLd5DZAwFKhT3s2ZMkW86F")),
Transaction.Input(dummyTransaction.id, 1, 2, 200, createAddress("XxQ7j37LfuXgsLD5DZAwFKhT3s2ZMkW86F"))
)
val outputs = List(
Transaction.Output(createTransactionId("ad9320dcea2fdaa357aac6eab00695cf07b487e34113598909f625c24629c981"), 0, BigDecimal(50), createAddress("Xbh5pJdBNm8J9PxnEmwVcuQKRmZZ7DkpcF"), HexString.from("00").get, None, None),
Transaction.Output(
createTransactionId("ad9320dcea2fdaa357aac6eab00695cf07b487e34113598909f625c24629c981"),
1,
BigDecimal(250),
createAddress("Xbh5pJdBNm8J9PxnEmwVcuQKRmZZ7DkpcF"),
HexString.from("00").get,
None, None)
)
val transactions = List(
Transaction(
createTransactionId("00051e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8"),
blockhash,
12312312L,
Size(1000),
inputs,
outputs),
Transaction(
createTransactionId("02c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8"),
blockhash,
12312302L,
Size(900),
inputs.map(x => x.copy(value = x.value * 2)),
outputs.map(x => x.copy(value = x.value * 2))),
Transaction(
createTransactionId("00c51e4fe89466faa734d6207a7ef6115fa1dd33f7156b006fafc6bb85a79eb8"),
blockhash,
12312310L,
Size(100),
inputs.map(x => x.copy(value = x.value / 2)),
outputs.map(x => x.copy(value = x.value / 2)))
)
val block = this.block.copy(
hash = blockhash,
height = Height(10),
transactions = transactions.map(_.id))
"find no results" in {
val blockhash = createBlockhash("021d335a910f6780bdf48f9efd751b162074367eeb6740ac205223496430260f")
val query = PaginatedQuery(Offset(0), Limit(10))
val expected = PaginatedResult(query.offset, query.limit, Count(0), List.empty)
val result = dataHandler.getByBlockhash(blockhash, query, defaultOrdering)
result mustEqual Good(expected)
}
"find the right values" in {
createBlock(block, transactions)
val query = PaginatedQuery(Offset(0), Limit(10))
val result = dataHandler.getByBlockhash(blockhash, query, defaultOrdering).get
result.total mustEqual Count(transactions.size)
result.offset mustEqual query.offset
result.limit mustEqual query.limit
result.data.size mustEqual transactions.size
}
def testOrdering[B](field: TransactionField)(sortBy: Transaction => B)(implicit order: Ordering[B]) = {
createBlock(block, transactions)
val ordering = FieldOrdering(field, OrderingCondition.AscendingOrder)
val query = PaginatedQuery(Offset(0), Limit(10))
val expected = transactions.sortBy(sortBy)(order).map(_.id)
val result = dataHandler.getByBlockhash(blockhash, query, ordering).get.data
result.map(_.id) mustEqual expected
val expectedReverse = expected.reverse
val resultReverse = dataHandler.getByBlockhash(blockhash, query, ordering.copy(orderingCondition = OrderingCondition.DescendingOrder)).get.data
resultReverse.map(_.id) mustEqual expectedReverse
}
"allow to sort by txid" in {
testOrdering(TransactionField.TransactionId)(_.id.string)
}
"allow to sort by time" in {
testOrdering(TransactionField.Time)(_.time)
}
"allow to sort by sent" in {
testOrdering(TransactionField.Sent)(_.inputs.map(_.value).sum)
}
"allow to sort by received" in {
testOrdering(TransactionField.Received)(_.outputs.map(_.value).sum)
}
}
private def createBlock(block: Block) = {
val transactions = block.transactions
.map(_.string)
@ -206,4 +306,10 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be
result.isGood mustEqual true
}
private def createBlock(block: Block, transactions: List[Transaction]) = {
val result = ledgerDataHandler.push(block, transactions)
result.isGood mustEqual true
}
}

2
server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala

@ -11,4 +11,6 @@ class TransactionDummyDataHandler extends TransactionBlockingDataHandler {
override def getBy(address: Address, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = ???
override def getUnspentOutputs(address: Address): ApplicationResult[List[Transaction.Output]] = ???
override def getByBlockhash(blockhash: Blockhash, paginatedQuery: PaginatedQuery, ordering: FieldOrdering[TransactionField]): ApplicationResult[PaginatedResult[TransactionWithValues]] = ???
}

Loading…
Cancel
Save