All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy