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

Remove flow database dependency #574

Merged
merged 32 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5820e3a
add flyway nav events table
nullpointer0x00 Nov 5, 2024
d59531d
add nav events domain object
nullpointer0x00 Nov 6, 2024
a463da6
add queries
nullpointer0x00 Nov 6, 2024
666f02b
add queries add tests
nullpointer0x00 Nov 6, 2024
f16cc62
add nav fetcher
nullpointer0x00 Nov 6, 2024
383b829
refactor fetchers, add utils file for shared methods, add tests
nullpointer0x00 Nov 7, 2024
e3214c9
add service to read marker nav events, add tests
nullpointer0x00 Nov 7, 2024
54b203a
refactor test in prep for more
nullpointer0x00 Nov 7, 2024
9176ef8
add id to nav table
nullpointer0x00 Nov 7, 2024
77bdcbc
add scope event parsing
nullpointer0x00 Nov 7, 2024
0446f53
add internal nav datasource
nullpointer0x00 Nov 7, 2024
6d40313
fix a few things, add tests
nullpointer0x00 Nov 12, 2024
5083647
fix lint
nullpointer0x00 Nov 12, 2024
625c6ab
fix database setup for tests
nullpointer0x00 Nov 12, 2024
6951112
fix lint
nullpointer0x00 Nov 12, 2024
e23747e
update sql script
nullpointer0x00 Nov 12, 2024
e3c0158
fix test
nullpointer0x00 Nov 12, 2024
ed09fb4
tx hash can be null
nullpointer0x00 Nov 12, 2024
c79af80
wire in nav service
nullpointer0x00 Nov 13, 2024
13faa8e
add temp log
nullpointer0x00 Nov 13, 2024
08120fd
fix logging
nullpointer0x00 Nov 13, 2024
8023eef
add temp log
nullpointer0x00 Nov 13, 2024
df01f92
fix lint
nullpointer0x00 Nov 13, 2024
c83e5fc
update loggin
nullpointer0x00 Nov 13, 2024
cd5e017
fix lint
nullpointer0x00 Nov 13, 2024
472885c
fix millis to epoch
nullpointer0x00 Nov 13, 2024
ccf982b
remove temporary logging
nullpointer0x00 Nov 13, 2024
1be2713
remove unused import
nullpointer0x00 Nov 13, 2024
c9dfe43
clean up a few things
nullpointer0x00 Nov 14, 2024
5e9b619
add change log
nullpointer0x00 Nov 14, 2024
495702b
remove some old references
nullpointer0x00 Nov 14, 2024
a46bee3
fix lint
nullpointer0x00 Nov 15, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
## Unreleased

### Features
* Remove flow dependency and collect on-chain navs in local database [#574](https://github.com/provenance-io/explorer-service/pull/574)
* Add hash price support for nav values from on chain events [#543](https://github.com/provenance-io/explorer-service/pull/543)
* Integrate on-chain NAV data into historical hash price calculations [#555](https://github.com/provenance-io/explorer-service/pull/555)
* Use on-chain NAV data for latest asset pricing on markers [#556](https://github.com/provenance-io/explorer-service/pull/556)
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ object Versions {
const val Jupiter = "5.9.1"
const val SpringMockk = "3.1.1"
const val Kotest = "5.5.4"
const val H2Database = "1.4.200"
const val H2Database = "2.1.214"
}

object Libraries {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SELECT 'Add nav events table' AS comment;

CREATE TABLE IF NOT EXISTS nav_events (
id SERIAL PRIMARY KEY,
block_height INT NOT NULL,
block_time TIMESTAMP NOT NULL,
tx_hash TEXT,
event_order INT,
event_type TEXT,
scope_id TEXT,
denom TEXT,
price_amount BIGINT,
price_denom TEXT,
volume BIGINT,
source TEXT,
UNIQUE (block_height, tx_hash, event_order)
);

CREATE INDEX IF NOT EXISTS idx_nav_events_block_time ON nav_events(block_time);
CREATE INDEX IF NOT EXISTS idx_nav_events_denom ON nav_events(denom);
CREATE INDEX IF NOT EXISTS idx_nav_events_scope_id ON nav_events(scope_id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package io.provenance.explorer.domain.entities

import io.provenance.explorer.domain.extensions.execAndMap
import io.provenance.explorer.domain.extensions.toDateTime
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ColumnType
import org.jetbrains.exposed.sql.VarCharColumnType
import org.jetbrains.exposed.sql.insertIgnore
import org.jetbrains.exposed.sql.jodatime.DateColumnType
import org.jetbrains.exposed.sql.jodatime.datetime
import org.jetbrains.exposed.sql.transactions.transaction
import org.joda.time.DateTime
import java.sql.ResultSet

object NavEventsTable : IntIdTable(name = "nav_events") {
val blockHeight = integer("block_height")
val blockTime = datetime("block_time")
val txHash = text("tx_hash")
val eventOrder = integer("event_order")
val eventType = text("event_type")
val scopeId = text("scope_id").nullable()
val denom = text("denom").nullable()
val priceAmount = long("price_amount").nullable()
val priceDenom = text("price_denom").nullable()
val volume = long("volume")
val dataSource = text("source")

init {
uniqueIndex("nav_events_unique_idx", blockHeight, txHash, eventOrder)
}
}

class NavEventsRecord(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<NavEventsRecord>(NavEventsTable) {
fun insert(
blockHeight: Int,
blockTime: DateTime,
txHash: String,
eventOrder: Int,
eventType: String,
scopeId: String?,
denom: String?,
priceAmount: Long?,
priceDenom: String?,
volume: Long,
source: String
) = transaction {
NavEventsTable.insertIgnore {
it[this.blockHeight] = blockHeight
it[this.blockTime] = blockTime
it[this.txHash] = txHash
it[this.eventOrder] = eventOrder
it[this.eventType] = eventType
it[this.scopeId] = scopeId
it[this.denom] = denom
it[this.priceAmount] = priceAmount
it[this.priceDenom] = priceDenom
it[this.volume] = volume
it[this.dataSource] = source
}
}

fun getNavEvents(
denom: String? = null,
scopeId: String? = null,
fromDate: DateTime? = null,
toDate: DateTime? = null,
priceDenoms: List<String>? = null
) = transaction {
var query = """
SELECT block_height, block_time, tx_hash, event_order,
event_type, scope_id, denom, price_amount, price_denom, volume, source
FROM nav_events
WHERE 1=1
""".trimIndent()

val args = mutableListOf<Pair<ColumnType, *>>()

denom?.let {
query += " AND denom = ?"
args.add(Pair(VarCharColumnType(), it))
} ?: scopeId?.let {
query += " AND scope_id = ?"
args.add(Pair(VarCharColumnType(), it))
}

fromDate?.let {
query += " AND block_time >= ?"
args.add(Pair(DateColumnType(true), it))
}

toDate?.let {
query += " AND block_time <= ?"
args.add(Pair(DateColumnType(true), it))
}

priceDenoms?.let {
if (it.isNotEmpty()) {
val placeholders = it.joinToString(", ") { "?" }
query += " AND price_denom IN ($placeholders)"
it.forEach { denom ->
args.add(Pair(VarCharColumnType(), denom))
}
}
}

query += " ORDER BY block_height DESC, event_order DESC"

query.execAndMap(args) {
NavEvent(
it.getInt("block_height"),
it.getTimestamp("block_time").toDateTime(),
it.getString("tx_hash"),
it.getInt("event_order"),
it.getString("event_type"),
it.getString("scope_id"),
it.getString("denom"),
it.getLong("price_amount"),
it.getString("price_denom"),
it.getLong("volume"),
it.getString("source")
)
}
}

fun getLatestNavEvents(
priceDenom: String,
includeMarkers: Boolean,
includeScopes: Boolean,
fromDate: DateTime? = null
) = transaction {
require(includeMarkers || includeScopes) { "Either includeMarkers or includeScope must be true" }

var query = """
SELECT DISTINCT ON (denom, scope_id)
block_height, block_time, tx_hash, event_order, event_type,
scope_id, denom, price_amount, price_denom, volume, source
FROM nav_events
WHERE price_denom = ?
""".trimIndent()

val args = mutableListOf<Pair<ColumnType, *>>(
Pair(VarCharColumnType(), priceDenom)
)

fromDate?.let {
query += " AND block_time >= ?"
args.add(Pair(DateColumnType(true), it))
}

when {
includeMarkers && includeScopes -> query += " AND (denom IS NOT NULL OR scope_id IS NOT NULL)"
includeMarkers -> query += " AND denom IS NOT NULL"
includeScopes -> query += " AND scope_id IS NOT NULL"
}

query += " ORDER BY denom, scope_id, block_height DESC, event_order DESC"

query.execAndMap(args) {
NavEvent(
it.getInt("block_height"),
it.getTimestamp("block_time").toDateTime(),
it.getString("tx_hash"),
it.getInt("event_order"),
it.getString("event_type"),
it.getString("scope_id"),
it.getString("denom"),
it.getLong("price_amount"),
it.getString("price_denom"),
it.getLong("volume"),
it.getString("source")
)
}
}
}

var blockHeight by NavEventsTable.blockHeight
var blockTime by NavEventsTable.blockTime
var txHash by NavEventsTable.txHash
var eventOrder by NavEventsTable.eventOrder
var eventType by NavEventsTable.eventType
var scopeId by NavEventsTable.scopeId
var denom by NavEventsTable.denom
var priceAmount by NavEventsTable.priceAmount
var priceDenom by NavEventsTable.priceDenom
var volume by NavEventsTable.volume
var source by NavEventsTable.dataSource
}

data class NavPrice(
val blockHeight: Int,
val blockTime: DateTime,
val txHash: String,
val eventOrder: Int,
val eventType: String,
val scopeId: String?,
val denom: String,
val priceAmount: Long,
val priceDenom: String,
val volume: Long,
val source: String
) {
constructor(rs: ResultSet) : this(
rs.getInt("block_height"),
rs.getTimestamp("block_time").toDateTime(),
rs.getString("tx_hash"),
rs.getInt("event_order"),
rs.getString("event_type"),
rs.getString("scope_id"),
rs.getString("denom"),
rs.getLong("price_amount"),
rs.getString("price_denom"),
rs.getLong("volume"),
rs.getString("source")
)

constructor(record: NavEventsRecord) : this(
record.blockHeight,
record.blockTime,
record.txHash,
record.eventOrder,
record.eventType,
record.scopeId,
record.denom!!,
record.priceAmount!!,
record.priceDenom!!,
record.volume,
record.source
)
}

data class NavEvent(
val blockHeight: Int,
val blockTime: DateTime,
val txHash: String?,
val eventOrder: Int,
val eventType: String,
val scopeId: String?,
val denom: String?,
val priceAmount: Long?,
val priceDenom: String?,
val volume: Long,
val source: String
) {
constructor(record: NavEventsRecord) : this(
record.blockHeight,
record.blockTime,
record.txHash,
record.eventOrder,
record.eventType,
record.scopeId,
record.denom,
record.priceAmount,
record.priceDenom,
record.volume,
record.source
)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package io.provenance.explorer.domain.extensions

import io.provenance.explorer.model.base.USD_LOWER
import io.provlabs.flow.api.NavEvent
import java.math.BigDecimal
import java.math.RoundingMode

fun io.provlabs.flow.api.NavEvent.calculateUsdPricePerUnit(): BigDecimal {
return calculateUsdPrice(this.priceDenom, this.priceAmount, this.volume)
}

fun io.provenance.explorer.domain.entities.NavEvent.calculateUsdPricePerUnit(): BigDecimal {
return calculateUsdPrice(this.priceDenom, this.priceAmount, this.volume)
}

/**
* Calculates the USD price per unit for a NAV event.
*
Expand All @@ -13,17 +20,17 @@ import java.math.RoundingMode
*
* @return The USD price per unit or `BigDecimal.ZERO` if not a USD event or volume is 0.
*/
fun NavEvent.calculateUsdPricePerUnit(): BigDecimal {
if (this.priceDenom != USD_LOWER) {
private fun calculateUsdPrice(priceDenom: String?, priceAmount: Long?, volume: Long): BigDecimal {
if (priceDenom != USD_LOWER) {
return BigDecimal.ZERO
}

if (this.volume == 0L) {
if (volume == 0L) {
return BigDecimal.ZERO
}

return BigDecimal(this.priceAmount)
return BigDecimal(priceAmount ?: 0)
.setScale(3, RoundingMode.DOWN)
.divide(BigDecimal(1000), RoundingMode.DOWN)
.divide(BigDecimal(this.volume), 3, RoundingMode.DOWN)
.divide(BigDecimal(volume), 3, RoundingMode.DOWN)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.provenance.explorer.domain.entities.AssetPricingRecord
import io.provenance.explorer.domain.entities.BaseDenomType
import io.provenance.explorer.domain.entities.MarkerCacheRecord
import io.provenance.explorer.domain.entities.MarkerUnitRecord
import io.provenance.explorer.domain.entities.NavEventsRecord
import io.provenance.explorer.domain.entities.TxMarkerJoinRecord
import io.provenance.explorer.domain.exceptions.requireNotNullToMessage
import io.provenance.explorer.domain.extensions.calculateUsdPricePerUnit
Expand Down Expand Up @@ -244,22 +245,22 @@ class AssetService(
val now = OffsetDateTime.now().withOffsetSameInstant(ZoneOffset.UTC)
logger.info("Updating asset pricing, last run at: $assetPricinglastRun")

val latestPrices = flowApiGrpcClient.getAllLatestNavPrices(
val latestPrices = NavEventsRecord.getLatestNavEvents(
priceDenom = USD_LOWER,
includeMarkers = true,
includeScopes = false,
fromDate = assetPricinglastRun?.toDateTime()
)
latestPrices.forEach { price ->
if (price.denom != UTILITY_TOKEN) {
val marker = getAssetRaw(price.denom)
val marker = getAssetRaw(price.denom!!)
insertAssetPricing(
marker = marker,
markerDenom = price.denom,
markerAddress = marker.second.markerAddress,
pricingDenom = price.priceDenom,
pricingDenom = price.priceDenom!!,
pricingAmount = price.calculateUsdPricePerUnit(),
timestamp = DateTime(price.blockTime * 1000)
timestamp = price.blockTime
)
}
}
Expand Down
Loading
Loading