Browse Source

server: Update endpoint "GET /v2/addresses/:address/transactions"

The inputs and outputs are now retrieved from the database, this makes
calls more reliable.
prometheus-integration
Alexis Hernandez 6 years ago
parent
commit
1d3e843254
  1. 50
      server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala
  2. 51
      server/app/com/xsn/explorer/services/TransactionService.scala
  3. 6
      server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala

50
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"

51
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
}

6
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 {

Loading…
Cancel
Save