Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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)
}