Skip to content

Commit

Permalink
[ATL-1923] feat(iris): Implement in-memory ledger
Browse files Browse the repository at this point in the history
  • Loading branch information
pva701 committed Sep 29, 2022
1 parent 0b1b089 commit 901ddd8
Show file tree
Hide file tree
Showing 29 changed files with 363 additions and 35 deletions.
23 changes: 15 additions & 8 deletions iris/service/build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Dependencies._
import sbtghpackages.GitHubPackagesPlugin.autoImport._

ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "3.1.3"
Expand All @@ -11,13 +12,22 @@ 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,
// 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 +38,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",
name := "iris-server",
libraryDependencies ++= apiServerDependencies,
// gRPC settings
Compile / PB.targets := Seq(scalapb.gen() -> (Compile / sourceManaged).value / "scalapb"),
Compile / PB.protoSources := Seq(apiBaseDirectory.value / "grpc")
)
.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,15 @@
package io.iohk.atala.iris.core.model.ledger

import enumeratum.{Enum, EnumEntry}

import scala.collection.immutable.ArraySeq

sealed trait Ledger extends EnumEntry

object Ledger extends Enum[Ledger] {
val values = ArraySeq(InMemory, CardanoMainnet, CardanoTestnet)

case object InMemory extends Ledger
case object CardanoMainnet extends Ledger
case object CardanoTestnet extends Ledger
}
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,18 @@
package io.iohk.atala.iris.core.model.ledger

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

import scala.collection.immutable.ArraySeq

case class TransactionId(hex: ArraySeq[Byte])

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(InWalletMempool, Submitted, Expired, InLedger)

case object InWalletMempool 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,105 @@
package io.iohk.atala.iris.core.service

import io.iohk.atala.iris.core.model.ledger.TransactionStatus.{InWalletMempool, InLedger}
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.service.IrisOperation
import zio.{Duration as ZDuration, *}
import zio.stm.*
import io.iohk.atala.iris.proto.dlt_operations as proto_dlt
import io.iohk.atala.iris.proto.service as proto_service
import io.iohk.atala.prism.crypto.Sha256

import scala.concurrent.duration.Duration

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

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

case class CardanoBlock(txs: List[CardanoTransaction])

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

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

def startBackgroundProcess(): UIO[Unit] = STM.atomically {
for {
// Craft a new block from mempool transactions
txs <- mempoolRef.modify(old => (old, List.empty))
_ <- blocksRef.update(CardanoBlock(txs) :: _)
} yield ()
}
.repeat(Schedule.spaced(ZDuration.fromScala(config.blockEvery)))
.fork.map(_ => ())

override def publish(operations: Seq[proto_service.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(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, InWalletMempool))
}.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
}
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.service 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.{service => 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.service 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
}
}
}
19 changes: 16 additions & 3 deletions iris/service/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ object Dependencies {
val akkaHttp = "10.2.9"
val doobie = "1.0.0-RC2"
val zioCatsInterop = "3.3.0"
val prismSdk = "v1.3.3-snapshot-1657194253-992dd96"
val shared = "0.1.0"
val enumeratum = "1.7.0"
}

private lazy val zio = "dev.zio" %% "zio" % Versions.zio
Expand All @@ -25,15 +28,25 @@ object Dependencies {

private lazy val doobiePostgres = "org.tpolecat" %% "doobie-postgres" % Versions.doobie
private lazy val doobieHikari = "org.tpolecat" %% "doobie-hikari" % Versions.doobie
// We have to exclude bouncycastle since for some reason bitcoinj depends on bouncycastle jdk15to18
// (i.e. JDK 1.5 to 1.8), but we are using JDK 11
private lazy val prismCrypto = "io.iohk.atala" % "prism-crypto-jvm" % Versions.prismSdk excludeAll
ExclusionRule(
organization = "org.bouncycastle"
)

private lazy val shared = "io.iohk.atala" % "shared" % Versions.shared

private lazy val enumeratum = ("com.beachape" %% "enumeratum" % Versions.enumeratum).cross(CrossVersion.for3Use2_13)

// Dependency Modules
private lazy val baseDependencies: Seq[ModuleID] = Seq(zio)
private lazy val baseDependencies: Seq[ModuleID] = Seq(zio, prismCrypto, shared, enumeratum)
private lazy val akkaHttpDependencies: Seq[ModuleID] = Seq(akkaTyped, akkaStream, akkaHttp, akkaSprayJson).map(_.cross(CrossVersion.for3Use2_13))
private lazy val grpcDependencies: Seq[ModuleID] = Seq(grpcNetty, grpcServices, scalaPbProto, scalaPbGrpc)
private lazy val doobieDependencies: Seq[ModuleID] = Seq(doobiePostgres, doobieHikari)

// Project Dependencies
lazy val coreDependencies: Seq[ModuleID] = baseDependencies
lazy val coreDependencies: Seq[ModuleID] = baseDependencies ++ grpcDependencies
lazy val sqlDependencies: Seq[ModuleID] = baseDependencies ++ doobieDependencies ++ Seq(zioCatsInterop)
lazy val apiServerDependencies: Seq[ModuleID] = baseDependencies ++ akkaHttpDependencies ++ grpcDependencies
lazy val apiServerDependencies: Seq[ModuleID] = baseDependencies ++ akkaHttpDependencies
}
2 changes: 0 additions & 2 deletions iris/service/project/build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")

libraryDependencies ++= Seq("org.openapitools" % "openapi-generator" % "6.0.0")
1 change: 1 addition & 0 deletions iris/service/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3")
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.iohk.atala.iris.apiserver
package io.iohk.atala.iris.server

import zio.*

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.iohk.atala.iris.apiserver
package io.iohk.atala.iris.server

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.server.Route
import cats.effect.std.Dispatcher
import doobie.util.transactor.Transactor
import io.iohk.atala.iris.apiserver.grpc.service.IrisServiceGrpcImpl
import io.iohk.atala.iris.apiserver.grpc.{GrpcServer, GrpcServices}
import io.iohk.atala.iris.server.grpc.service.IrisServiceGrpcImpl
import io.iohk.atala.iris.server.grpc.{GrpcServer, GrpcServices}
import io.iohk.atala.iris.core.repository.OperationsRepository
import io.iohk.atala.iris.core.service.*
import io.iohk.atala.iris.core.worker.{MockPublishingScheduler, PublishingScheduler}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.iohk.atala.iris.apiserver.grpc
package io.iohk.atala.iris.server.grpc

import io.grpc.{ServerBuilder, ServerServiceDefinition}
import io.grpc.protobuf.services.ProtoReflectionService
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.iohk.atala.iris.apiserver.grpc
package io.iohk.atala.iris.server.grpc

import akka.actor.typed.ActorSystem
import io.grpc.ServerServiceDefinition
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.iohk.atala.iris.apiserver.grpc.service
package io.iohk.atala.iris.server.grpc.service

import com.google.protobuf.ByteString
import io.iohk.atala.iris.core.service.PublishingService
Expand Down
Loading

0 comments on commit 901ddd8

Please sign in to comment.