diff --git a/server/app/com/xsn/explorer/data/TransactionDataHandler.scala b/server/app/com/xsn/explorer/data/TransactionDataHandler.scala index fb3cfb27..a922da9b 100644 --- a/server/app/com/xsn/explorer/data/TransactionDataHandler.scala +++ b/server/app/com/xsn/explorer/data/TransactionDataHandler.scala @@ -31,6 +31,10 @@ trait TransactionDataHandler[F[_]] { limit: Limit, lastSeenTxid: Option[TransactionId]): F[List[TransactionWithValues]] + def getTransactionsWithIOBy( + blockhash: Blockhash, + limit: Limit, + lastSeenTxid: Option[TransactionId]): F[List[Transaction.HasIO]] } trait TransactionBlockingDataHandler extends TransactionDataHandler[ApplicationResult] diff --git a/server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala b/server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala index 30bdec2a..983408fd 100644 --- a/server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala +++ b/server/app/com/xsn/explorer/data/anorm/TransactionPostgresDataHandler.scala @@ -73,4 +73,16 @@ class TransactionPostgresDataHandler @Inject() ( Good(transactions) } + + override def getTransactionsWithIOBy( + blockhash: Blockhash, + limit: Limit, + lastSeenTxid: Option[TransactionId]): ApplicationResult[List[Transaction.HasIO]] = withConnection { implicit conn => + + val transactions = lastSeenTxid + .map { transactionPostgresDAO.getTransactionsWithIOBy(blockhash, _, limit) } + .getOrElse { transactionPostgresDAO.getTransactionsWithIOBy(blockhash, limit) } + + Good(transactions) + } } diff --git a/server/app/com/xsn/explorer/data/anorm/dao/TransactionInputPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/TransactionInputPostgresDAO.scala index f1b912fb..72107687 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/TransactionInputPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/TransactionInputPostgresDAO.scala @@ -62,6 +62,18 @@ class TransactionInputPostgresDAO { ).as(parseTransactionInput.*) } + def getInputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Input] = { + SQL( + """ + |SELECT txid, index, from_txid, from_output_index, value, address + |FROM transaction_inputs + |WHERE txid = {txid} + """.stripMargin + ).on( + 'txid -> txid.string + ).as(parseTransactionInput.*) + } + def getInputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Input] = { SQL( """ diff --git a/server/app/com/xsn/explorer/data/anorm/dao/TransactionOutputPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/TransactionOutputPostgresDAO.scala index 47fe6517..95616aef 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/TransactionOutputPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/TransactionOutputPostgresDAO.scala @@ -78,6 +78,18 @@ class TransactionOutputPostgresDAO { result } + def getOutputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Output] = { + SQL( + """ + |SELECT txid, index, hex_script, value, address, tpos_owner_address, tpos_merchant_address + |FROM transaction_outputs + |WHERE txid = {txid} + """.stripMargin + ).on( + 'txid -> txid.string + ).as(parseTransactionOutput.*) + } + def getOutputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Output] = { SQL( """ diff --git a/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala index 78906d5b..66f2042b 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala @@ -332,6 +332,59 @@ class TransactionPostgresDAO @Inject() ( ).as(parseTransactionWithValues.*) } + def getTransactionsWithIOBy(blockhash: Blockhash, limit: Limit)(implicit conn: Connection): List[Transaction.HasIO] = { + val transactions = SQL( + """ + |SELECT t.txid, t.blockhash, t.time, t.size + |FROM transactions t JOIN blocks USING (blockhash) + |WHERE blockhash = {blockhash} + |ORDER BY t.index ASC + |LIMIT {limit} + """.stripMargin + ).on( + 'limit -> limit.int, + 'blockhash -> blockhash.string + ).as(parseTransaction.*) + + for { + tx <- transactions + } yield { + val inputs = transactionInputDAO.getInputs(tx.id) + val outputs = transactionOutputDAO.getOutputs(tx.id) + Transaction.HasIO(tx, inputs = inputs, outputs = outputs) + } + } + + def getTransactionsWithIOBy(blockhash: Blockhash, lastSeenTxid: TransactionId, limit: Limit)(implicit conn: Connection): List[Transaction.HasIO] = { + val transactions = SQL( + """ + |WITH CTE AS ( + | SELECT index AS lastSeenIndex + | FROM transactions + | WHERE txid = {lastSeenTxid} + |) + |SELECT t.txid, t.blockhash, t.time, t.size + |FROM CTE CROSS JOIN transactions t JOIN blocks USING (blockhash) + |WHERE blockhash = {blockhash} AND + | t.index > lastSeenIndex + |ORDER BY t.index ASC + |LIMIT {limit} + """.stripMargin + ).on( + 'limit -> limit.int, + 'blockhash -> blockhash.string, + 'lastSeenTxid -> lastSeenTxid.string + ).as(parseTransaction.*) + + for { + tx <- transactions + } yield { + val inputs = transactionInputDAO.getInputs(tx.id) + val outputs = transactionOutputDAO.getOutputs(tx.id) + Transaction.HasIO(tx, inputs = inputs, outputs = outputs) + } + } + private def upsertTransaction(index: Int, transaction: Transaction)(implicit conn: Connection): Option[Transaction] = { SQL( """ diff --git a/server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala b/server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala index 00fa209c..52e4dbfa 100644 --- a/server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala +++ b/server/app/com/xsn/explorer/data/async/TransactionFutureDataHandler.scala @@ -54,4 +54,12 @@ class TransactionFutureDataHandler @Inject() ( blockingDataHandler.getByBlockhash(blockhash, limit, lastSeenTxid) } + + override def getTransactionsWithIOBy( + blockhash: Blockhash, + limit: Limit, + lastSeenTxid: Option[TransactionId]): FutureApplicationResult[List[Transaction.HasIO]] = Future { + + blockingDataHandler.getTransactionsWithIOBy(blockhash, limit, lastSeenTxid) + } } diff --git a/server/app/com/xsn/explorer/services/TransactionService.scala b/server/app/com/xsn/explorer/services/TransactionService.scala index 75c4f708..303f14e0 100644 --- a/server/app/com/xsn/explorer/services/TransactionService.scala +++ b/server/app/com/xsn/explorer/services/TransactionService.scala @@ -109,6 +109,33 @@ class TransactionService @Inject() ( result.toFuture } + def getLightWalletTransactionsByBlockhash( + blockhashString: String, + limit: Limit, + lastSeenTxidString: Option[String]): FutureApplicationResult[WrappedResult[List[LightWalletTransaction]]] = { + + val result = for { + blockhash <- Or.from(Blockhash.from(blockhashString), One(BlockhashFormatError)).toFutureOr + _ <- paginatedQueryValidator.validate(PaginatedQuery(Offset(0), limit), maxTransactionsPerQuery).toFutureOr + + lastSeenTxid <- { + lastSeenTxidString + .map(TransactionId.from) + .map { txid => Or.from(txid, One(TransactionFormatError)).map(Option.apply) } + .getOrElse(Good(Option.empty)) + .toFutureOr + } + + transactions <- transactionFutureDataHandler.getTransactionsWithIOBy(blockhash, limit, lastSeenTxid).toFutureOr + } yield { + val lightTxs = transactions.map(toLightWalletTransaction) + + WrappedResult(lightTxs) + } + + result.toFuture + } + /** TODO: Move to another file */ private def getOrderingConditionResult(unsafeOrderingCondition: String) = { val maybe = parseOrderingCondition(unsafeOrderingCondition) diff --git a/server/app/controllers/BlocksController.scala b/server/app/controllers/BlocksController.scala index a0484873..e18f2dff 100644 --- a/server/app/controllers/BlocksController.scala +++ b/server/app/controllers/BlocksController.scala @@ -53,4 +53,8 @@ class BlocksController @Inject() ( def getTransactionsV2(blockhash: String, limit: Int, lastSeenTxid: Option[String]) = public { _ => transactionService.getByBlockhash(blockhash, Limit(limit), lastSeenTxid) } + + def getLightTransactionsV2(blockhash: String, limit: Int, lastSeenTxid: Option[String]) = public { _ => + transactionService.getLightWalletTransactionsByBlockhash(blockhash, Limit(limit), lastSeenTxid) + } } diff --git a/server/conf/routes b/server/conf/routes index b93a2d48..1e162ac7 100644 --- a/server/conf/routes +++ b/server/conf/routes @@ -22,6 +22,7 @@ GET /blocks/:query controllers.BlocksController.getDetails GET /blocks/:query/raw controllers.BlocksController.getRawBlock(query: String) GET /blocks/:blockhash/transactions controllers.BlocksController.getTransactions(blockhash: String, offset: Int ?= 0, limit: Int ?= 10, orderBy: String ?= "") GET /v2/blocks/:blockhash/transactions controllers.BlocksController.getTransactionsV2(blockhash: String, limit: Int ?= 10, lastSeenTxid: Option[String]) +GET /v2/blocks/:blockhash/light-wallet-transactions controllers.BlocksController.getLightTransactionsV2(blockhash: String, limit: Int ?= 10, lastSeenTxid: Option[String]) GET /stats controllers.StatisticsController.getStatus() diff --git a/server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala b/server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala index a9ccf931..fe95f174 100644 --- a/server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala +++ b/server/test/com/xsn/explorer/helpers/TransactionDummyDataHandler.scala @@ -22,4 +22,5 @@ class TransactionDummyDataHandler extends TransactionBlockingDataHandler { override def getByBlockhash(blockhash: Blockhash, limit: pagination.Limit, lastSeenTxid: Option[TransactionId]): ApplicationResult[List[TransactionWithValues]] = ??? + override def getTransactionsWithIOBy(blockhash: Blockhash, limit: pagination.Limit, lastSeenTxid: Option[TransactionId]): ApplicationResult[List[Transaction.HasIO]] = ??? }