Skip to content

Commit

Permalink
fea(iris): ATL-1923 Implement in-memory ledger (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
pva701 authored Sep 30, 2022
1 parent 3f381cd commit 1a592af
Show file tree
Hide file tree
Showing 36 changed files with 654 additions and 53 deletions.
2 changes: 1 addition & 1 deletion iris/api/grpc/protocol/did_operations.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ message DocumentDefinition {
message CreateDid {
bytes initial_update_commitment = 1;
bytes initial_recovery_commitment = 2;
string storage = 3;
string ledger = 3;
DocumentDefinition document = 4;
}

Expand Down
2 changes: 1 addition & 1 deletion iris/api/grpc/protocol/dlt.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ message IrisOperation {
}

// List of operations which will be stored in the blockchain transaction metadata
message AtalaObject {
message IrisObject {
repeated IrisOperation operations = 1;
}
2 changes: 2 additions & 0 deletions iris/api/grpc/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ message IrisOperationInfo {
UpdateDid update_did = 3;
RecoverDid recovery_did = 4;
DeactivateDid deactivate_did = 5;
IssueCredentialsBatch issue_credentials_batch = 6;
RevokeCredentials revoke_credentials = 7;
}
}

Expand Down
5 changes: 5 additions & 0 deletions iris/service/.scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version = 3.5.8
runner.dialect = scala3

maxColumn = 120
trailingCommas = preserve
27 changes: 18 additions & 9 deletions iris/service/build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Dependencies._
import sbt.Keys.testFrameworks
import sbtghpackages.GitHubPackagesPlugin.autoImport._

ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "3.1.3"
Expand All @@ -11,13 +13,23 @@ ThisBuild / apiBaseDirectory := baseDirectory.value / "../api"
// Project definitions
lazy val root = project
.in(file("."))
.aggregate(core, sql, `api-server`)
.aggregate(core, sql, server)

lazy val core = project
.in(file("core"))
.settings(
name := "iris-core",
libraryDependencies ++= coreDependencies
githubTokenSource := TokenSource.Environment("ATALA_GITHUB_TOKEN"),
resolvers += Resolver
.githubPackages("input-output-hk", "atala-prism-sdk"),
// Needed for Kotlin coroutines that support new memory management mode
resolvers +=
"JetBrains Space Maven Repository" at "https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven",
libraryDependencies ++= coreDependencies,
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"),
// gRPC settings
Compile / PB.targets := Seq(scalapb.gen() -> (Compile / sourceManaged).value / "scalapb"),
Compile / PB.protoSources := Seq(apiBaseDirectory.value / "grpc")
)

lazy val sql = project
Expand All @@ -28,13 +40,10 @@ lazy val sql = project
)
.dependsOn(core)

lazy val `api-server` = project
.in(file("api-server"))
lazy val server = project
.in(file("server"))
.settings(
name := "iris-api-server",
libraryDependencies ++= apiServerDependencies,
// gRPC settings
Compile / PB.targets := Seq(scalapb.gen() -> (Compile / sourceManaged).value / "scalapb"),
Compile / PB.protoSources := Seq(apiBaseDirectory.value / "grpc")
name := "iris-server",
libraryDependencies ++= serverDependencies,
)
.dependsOn(core, sql)
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ final case class IrisNotification(foo: String)

final case class IrisOperationId(id: String)
final case class IrisOperation(foo: String)
final case class SignedIrisOperation(foo: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.iohk.atala.iris.core.model.ledger

case class Funds(lovelaces: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.iohk.atala.iris.core.model.ledger

import enumeratum.{Enum, EnumEntry}

import scala.collection.immutable.ArraySeq

case class Ledger(name: String)

object Ledger{
val InMemory: Ledger = Ledger("in-memory")
val Mainnet: Ledger = Ledger("mainnet")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.iohk.atala.iris.core.model.ledger

case class TransactionDetails(id: TransactionId, status: TransactionStatus)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.iohk.atala.iris.core.model.ledger

import com.typesafe.config.ConfigMemorySize
import io.iohk.atala.shared.{HashValue, HashValueConfig, HashValueFrom}

import scala.collection.immutable.ArraySeq

class TransactionId(bytes: ArraySeq[Byte]) extends HashValue {
override def value: ArraySeq[Byte] = bytes
}

object TransactionId extends HashValueFrom[TransactionId] {

override val config: HashValueConfig = HashValueConfig(
ConfigMemorySize.ofBytes(32)
)

override protected def constructor(value: ArraySeq[Byte]): TransactionId =
new TransactionId(value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.iohk.atala.iris.core.model.ledger

import enumeratum.{Enum, EnumEntry}
import enumeratum.EnumEntry.Snakecase
import scala.collection.immutable.ArraySeq

sealed trait TransactionStatus extends EnumEntry with Snakecase

object TransactionStatus extends Enum[TransactionStatus] {
val values = ArraySeq(InMempool, Submitted, Expired, InLedger)

case object InMempool extends TransactionStatus
case object Submitted extends TransactionStatus
case object Expired extends TransactionStatus
case object InLedger extends TransactionStatus
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package io.iohk.atala.iris.core.repository

import io.iohk.atala.iris.core.model.{IrisOperation, IrisOperationId, SignedIrisOperation}
import io.iohk.atala.iris.core.model as model
import zio.*

// TODO: replace with actual implementation
trait OperationsRepository[F[_]] {
def getOperation(id: IrisOperationId): F[IrisOperation]
def saveOperations(ops: Seq[SignedIrisOperation]): F[Unit]
def getOperation(id: model.IrisOperationId): F[model.IrisOperation]
def saveOperations(ops: Seq[model.IrisOperation]): F[Unit]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.iohk.atala.iris.core.service

import io.iohk.atala.iris.core.model.ledger.TransactionStatus.{InLedger, InMempool}
import io.iohk.atala.iris.core.model.ledger.{Funds, TransactionDetails, TransactionId}
import io.iohk.atala.iris.core.service.InmemoryUnderlyingLedgerService.{CardanoBlock, CardanoTransaction, Config}
import io.iohk.atala.iris.proto.dlt as proto
import io.iohk.atala.prism.crypto.Sha256
import zio.stm.*
import zio.*

object InmemoryUnderlyingLedgerService {
case class Config(blockEvery: Duration, initialFunds: Funds, txFee: Funds)

case class CardanoTransaction(operations: Seq[proto.IrisOperation]) {
lazy val transactionId: TransactionId = {
val objectBytes = proto.IrisObject(operations).toByteArray
val hash = Sha256.compute(objectBytes)
TransactionId
.from(hash.getValue)
.getOrElse(throw new RuntimeException("Unexpected invalid hash"))
}
}

case class CardanoBlock(txs: Seq[CardanoTransaction])

def layer(config: Config): ULayer[InmemoryUnderlyingLedgerService] = ZLayer.fromZIO {
for {
mempoolRef <- TRef.make(Vector[CardanoTransaction]()).commit
blocksRef <- TRef.make(Vector[CardanoBlock]()).commit
initialBalance <- TRef.make(config.initialFunds).commit
srv = InmemoryUnderlyingLedgerService(config, mempoolRef, blocksRef, initialBalance)
_ <- srv.startBackgroundProcess()
} yield srv
}
}

class InmemoryUnderlyingLedgerService(
config: Config,
mempoolRef: TRef[Vector[CardanoTransaction]],
blocksRef: TRef[Vector[CardanoBlock]],
balanceRef: TRef[Funds]
) extends UnderlyingLedgerService {

override def publish(operations: Seq[proto.IrisOperation]): IO[LedgerError, Unit] =
STM.atomically {
for {
curFunds <- balanceRef.get
newFunds <- STM.cond(
curFunds.lovelaces >= config.txFee.lovelaces,
Funds(curFunds.lovelaces - config.txFee.lovelaces),
LedgerError("Insufficient wallet balance")
)
_ <- balanceRef.set(newFunds)
_ <- mempoolRef.update(_.appended(CardanoTransaction(operations)))
} yield ()
}

override def getTransactionDetails(transactionId: TransactionId): IO[LedgerError, TransactionDetails] =
STM.atomically {
for {
mempool <- mempoolRef.get
blockchain <- blocksRef.get
tdetails <- STM
.fromOption {
mempool
.find(_.transactionId == transactionId)
.map(_ => TransactionDetails(transactionId, InMempool))
}
.orElse {
STM.fromOption {
blockchain
.find(block => block.txs.exists(t => t.transactionId == transactionId))
.map(_ => TransactionDetails(transactionId, InLedger))
}
}
.orElseFail(LedgerError(s"Couldn't find tx $transactionId"))
} yield tdetails
}

override def deleteTransaction(transactionId: TransactionId): IO[LedgerError, Unit] = STM.atomically {
for {
mempool <- mempoolRef.get
_ <- STM.cond(
mempool.exists(_.transactionId == transactionId),
(),
LedgerError(s"Transaction $transactionId not found in the mempool")
)
_ <- mempoolRef.update(m => m.filter(_.transactionId != transactionId))
_ <- balanceRef.update(b => Funds(b.lovelaces + config.txFee.lovelaces))
} yield ()
}

override def getWalletBalance: IO[LedgerError, Funds] = balanceRef.get.commit

def getMempool: UIO[List[CardanoTransaction]] = mempoolRef.get.commit.map(_.toList)

def getBlocks: UIO[List[CardanoBlock]] = blocksRef.get.commit.map(_.toList)

private[service] def startBackgroundProcess(): UIO[Unit] = STM
.atomically {
for {
// Craft a new block from mempool transactions
txs <- mempoolRef.modify(old => (old, Vector.empty))
_ <- blocksRef.update(_.appended(CardanoBlock(txs)))
} yield ()
}
.repeat(Schedule.spaced(config.blockEvery))
.fork
.map(_ => ())
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package io.iohk.atala.iris.core.service

import io.iohk.atala.iris.core.model.SignedIrisOperation
import io.iohk.atala.iris.proto.dlt as proto
import zio.*

// TODO: replace with actual implementation
trait PublishingService {
def publishOperations(op: SignedIrisOperation): UIO[Unit]
def publishOperation(op: proto.IrisOperation): UIO[Unit]
}

object MockPublishingService {
val layer: ULayer[PublishingService] = ZLayer.succeed {
new PublishingService {
override def publishOperations(op: SignedIrisOperation): UIO[Unit] = ZIO.unit
override def publishOperation(op: proto.IrisOperation): UIO[Unit] = ZIO.unit
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.iohk.atala.iris.core.service

import io.iohk.atala.iris.proto.dlt as proto
import io.iohk.atala.iris.core.model.IrisOperation
import io.iohk.atala.iris.core.model.ledger.{Funds, TransactionDetails, TransactionId}
import zio.{IO, UIO}

case class LedgerError(msg: String) extends RuntimeException(msg)

trait UnderlyingLedgerService {
// def getType: Ledger

def publish(operations: Seq[proto.IrisOperation]): IO[LedgerError, Unit]

def getTransactionDetails(transactionId: TransactionId): IO[LedgerError, TransactionDetails]

def deleteTransaction(transactionId: TransactionId): IO[LedgerError, Unit]

def getWalletBalance: IO[LedgerError, Funds]
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package io.iohk.atala.iris.core.worker

import io.iohk.atala.iris.core.model.SignedIrisOperation
import io.iohk.atala.iris.core.service.PublishingService
import io.iohk.atala.iris.proto.dlt as proto
import zio.{UIO, ULayer, ZIO, ZLayer}

trait PublishingScheduler {
def scheduleOperations(op: SignedIrisOperation): UIO[Unit]
def scheduleOperations(op: proto.IrisOperation): UIO[Unit]
}

object MockPublishingScheduler {
val layer: ULayer[PublishingScheduler] = ZLayer.succeed {
new PublishingScheduler {
def scheduleOperations(op: SignedIrisOperation): UIO[Unit] = ZIO.unit
def scheduleOperations(op: proto.IrisOperation): UIO[Unit] = ZIO.unit
}
}
}
Loading

0 comments on commit 1a592af

Please sign in to comment.