Skip to content

Commit

Permalink
server: Update endpoint "GET /v2/addresses/:address/transactions"
Browse files Browse the repository at this point in the history
The inputs and outputs are now retrieved from the database, this makes
calls more reliable.
  • Loading branch information
AlexITC committed Jan 5, 2019
1 parent a3aa33b commit 1d3e843
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 34 deletions.
Expand Up @@ -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)
Expand All @@ -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)
}
}

/**
Expand All @@ -128,7 +136,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi
case OrderingCondition.AscendingOrder => ">"
}

SQL(
val transactions = SQL(
s"""
|WITH CTE AS (
| SELECT time AS lastSeenTime
Expand All @@ -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(
Expand Down Expand Up @@ -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"
Expand Down
51 changes: 23 additions & 28 deletions server/app/com/xsn/explorer/services/TransactionService.scala
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down

0 comments on commit 1d3e843

Please sign in to comment.