diff --git a/iris/service/build.sbt b/iris/service/build.sbt index b534e2fe4f..2723fc860a 100644 --- a/iris/service/build.sbt +++ b/iris/service/build.sbt @@ -1,4 +1,5 @@ import Dependencies._ +import sbtghpackages.GitHubPackagesPlugin.autoImport._ ThisBuild / version := "0.1.0-SNAPSHOT" ThisBuild / scalaVersion := "3.1.3" @@ -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 @@ -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) diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/Models.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/Models.scala index 89d3a8b44b..7fc22339cd 100644 --- a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/Models.scala +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/Models.scala @@ -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) diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/Funds.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/Funds.scala new file mode 100644 index 0000000000..197ba8fd6f --- /dev/null +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/Funds.scala @@ -0,0 +1,3 @@ +package io.iohk.atala.iris.core.model.ledger + +case class Funds(lovelaces: Int) diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/Ledger.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/Ledger.scala new file mode 100644 index 0000000000..5cf93c7d9e --- /dev/null +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/Ledger.scala @@ -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 +} diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionDetails.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionDetails.scala new file mode 100644 index 0000000000..a979b38dc3 --- /dev/null +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionDetails.scala @@ -0,0 +1,3 @@ +package io.iohk.atala.iris.core.model.ledger + +case class TransactionDetails(id: TransactionId, status: TransactionStatus) diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionId.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionId.scala new file mode 100644 index 0000000000..40191ea26d --- /dev/null +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionId.scala @@ -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) +} diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionStatus.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionStatus.scala new file mode 100644 index 0000000000..ca09ec1c9f --- /dev/null +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/model/ledger/TransactionStatus.scala @@ -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 +} diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/repository/OperationsRepository.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/repository/OperationsRepository.scala index 7cfc55cbba..5775e6954e 100644 --- a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/repository/OperationsRepository.scala +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/repository/OperationsRepository.scala @@ -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] } diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/InmemoryUnderlyingLedgerService.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/InmemoryUnderlyingLedgerService.scala new file mode 100644 index 0000000000..fc18acaab9 --- /dev/null +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/InmemoryUnderlyingLedgerService.scala @@ -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 +} diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/PublishingService.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/PublishingService.scala index 37c2141fd3..23cc753dbe 100644 --- a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/PublishingService.scala +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/PublishingService.scala @@ -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 } } } diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/UnderlyingLedgerService.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/UnderlyingLedgerService.scala new file mode 100644 index 0000000000..f0b8259bc7 --- /dev/null +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/service/UnderlyingLedgerService.scala @@ -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] +} diff --git a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/worker/PublishingScheduler.scala b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/worker/PublishingScheduler.scala index fdd1b6206d..167d3dc36e 100644 --- a/iris/service/core/src/main/scala/io/iohk/atala/iris/core/worker/PublishingScheduler.scala +++ b/iris/service/core/src/main/scala/io/iohk/atala/iris/core/worker/PublishingScheduler.scala @@ -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 } } } diff --git a/iris/service/project/Dependencies.scala b/iris/service/project/Dependencies.scala index 8a750ca5c4..ee0413cee4 100644 --- a/iris/service/project/Dependencies.scala +++ b/iris/service/project/Dependencies.scala @@ -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 @@ -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 } diff --git a/iris/service/project/build.sbt b/iris/service/project/build.sbt index 2625ab2264..83adafa3f1 100644 --- a/iris/service/project/build.sbt +++ b/iris/service/project/build.sbt @@ -1,3 +1 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") - -libraryDependencies ++= Seq("org.openapitools" % "openapi-generator" % "6.0.0") diff --git a/iris/service/project/plugins.sbt b/iris/service/project/plugins.sbt new file mode 100644 index 0000000000..54f54acbe0 --- /dev/null +++ b/iris/service/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3") diff --git a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/Main.scala b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/Main.scala similarity index 69% rename from iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/Main.scala rename to iris/service/server/src/main/scala/io/iohk/atala/iris/server/Main.scala index 1bdac42323..fb6cb3f94f 100644 --- a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/Main.scala +++ b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/Main.scala @@ -1,4 +1,4 @@ -package io.iohk.atala.iris.apiserver +package io.iohk.atala.iris.server import zio.* diff --git a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/Modules.scala b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/Modules.scala similarity index 91% rename from iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/Modules.scala rename to iris/service/server/src/main/scala/io/iohk/atala/iris/server/Modules.scala index 4774d9cb5b..6532653db0 100644 --- a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/Modules.scala +++ b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/Modules.scala @@ -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} diff --git a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/GrpcServer.scala b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/GrpcServer.scala similarity index 96% rename from iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/GrpcServer.scala rename to iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/GrpcServer.scala index c0423b8ab1..b3e9203464 100644 --- a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/GrpcServer.scala +++ b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/GrpcServer.scala @@ -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 diff --git a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/GrpcServices.scala b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/GrpcServices.scala similarity index 91% rename from iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/GrpcServices.scala rename to iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/GrpcServices.scala index f4ef958a81..65838def92 100644 --- a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/GrpcServices.scala +++ b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/GrpcServices.scala @@ -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 diff --git a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/service/IrisServiceGrpcImpl.scala b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/service/IrisServiceGrpcImpl.scala similarity index 97% rename from iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/service/IrisServiceGrpcImpl.scala rename to iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/service/IrisServiceGrpcImpl.scala index 19464c4bf7..8b488ad463 100644 --- a/iris/service/api-server/src/main/scala/io/iohk/atala/iris/apiserver/grpc/service/IrisServiceGrpcImpl.scala +++ b/iris/service/server/src/main/scala/io/iohk/atala/iris/server/grpc/service/IrisServiceGrpcImpl.scala @@ -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 diff --git a/iris/service/sql/src/main/scala/io/iohk/atala/iris/sql/repository/JdbcOperationsRepository.scala b/iris/service/sql/src/main/scala/io/iohk/atala/iris/sql/repository/JdbcOperationsRepository.scala index dd7fecb8c8..75ac5c5017 100644 --- a/iris/service/sql/src/main/scala/io/iohk/atala/iris/sql/repository/JdbcOperationsRepository.scala +++ b/iris/service/sql/src/main/scala/io/iohk/atala/iris/sql/repository/JdbcOperationsRepository.scala @@ -2,7 +2,7 @@ package io.iohk.atala.iris.sql.repository import doobie.* import doobie.implicits.* -import io.iohk.atala.iris.core.model.{IrisOperation, IrisOperationId, SignedIrisOperation} +import io.iohk.atala.iris.core.model as model import io.iohk.atala.iris.core.repository.OperationsRepository import io.iohk.atala.iris.sql.repository.JdbcOperationsRepository import zio.* @@ -11,17 +11,17 @@ import zio.interop.catz.* // TODO: replace with actual implementation class JdbcOperationsRepository(xa: Transactor[Task]) extends OperationsRepository[Task] { - override def getOperation(id: IrisOperationId): Task[IrisOperation] = { + override def getOperation(id: model.IrisOperationId): Task[model.IrisOperation] = { val cxnIO = sql""" |SELECT foo FROM public.iris_operations |""".stripMargin.query[String].unique cxnIO .transact(xa) - .map(IrisOperation.apply) + .map(model.IrisOperation.apply) } - override def saveOperations(ops: Seq[SignedIrisOperation]): Task[Unit] = ZIO.unit + override def saveOperations(ops: Seq[model.IrisOperation]): Task[Unit] = ZIO.unit } object JdbcOperationsRepository { diff --git a/shared/README.md b/shared/README.md new file mode 100644 index 0000000000..3b85b855ca --- /dev/null +++ b/shared/README.md @@ -0,0 +1,3 @@ +## shared + +Contains a stateless utility code which might be reused across all the building blocks. diff --git a/shared/build.sbt b/shared/build.sbt new file mode 100644 index 0000000000..486f3da97b --- /dev/null +++ b/shared/build.sbt @@ -0,0 +1,17 @@ +import sbtbuildinfo.BuildInfoPlugin +import sbtbuildinfo.BuildInfoPlugin.autoImport._ +import Dependencies._ + +ThisBuild / version := "0.1.0" + +ThisBuild / scalaVersion := "3.1.3" + +lazy val root = (project in file(".")) + .settings( + organization := "io.iohk.atala", + organizationName := "Input Output HK", + buildInfoPackage := "io.iohk.atala.shared", + name := "shared", + crossPaths := false, + libraryDependencies ++= dependencies + ).enablePlugins(BuildInfoPlugin) diff --git a/shared/project/Dependencies.scala b/shared/project/Dependencies.scala new file mode 100644 index 0000000000..d70c3dd4c1 --- /dev/null +++ b/shared/project/Dependencies.scala @@ -0,0 +1,11 @@ +import sbt._ + +object Dependencies { + object Versions { + val typesafeConfig = "1.4.2" + } + + private lazy val typesafeConfig = "com.typesafe" % "config" % Versions.typesafeConfig + + lazy val dependencies: Seq[ModuleID] = Seq(typesafeConfig) +} diff --git a/shared/project/build.properties b/shared/project/build.properties new file mode 100644 index 0000000000..d738b858c8 --- /dev/null +++ b/shared/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.7.1 diff --git a/shared/project/plugins.sbt b/shared/project/plugins.sbt new file mode 100644 index 0000000000..78bb5eba38 --- /dev/null +++ b/shared/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.1") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") diff --git a/shared/src/main/scala/io/iohk/atala/shared/HashValue.scala b/shared/src/main/scala/io/iohk/atala/shared/HashValue.scala new file mode 100644 index 0000000000..26387349a8 --- /dev/null +++ b/shared/src/main/scala/io/iohk/atala/shared/HashValue.scala @@ -0,0 +1,51 @@ +package io.iohk.atala.shared + +import java.util.Locale +import com.typesafe.config.ConfigMemorySize +import io.iohk.atala.shared.utils.BytesOps + +import scala.collection.immutable.ArraySeq +import scala.util.matching.Regex + +trait HashValue extends Any { + + def value: ArraySeq[Byte] + + override def toString: String = { + BytesOps.bytesToHex(value) + } +} + +trait HashValueFrom[A] { + protected val config: HashValueConfig + + protected def constructor(value: ArraySeq[Byte]): A + + def from(string: String): Option[A] = { + val lowercaseString = string.toLowerCase(Locale.ROOT) + + lowercaseString match { + case config.HexPattern() => + val bytes = lowercaseString + .grouped(2) + .toList + .map { hex => + Integer.parseInt(hex, 16).asInstanceOf[Byte] + } + Some(constructor(ArraySeq.from(bytes))) + case _ => None + } + } + + def from(bytes: Iterable[Byte]): Option[A] = { + if (bytes.size == config.size.toBytes) { + Some(constructor(ArraySeq.from(bytes))) + } else { + None + } + } +} + +case class HashValueConfig(size: ConfigMemorySize) { + private[shared] val HexPattern: Regex = s"^[a-f0-9]{${2 * size.toBytes}}$$".r +} diff --git a/shared/src/main/scala/io/iohk/atala/shared/utils/Base64.scala b/shared/src/main/scala/io/iohk/atala/shared/utils/Base64.scala new file mode 100644 index 0000000000..de3c217d4d --- /dev/null +++ b/shared/src/main/scala/io/iohk/atala/shared/utils/Base64.scala @@ -0,0 +1,18 @@ +package io.iohk.atala.shared.utils + +import java.nio.charset.StandardCharsets +import java.util.Base64 + +object Base64Utils { + def encodeURL(bytes: Array[Byte]): String = { + Base64.getUrlEncoder.encodeToString(bytes) + } + + def decodeUrlToString(encodedStr: String): String = { + new String(Base64.getUrlDecoder.decode(encodedStr), StandardCharsets.UTF_8) + } + + def decodeURL(string: String): Array[Byte] = { + Base64.getUrlDecoder.decode(string) + } +} diff --git a/shared/src/main/scala/io/iohk/atala/shared/utils/BytesOps.scala b/shared/src/main/scala/io/iohk/atala/shared/utils/BytesOps.scala new file mode 100644 index 0000000000..afc5c4dabc --- /dev/null +++ b/shared/src/main/scala/io/iohk/atala/shared/utils/BytesOps.scala @@ -0,0 +1,28 @@ +package io.iohk.atala.shared.utils + +import java.nio.charset.StandardCharsets + +object BytesOps { + private val HexArray = "0123456789abcdef".getBytes(StandardCharsets.US_ASCII); + + // Converts hex string to bytes representation + def hexToBytes(hexEncoded: String): Array[Byte] = { + require(hexEncoded.length % 2 == 0, "Hex length needs to be even") + hexEncoded.grouped(2).toVector.map(hexToByte).toArray + } + + // Converts bytes to hex string representation + def bytesToHex(bytes: Iterable[Byte]): String = { + val hexChars = new Array[Byte](bytes.size * 2) + for ((byte, i) <- bytes.zipWithIndex) { + val v = byte & 0xff + hexChars(i * 2) = HexArray(v >>> 4) + hexChars(i * 2 + 1) = HexArray(v & 0x0f) + } + new String(hexChars, StandardCharsets.UTF_8) + } + + private def hexToByte(h: String): Byte = { + Integer.parseInt(h, 16).toByte + } +}