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

sss.openstar.wallet.WalletPersistence.scala Maven / Gradle / Ivy

package sss.openstar.wallet

import sss.ancillary.ByteArrayEncodedStrOps._
import sss.ancillary.Logging
import sss.db._
import sss.db.ops.DbOps.{DbRunOps, FutureTxOps, OptFutureTxOps}
import sss.openstar.UniqueNodeIdentifier
import sss.openstar.balanceledger._
import sss.openstar.contract.ContractSerializer._
import sss.openstar.ledger._
import sss.openstar.schemamigration.SqlSchemaNames.ColumnNames._
import sss.openstar.schemamigration.SqlSchemaNames.TableNames.walletsTableName
import sss.openstar.wallet.WalletPersistence.LodgementStatus.LodgementStatus

import java.util.Date

/**
 * Created by alan on 6/28/16.
 */

object WalletPersistence {

  object LodgementStatus extends Enumeration {
    type LodgementStatus = Value
    val UnSpent = Value(0)
    val Spent = Value(1)
    val InFlight = Value(2)
  }

  object Lodgement {

    def apply(txIndex: TxIndex, txOutput: TxOutput, inBlock: Long): Lodgement = {
      apply(None, LodgementStatus.UnSpent, txIndex, new LazyTxOutput(txOutput.amount, Right(txOutput.encumbrance)), inBlock)
    }

    def apply(status: LodgementStatus, txIndex: TxIndex, txOutput: TxOutput, inBlock: Long): Lodgement = {
      apply(None, status, txIndex, txOutput, inBlock)
    }

    def apply(client: Option[UniqueNodeIdentifier], status: LodgementStatus, txIndex: TxIndex, txOutput: TxOutput, inBlock: Long): Lodgement = {
      apply(client, status, txIndex, new LazyTxOutput(txOutput.amount, Right(txOutput.encumbrance)), inBlock)
    }

  }

  //case class Lodgement2(status: Int, encCol: Array[Byte], r: Row)
  case class Lodgement(

                        client: Option[UniqueNodeIdentifier],
                        status: LodgementStatus,
                        txIndex: TxIndex,
                        txOutput: LazyTxOutput,
                        inBlock: Long)

}

//TODO take IO off main thread.
// TODO delete old entries?
class WalletPersistence(uniqueTag: String, db: Db) extends Logging {

  import WalletPersistence.LodgementStatus._
  import WalletPersistence._

  private[wallet] implicit val implDb = db
  import db.syncRunContext

  def list(ledgerId: LedgerId): Seq[Lodgement] = {
    table
      .filter(
        where(
          identityCol -> uniqueTag,
          ledgerIdCol -> ledgerId.id
        )
      )
      .dbRunSyncGet
      .map(toLodgement)
  }


  def markSpent(txIndex: TxIndex): Option[Lodgement] = {

    for {
      rOpt <- table.find(identityCol -> uniqueTag, txIdCol -> txIndex.txId.toBase64Str, txIdIndxCol -> txIndex.index)
      _ <- (for {
        r <- rOpt
        if r.int(statusCol) != Spent.id
        _ = log.info(s"$uniqueTag marking spent txIndex $txIndex")
      } yield(table.updateRow(Map(idCol -> r.id, versionCol -> r.int(versionCol), statusCol -> Spent)))).toFutureTxOpt

    } yield rOpt.map(toLodgement)

  }.dbRunSyncGet


  def markUnSpent(txIndex: TxIndex, activeAtHeight: Long): Unit = {

    for {
      rowOpt <- table.find(identityCol -> uniqueTag, txIdCol -> txIndex.txId.toBase64Str, txIdIndxCol -> txIndex.index)
      _ <- rowOpt.map(r => {
        require(r.int(statusCol) == InFlight.id)
        table.updateRow(Map(idCol -> r.id, statusCol -> UnSpent, blockHeightCol -> activeAtHeight))
      }).toFutureTxOpt
      _ = log.info(s"$uniqueTag marking UN spent txIndex $txIndex")
    } yield ()

  }.dbRunSyncGet

  def markInFlight(txIndex: TxIndex, reclaimIfNotSpentBlock: Long): Unit =
    markInFlightFTx(txIndex, reclaimIfNotSpentBlock).dbRunSyncGet

  def markInFlightFTx(txIndex: TxIndex, reclaimIfNotSpentBlock: Long): FutureTx[Unit] = {

    for {
      rowOpt <- table.find(identityCol -> uniqueTag, txIdCol -> txIndex.txId.toBase64Str, txIdIndxCol -> txIndex.index)
      _ <- rowOpt.map(r => {
        require(r.int(statusCol) == UnSpent.id)
        require(r.long(blockHeightCol) <= reclaimIfNotSpentBlock)
        table.updateRow(
          Map(
            idCol -> r.id,
            txIdIndxCol -> txIndex.index,
            versionCol -> r.int(versionCol),
            statusCol -> InFlight,
            blockHeightCol -> reclaimIfNotSpentBlock
          )
        )
      }).toFutureTxOpt
      _ = log.info(s"$uniqueTag marking IN FLIGHT txIndex $txIndex")
    } yield ()

  }

  def track(lodgement: Lodgement)(implicit ledgerId: LedgerId): Option[Lodgement] = trackFTx(lodgement).dbRunSyncGet

  // for debug
  def find(txIndex: TxIndex): Option[Row] = {
    val txId = txIndex.txId.toBase64Str
    val txIdIndx = txIndex.index

    table
      .find(where(identityCol -> uniqueTag, txIdIndxCol -> txIdIndx, txIdCol -> txId))
      .dbRunSyncGet
  }

  def trackFTx(lodgement: Lodgement)(implicit ledgerId: LedgerId): FutureTx[Option[Lodgement]] =  {

    val txId = lodgement.txIndex.txId.toBase64Str
    val txIdIndx = lodgement.txIndex.index

    table.insert(Map(
        identityCol -> uniqueTag,
        txIdCol -> txId,
        ledgerIdCol -> ledgerId.id,
        versionCol -> 1,
        txIdIndxCol -> txIdIndx,
        amountCol -> lodgement.txOutput.amount,
        encumbranceCol -> lodgement.txOutput.encumbrance.toBytes,
        blockHeightCol -> lodgement.inBlock,
        createdAtCol -> new Date().getTime,
        statusCol -> lodgement.status.id,
      )).map(row => {
      log.info(s"$row $uniqueTag add lodgement")
      //NB toLodgement will DROP the client, it's not stored in this table
      Some(lodgement)
    }) recoverWithDb {
      case e =>
        //log.info(s"updating fail ${e.getClass.getCanonicalName}")
        table.update(Map(statusCol -> lodgement.status.id), where(
          identityCol -> uniqueTag,
          txIdCol -> txId,
          txIdIndxCol -> txIdIndx,
          statusCol -> InFlight,
          ledgerIdCol -> ledgerId.id
        )).recoverDb {
          case e => log.info(s"update error $e")
        }.map(_ => None)
    }

  }

  def listUnSpent(blockHeight: Long)(implicit ledgerId: LedgerId): LazyList[Lodgement] = {

    val pageFilter: Where = where(
      identityCol -> uniqueTag,
      statusCol -> UnSpent,
      ledgerIdCol -> ledgerId.id
    ) and where(
      s"$blockHeightCol <= ?", blockHeight
    ) orderAsc(amountCol)

    table.toPaged(2000, pageFilter).toStream.map(toLodgement)

  }

  private def toLodgement(r: Row): Lodgement = {

    val sCol = r.int(statusCol)
    val encCol = Left(r.arrayByte(encumbranceCol))
    val txId = r.string(txIdCol).asTxId
    val txIndx = r.int(txIdIndxCol)
    val amount = r.int(amountCol)
    val height = r.long(blockHeightCol)

    Lodgement(
      None,
      LodgementStatus(sCol),
      TxIndex(txId,
        txIndx),
      new LazyTxOutput(amount, encCol),
      height)
  }

  private val table = db.table(walletsTableName)


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy