From 1d3e8432542b087893d1d893501518a3f9863753 Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Sat, 5 Jan 2019 00:10:40 -0700 Subject: [PATCH] server: Update endpoint "GET /v2/addresses/:address/transactions" The inputs and outputs are now retrieved from the database, this makes calls more reliable. --- .../anorm/dao/TransactionPostgresDAO.scala | 50 ++++++++++++++++-- .../services/TransactionService.scala | 51 +++++++++---------- .../TransactionPostgresDataHandlerSpec.scala | 6 +-- 3 files changed, 73 insertions(+), 34 deletions(-) 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 fd99489..f78d6e1 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala @@ -95,7 +95,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi def getBy(address: Address, limit: Limit, orderingCondition: OrderingCondition)(implicit conn: Connection): List[Transaction] = { val order = toSQL(orderingCondition) - SQL( + val transactions = SQL( s""" |SELECT t.txid, t.blockhash, t.time, t.size |FROM transactions t JOIN address_transaction_details USING (txid) @@ -107,6 +107,14 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi 'address -> address.string, 'limit -> limit.int ).as(parseTransaction.*).flatten + + for { + tx <- transactions + } yield { + val inputs = getInputs(tx.id, address) + val outputs = getOutputs(tx.id, address) + tx.copy(inputs = inputs, outputs = outputs) + } } /** @@ -128,7 +136,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi case OrderingCondition.AscendingOrder => ">" } - SQL( + val transactions = SQL( s""" |WITH CTE AS ( | SELECT time AS lastSeenTime @@ -147,7 +155,15 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi 'address -> address.string, 'limit -> limit.int, 'lastSeenTxid -> lastSeenTxid.string - ).executeQuery().as(parseTransaction.*).flatten + ).as(parseTransaction.*).flatten + + for { + tx <- transactions + } yield { + val inputs = getInputs(tx.id, address) + val outputs = getOutputs(tx.id, address) + tx.copy(inputs = inputs, outputs = outputs) + } } def getBy( @@ -505,6 +521,34 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi result } + private def getInputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Input] = { + SQL( + """ + |SELECT txid, index, from_txid, from_output_index, value, address + |FROM transaction_inputs + |WHERE txid = {txid} AND + | address = {address} + """.stripMargin + ).on( + 'txid -> txid.string, + 'address -> address.string + ).as(parseTransactionInput.*).flatten + } + + private def getOutputs(txid: TransactionId, address: Address)(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} AND + | address = {address} + """.stripMargin + ).on( + 'txid -> txid.string, + 'address -> address.string + ).as(parseTransactionOutput.*).flatten + } + private def toSQL(condition: OrderingCondition): String = condition match { case OrderingCondition.AscendingOrder => "ASC" case OrderingCondition.DescendingOrder => "DESC" diff --git a/server/app/com/xsn/explorer/services/TransactionService.scala b/server/app/com/xsn/explorer/services/TransactionService.scala index 47dac3d..ef75b43 100644 --- a/server/app/com/xsn/explorer/services/TransactionService.scala +++ b/server/app/com/xsn/explorer/services/TransactionService.scala @@ -156,32 +156,6 @@ class TransactionService @Inject() ( lastSeenTxidString: Option[String], orderingConditionString: String): FutureApplicationResult[WrappedResult[List[LightWalletTransaction]]] = { - def buildData(address: Address, txValues: Transaction) = { - val result = for { - plain <- xsnService.getTransaction(txValues.id).toFutureOr - vin <- getTransactionVIN(plain.vin).toFutureOr - } yield { - val inputs = vin - .collect { - case TransactionVIN(txid, index, Some(value), Some(a)) if a == address => - LightWalletTransaction.Input(txid, index, value) - } - - val outputs = plain - .vout - .filter(_.address contains address) - .map { _.into[LightWalletTransaction.Output].withFieldRenamed(_.n, _.index).transform } - - txValues - .into[LightWalletTransaction] - .withFieldConst(_.inputs, inputs) - .withFieldConst(_.outputs, outputs) - .transform - } - - result.toFuture - } - val result = for { address <- { val maybe = Address.from(addressString) @@ -200,8 +174,29 @@ class TransactionService @Inject() ( } transactions <- transactionFutureDataHandler.getBy(address, limit, lastSeenTxid, orderingCondition).toFutureOr - data <- transactions.map { transaction => buildData(address, transaction) }.toFutureOr - } yield WrappedResult(data) + } yield { + val lightTxs = transactions.map { tx => + val inputs = tx.inputs.map { input => + input + .into[LightWalletTransaction.Input] + .withFieldRenamed(_.fromOutputIndex, _.index) + .withFieldRenamed(_.fromTxid, _.txid) + .transform + } + + val outputs = tx.outputs.map { output => + output.into[LightWalletTransaction.Output].transform + } + + tx + .into[LightWalletTransaction] + .withFieldConst(_.inputs, inputs) + .withFieldConst(_.outputs, outputs) + .transform + } + + WrappedResult(lightTxs) + } result.toFuture } diff --git a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala index 487a1fe..b828ad2 100644 --- a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala @@ -392,7 +392,7 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be prepare() val expected = sorted.head val result = dataHandler.getBy(address, Limit(1), None, condition).get - result mustEqual List(expected.copy(inputs = List.empty, outputs = List.empty)) + result.map(_.copy(inputs = List.empty, outputs = List.empty)) mustEqual List(expected.copy(inputs = List.empty, outputs = List.empty)) } s"[$tag] return the next elements given the last seen tx" in { @@ -401,7 +401,7 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be val lastSeenTxid = sorted.head.id val expected = sorted(1) val result = dataHandler.getBy(address, Limit(1), Option(lastSeenTxid), condition).get - result mustEqual List(expected.copy(inputs = List.empty, outputs = List.empty)) + result.map(_.copy(inputs = List.empty, outputs = List.empty)) mustEqual List(expected.copy(inputs = List.empty, outputs = List.empty)) } s"[$tag] return the element with the same time breaking ties by txid" in { @@ -410,7 +410,7 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be val lastSeenTxid = sorted(2).id val expected = sorted(3) val result = dataHandler.getBy(address, Limit(1), Option(lastSeenTxid), condition).get - result mustEqual List(expected.copy(inputs = List.empty, outputs = List.empty)) + result.map(_.copy(inputs = List.empty, outputs = List.empty)) mustEqual List(expected.copy(inputs = List.empty, outputs = List.empty)) } s"[$tag] return no elements on unknown lastSeenTransaction" in {