sss.openstar.message.UtxoMonitor.scala Maven / Gradle / Ivy
package sss.openstar.message
import java.sql.SQLIntegrityConstraintViolationException
import java.time.LocalDateTime
import sss.db._
import sss.openstar.balanceledger.TxIndex
import sss.openstar.util.DateOps._
import sss.openstar.schemamigration.SqlSchemaNames.ColumnNames._
import sss.db.ops.DbOps.{DbRunOps, FutureTxOps}
import sss.openstar.schemamigration.SqlSchemaNames.TableNames.utxoWatchTableName
import scala.util.Try
trait MonitoredUtxo {
val txIndex: TxIndex
}
case class ConsumedMonitoredUtxo(txIndex: TxIndex,
atHeight: Long,
noticed: LocalDateTime) extends MonitoredUtxo
case class UnConsumedMonitoredUtxo(txIndex: TxIndex) extends MonitoredUtxo
trait UtxoWatch extends (TxIndex => Try[TxIndex])
trait UtxoQuery extends (TxIndex => Option[MonitoredUtxo])
class UtxoMonitor(maxRowsBeforePrune: Int)(implicit db: Db) {
lazy private val table = db.table(utxoWatchTableName)
val utxoWatch: UtxoWatch = watch
/**
* Use this only if dealing with a TxIndex that does not exists but will in the future,
* Normally a TxIndex exists before anyone is interested in it being consumed.
* If you want to watch a tx Index that will exist sometime in the future - use this.
* @param index
* @return
*/
def watch(index: TxIndex): Try[TxIndex] = ({
table.insert(Map(
txIdCol -> index.txId,
indxCol -> index.index
))
} map (_ => index) recoverDb {
case _: SQLIntegrityConstraintViolationException =>
//already watched, could happen if the utxo is watched as sent and then again as received, no problem
index
}).dbRunSync
def unwatch(index: TxIndex): Try[Boolean] = {
table.delete(
where(
txIdCol -> index.txId,
indxCol -> index.index,
)
)
}.dbRunSync.map(_ == 1)
def query(index: TxIndex): Option[MonitoredUtxo] = {
table.find(
where(
txIdCol -> index.txId,
indxCol -> index.index
)
).dbRunSyncGet.map(rowToConsumed)
}
private def rowToConsumed(r: Row): MonitoredUtxo = {
val index = TxIndex(
r.arrayByte(txIdCol),
r.int(indxCol)
)
r.longOpt(heightCol) match {
case None => UnConsumedMonitoredUtxo(index)
case Some(height) => ConsumedMonitoredUtxo(index, height, r.long(whenCol).toLocalDateTime)
}
}
private def updateToConsumedFTx(index: TxIndex, height: Long): FutureTx[Int] = {
table.update(
Map(
heightCol -> height,
whenCol -> LocalDateTime.now.toMillis
),
where(
txIdCol -> index.txId,
indxCol -> index.index
)
)
}
def markConsumed(index: TxIndex, height: Long): Try[Unit] = {
table.insert(
Map(
heightCol -> height,
whenCol -> LocalDateTime.now.toMillis,
txIdCol -> index.txId,
indxCol -> index.index
)
)
}.recoverWithDb {
case _: SQLIntegrityConstraintViolationException =>
updateToConsumedFTx(index, height)
}.dbRunSync.map(_ => ())
def count: Long = table.count.dbRunSyncGet
def prune(): Try[Int] = Try {
val ids = table.filter(
where()
.orderBy(OrderDesc(heightCol, NullOrder.NullsFirst))
.limit(maxRowsBeforePrune, Int.MaxValue)
).dbRunSyncGet.map(_.id).toSet
if(ids.nonEmpty) {
(table delete(
where(idCol) in ids
)).dbRunSyncGet
} else 0
}
}