Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new endpoint /addresses/<address>/amount-history-deltas #508

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions app/src/main/resources/explorer-backend-openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3166,6 +3166,138 @@
}
}
},
"/addresses/{address}/amount-history-deltas": {
"get": {
"tags": [
"Addresses"
],
"operationId": "getAddressesAddressAmount-history-deltas",
"parameters": [
{
"name": "address",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "address"
}
},
{
"name": "fromTs",
"in": "query",
"required": true,
"schema": {
"type": "integer",
"format": "int64",
"minimum": "0"
}
},
{
"name": "toTs",
"in": "query",
"required": true,
"schema": {
"type": "integer",
"format": "int64",
"minimum": "0"
}
},
{
"name": "interval-type",
"in": "query",
"required": true,
"schema": {
"$ref": "#/components/schemas/IntervalType"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AmountHistory"
},
"example": {
"amountHistory": [
[
1611041396892,
"1"
]
]
}
}
}
},
"400": {
"description": "BadRequest",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequest"
},
"example": {
"detail": "Something bad in the request"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Unauthorized"
},
"example": {
"detail": "You shall not pass"
}
}
}
},
"404": {
"description": "NotFound",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotFound"
},
"example": {
"resource": "wallet-name",
"detail": "wallet-name not found"
}
}
}
},
"500": {
"description": "InternalServerError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalServerError"
},
"example": {
"detail": "Ouch"
}
}
}
},
"503": {
"description": "ServiceUnavailable",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServiceUnavailable"
},
"example": {
"detail": "Self clique unsynced"
}
}
}
}
}
}
},
"/infos": {
"get": {
"tags": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ trait AddressesEndpoints extends BaseEndpoint with QueryParams {
.in(intervalTypeQuery)
.out(jsonBody[AmountHistory])

val getAddressAmountHistoryDeltas
: BaseEndpoint[(Address, TimeInterval, IntervalType), AmountHistory] =
addressesEndpoint.get
.in("amount-history-deltas")
.in(timeIntervalQuery)
.in(intervalTypeQuery)
.out(jsonBody[AmountHistory])

private case class TextCsv() extends CodecFormat {
override val mediaType: MediaType = MediaType.TextCsv
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ trait Documentation
exportTransactionsCsvByAddress,
getAddressAmountHistoryDEPRECATED,
getAddressAmountHistory,
getAddressAmountHistoryDeltas,
getInfos,
getHeights,
listMempoolTransactions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.alephium.protocol.ALPH
import org.alephium.protocol.model.{Address, BlockHash, TransactionId}
import org.alephium.util.{TimeStamp, U256}

// scalastyle:off number.of.methods
object TransactionQueries extends StrictLogging {

@SuppressWarnings(Array("org.wartremover.warts.PublicInference"))
Expand Down Expand Up @@ -371,6 +372,26 @@ object TransactionQueries extends StrictLogging {
""".asAS[(TimeStamp, Option[U256])]
}

def sumAddressOutputsAsDeltas(
address: Address,
from: TimeStamp,
to: TimeStamp,
intervalType: IntervalType
): DBActionSR[(TimeStamp, Option[U256])] = {
val dateGroup = QueryUtil.dateGroupQuery(intervalType)
sql"""
SELECT
LEAST($to, GREATEST($from, #${QueryUtil.extractEpoch(dateGroup)} - 1)) as ts,
SUM(amount)
FROM outputs
WHERE address = $address
AND main_chain = true
AND block_timestamp >= $from
AND block_timestamp <= $to
GROUP BY ts
""".asAS[(TimeStamp, Option[U256])]
}

def sumAddressOutputsDEPRECATED(address: Address, from: TimeStamp, to: TimeStamp)(implicit
ec: ExecutionContext
): DBActionR[U256] = {
Expand Down Expand Up @@ -422,6 +443,27 @@ object TransactionQueries extends StrictLogging {
""".asAS[(TimeStamp, Option[U256])]
}

def sumAddressInputsAsDeltas(
address: Address,
from: TimeStamp,
to: TimeStamp,
intervalType: IntervalType
): DBActionSR[(TimeStamp, Option[U256])] = {
val dateGroup = QueryUtil.dateGroupQuery(intervalType)

sql"""
SELECT
LEAST($to, GREATEST($from, #${QueryUtil.extractEpoch(dateGroup)} - 1)) as ts,
SUM(output_ref_amount)
FROM inputs
WHERE output_ref_address = $address
AND main_chain = true
AND block_timestamp >= $from
AND block_timestamp <= $to
GROUP BY ts
""".asAS[(TimeStamp, Option[U256])]
}

private def buildTransaction(
txHashesTs: ArraySeq[TxByAddressQR],
inputs: ArraySeq[InputsFromTxQR],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ trait TransactionService {
ec: ExecutionContext,
dc: DatabaseConfig[PostgresProfile]
): Future[ArraySeq[(TimeStamp, BigInteger)]]

def getAmountHistoryAsDeltas(
address: Address,
from: TimeStamp,
to: TimeStamp,
intervalType: IntervalType
)(implicit
ec: ExecutionContext,
dc: DatabaseConfig[PostgresProfile]
): Future[ArraySeq[(TimeStamp, BigInteger)]]
}

object TransactionService extends TransactionService {
Expand Down Expand Up @@ -310,6 +320,44 @@ object TransactionService extends TransactionService {
)
}

def getAmountHistoryAsDeltas(
address: Address,
from: TimeStamp,
to: TimeStamp,
intervalType: IntervalType
)(implicit
ec: ExecutionContext,
dc: DatabaseConfig[PostgresProfile]
): Future[ArraySeq[(TimeStamp, BigInteger)]] = {
run(
for {
inputs <- sumAddressInputsAsDeltas(address, from, to, intervalType)
outputs <- sumAddressOutputsAsDeltas(address, from, to, intervalType)
} yield {
val ins = inputs.collect { case (ts, Some(u256)) => (ts, u256) }.toMap
val outs = outputs.collect { case (ts, Some(u256)) => (ts, u256) }.toMap

val timestamps = scala.collection.SortedSet.from(ins.keys ++ outs.keys)

timestamps
.foldLeft(ArraySeq.empty[(TimeStamp, BigInteger)]) { case (res, ts) =>
(ins.get(ts), outs.get(ts)) match {
case (Some(in), Some(out)) =>
val diff = out.v.subtract(in.v)
res :+ (ts, diff)
case (Some(in), None) =>
// No Output, all inputs are spent
res :+ (ts, in.v.negate)
case (None, Some(out)) =>
res :+ (ts, out.v)
case (None, None) =>
res
}
}
}
)
}

def amountHistoryToJsonFlowable(history: Flowable[(BigInteger, TimeStamp)]): Flowable[Buffer] = {
history
.concatMap { case (diff, to: TimeStamp) =>
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/scala/org/alephium/explorer/web/AddressServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,23 @@ class AddressServer(
})
}
}
}),
route(getAddressAmountHistoryDeltas.serverLogic[Future] {
case (address, timeInterval, intervalType) =>
validateTimeInterval(timeInterval, intervalType) {
transactionService
.getAmountHistoryAsDeltas(
address,
timeInterval.from,
timeInterval.to,
intervalType
)
.map { values =>
AmountHistory(values.map { case (ts, value) =>
TimedAmount(ts, value)
})
}
}
})
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,14 @@ trait EmptyTransactionService extends TransactionService {
ec: ExecutionContext,
dc: DatabaseConfig[PostgresProfile]
): Future[ArraySeq[(TimeStamp, BigInteger)]] = ???

def getAmountHistoryAsDeltas(
address: Address,
from: TimeStamp,
to: TimeStamp,
intervalType: IntervalType
)(implicit
ec: ExecutionContext,
dc: DatabaseConfig[PostgresProfile]
): Future[ArraySeq[(TimeStamp, BigInteger)]] = ???
}
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,14 @@ class TransactionServiceSpec extends AlephiumActorSpecLike with DatabaseFixtureF

history is historyDepracted

val deltas = TransactionService
.getAmountHistoryAsDeltas(address, fromTs, toTs, intervalType)
.futureValue
.map { case (ts, sum) => (ts.millis, sum) }
val deltaResult = deltas.map(_._2).fold(java.math.BigInteger.ZERO)(_ add _)

deltaResult is history.last._2

val times = history.map(_._1)

// Test that history is always ordered correctly
Expand Down
Loading
Loading