diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..7eae215 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,11 @@ +maxColumn = 120 +align = most +continuationIndent.defnSite = 2 +assumeStandardLibraryStripMargin = true +docstrings = JavaDoc +lineEndings = preserve +includeCurlyBraceInSelectChains = false +danglingParentheses = true +spaces { inImportCurlyBraces = true } +optIn.annotationNewlines = true +rewrite.rules = [SortImports, RedundantBraces] \ No newline at end of file diff --git a/build.sbt b/build.sbt index 9e345b5..b263ad6 100644 --- a/build.sbt +++ b/build.sbt @@ -1,47 +1,43 @@ -name := "TransactionsGenerator" -version := "0.8.3" +name := "TransactionsGenerator" +version := "0.8.3" organization := "org.encryFoundation" -val logbackVersion = "1.2.3" -val catsVersion = "1.0.1" -val akkaHttpVersion = "10.0.9" -val akkaVersion = "2.5.13" +val akkaHttpVersion = "10.1.10" +val akkaVersion = "2.6.0" -scalaVersion := "2.12.6" +val catsVersion = "2.0.0" +val fs2Version = "2.0.0" +val http4sVersion = "0.20.12" -val loggingDependencies = Seq( - "com.typesafe.scala-logging" %% "scala-logging" % "3.9.0", - "ch.qos.logback" % "logback-classic" % logbackVersion, - "ch.qos.logback" % "logback-core" % logbackVersion -) - -val testingDependencies = Seq( - "com.typesafe.akka" %% "akka-testkit" % "2.4.+" % Test, - "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test, - "org.scalatest" %% "scalatest" % "3.0.3" % Test, - "org.scalacheck" %% "scalacheck" % "1.13.+" % Test, - "org.mockito" % "mockito-core" % "2.19.1" % Test -) +scalaVersion := "2.12.10" libraryDependencies ++= Seq( - "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, - "com.typesafe.akka" %% "akka-actor" % akkaVersion, - "com.typesafe.akka" %% "akka-stream" % akkaVersion, - "org.influxdb" % "influxdb-java" % "2.10", - "com.iheart" %% "ficus" % "1.4.3", - "org.encry" %% "encry-common" % "0.8.9", - "org.typelevel" % "cats-core_2.12" % "1.0.1", - "org.typelevel" % "cats-kernel_2.12" % "1.0.1", - "org.typelevel" % "cats-macros_2.12" % "1.0.1", - "com.google.guava" % "guava" % "27.1-jre", - "com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf", - "commons-net" % "commons-net" % "3.3" -) ++ loggingDependencies ++ testingDependencies - -scalacOptions += "-Ypartial-unification" - -scalacOptions += "-language:higherKinds" + "org.typelevel" %% "cats-core" % catsVersion, + "co.fs2" %% "fs2-io" % fs2Version, + "org.scodec" %% "scodec-stream" % "2.0.0", + "org.http4s" %% "http4s-blaze-client" % http4sVersion, + "org.http4s" %% "http4s-circe" % http4sVersion, + "org.http4s" %% "http4s-dsl" % http4sVersion, + "io.chrisdavenport" %% "log4cats-slf4j" % "0.4.0-M2", + // "org.scalameta" %% "semanticdb-scalac" % "4.2.5", + "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, + "com.typesafe.akka" %% "akka-actor" % akkaVersion, + "com.typesafe.akka" %% "akka-stream" % akkaVersion, + "org.influxdb" % "influxdb-java" % "2.10", + "com.iheart" %% "ficus" % "1.4.7", + "org.encry" %% "encry-common" % "0.9.2", + "com.google.guava" % "guava" % "27.1-jre", + "com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf", + "commons-net" % "commons-net" % "3.3" +) +scalacOptions ++= List( + "-feature", + "-language:higherKinds", + "-Xlint", + "-Yrangepos" + //"-Ywarn-unused" +) resolvers ++= Seq( "Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/", @@ -52,4 +48,4 @@ resolvers ++= Seq( PB.targets in Compile := Seq( scalapb.gen() -> (sourceManaged in Compile).value / "protobuf" -) \ No newline at end of file +) diff --git a/project/scalapb.sbt b/project/scalapb.sbt index a166973..35b8737 100644 --- a/project/scalapb.sbt +++ b/project/scalapb.sbt @@ -1,3 +1,3 @@ -addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.19") +addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.23") -libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.8.2" \ No newline at end of file +libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.9.4" \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/GeneratorApp.scala b/src/main/scala/org/encryfoundation/generator/GeneratorApp.scala index 8402313..ac847c2 100644 --- a/src/main/scala/org/encryfoundation/generator/GeneratorApp.scala +++ b/src/main/scala/org/encryfoundation/generator/GeneratorApp.scala @@ -11,7 +11,7 @@ import net.ceedubs.ficus.readers.ArbitraryTypeReader._ import org.encryfoundation.generator.network.NetworkServer import scala.concurrent.ExecutionContextExecutor -object GeneratorApp extends App with StrictLogging { +object GeneratorApp extends StrictLogging { implicit lazy val system: ActorSystem = ActorSystem() implicit lazy val materializer: ActorMaterializer = ActorMaterializer() diff --git a/src/main/scala/org/encryfoundation/generator/Http4sApp.scala b/src/main/scala/org/encryfoundation/generator/Http4sApp.scala new file mode 100644 index 0000000..6643d8b --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/Http4sApp.scala @@ -0,0 +1,31 @@ +package org.encryfoundation.generator + +import cats.effect.{ ExitCode, IO, IOApp } +import fs2.Stream +import cats.syntax.functor._ +import io.chrisdavenport.log4cats.slf4j.Slf4jLogger +import org.encryfoundation.generator.processors.{ HttpApiBoxesProcessor, TransactionGenerator } +import org.http4s.client.blaze.BlazeClientBuilder +import org.encryfoundation.generator.storage.{ BatchesStorage, ContractHashStorage, TransactionsStorage } +import scala.concurrent.ExecutionContext.Implicits.global + +object Http4sApp extends IOApp { + override def run(args: List[String]): IO[ExitCode] = { + + val streamF: Stream[IO, Unit] = for { + logger <- Stream.eval(Slf4jLogger.create[IO]) + _ <- Stream.eval(logger.info("Generator app started")) + service <- Stream.resource(BlazeClientBuilder[IO](global).resource) + batchesStorage <- Stream.eval(BatchesStorage.apply[IO](logger)) + keysStorage <- Stream.eval(ContractHashStorage.apply[IO](logger)) + transactionsStorage <- Stream.eval(TransactionsStorage.apply[IO]) + boxesProcessor <- Stream.eval( + TransactionGenerator.apply[IO](logger, batchesStorage, keysStorage, transactionsStorage) + ) + _ <- Stream.eval(keysStorage.init) + httpClient <- Stream.eval(HttpApiBoxesProcessor.apply[IO](service, logger, batchesStorage, keysStorage)) + _ <- httpClient.run concurrently boxesProcessor.run + } yield () + streamF.compile.drain.as(ExitCode.Success) + } +} diff --git a/src/main/scala/org/encryfoundation/generator/actors/BoxesBatch.scala b/src/main/scala/org/encryfoundation/generator/actors/BoxesBatch.scala new file mode 100644 index 0000000..02a12d7 --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/actors/BoxesBatch.scala @@ -0,0 +1,7 @@ +package org.encryfoundation.generator.actors + +import org.encryfoundation.common.modifiers.state.box.{EncryBaseBox, MonetaryBox} + +final case class BoxesBatch(boxes: List[MonetaryBox]) + +object BoxesBatch { def empty: BoxesBatch = BoxesBatch(List.empty[MonetaryBox]) } diff --git a/src/main/scala/org/encryfoundation/generator/actors/BoxesHolder.scala b/src/main/scala/org/encryfoundation/generator/actors/BoxesHolder.scala index cd85a11..7d421a6 100644 --- a/src/main/scala/org/encryfoundation/generator/actors/BoxesHolder.scala +++ b/src/main/scala/org/encryfoundation/generator/actors/BoxesHolder.scala @@ -4,10 +4,10 @@ import akka.actor.{Actor, ActorRef, Cancellable, Props} import com.typesafe.scalalogging.StrictLogging import org.encryfoundation.generator.actors.BoxesHolder._ import org.encryfoundation.generator.actors.InfluxActor._ -import org.encryfoundation.generator.modifiers.box.AssetBox import org.encryfoundation.generator.utils.{NetworkService, Node, Settings} import com.google.common.base.Charsets import com.google.common.hash.{BloomFilter, Funnels} +import org.encryfoundation.common.modifiers.state.box.AssetBox import org.encryfoundation.common.utils.Algos import scala.concurrent.ExecutionContext.Implicits.global @@ -84,7 +84,7 @@ class BoxesHolder(settings: Settings, case RequestForNewBoxesFromApi => if (pool.size < settings.boxesHolderSettings.poolSize) { logger.info(s"Current pool size is: ${pool.size}. Asking new boxes from api!") - getBoxes(0, settings.boxesHolderSettings.rangeForAskingBoxes) + //getBoxes(0, settings.boxesHolderSettings.rangeForAskingBoxes) } else logger.info(s"Current pool is: ${pool.size}. We won't ask new boxes from api!") } @@ -117,20 +117,20 @@ class BoxesHolder(settings: Settings, (newBoxes.values.toList, usedBoxes) } - def getBoxes(from: Int, to: Int): Future[Unit] = - NetworkService.requestUtxos(peer, from, to).map { request => - logger.debug(s"Boxes from API: ${request.size}") - if (request.nonEmpty && to < settings.boxesHolderSettings.maxPoolSize) { - val newFrom: Int = from + settings.boxesHolderSettings.rangeForAskingBoxes - val newTo: Int = to + settings.boxesHolderSettings.rangeForAskingBoxes - getBoxes(newFrom, newTo) - logger.debug(s"Asking new boxes in range: $newFrom -> $newTo.") - } - request.collect { case mb: AssetBox if mb.tokenIdOpt.isEmpty && !bloomFilter.mightContain(Algos.encode(mb.id)) => - bloomFilter.put(Algos.encode(mb.id)) - mb - } - }.map(boxes => self ! BoxesFromApi(boxes)) +// def getBoxes(from: Int, to: Int): Future[Unit] = +// NetworkService.requestUtxos(peer, from, to).map { request => +// logger.debug(s"Boxes from API: ${request.size}") +// if (request.nonEmpty && to < settings.boxesHolderSettings.maxPoolSize) { +// val newFrom: Int = from + settings.boxesHolderSettings.rangeForAskingBoxes +// val newTo: Int = to + settings.boxesHolderSettings.rangeForAskingBoxes +// //getBoxes(newFrom, newTo) +// logger.debug(s"Asking new boxes in range: $newFrom -> $newTo.") +// } +// request.collect { case mb: AssetBox if mb.tokenIdOpt.isEmpty && !bloomFilter.mightContain(Algos.encode(mb.id)) => +// bloomFilter.put(Algos.encode(mb.id)) +// mb +// } +// }.map(boxes => self ! BoxesFromApi(boxes)) def initBloomFilter: BloomFilter[String] = BloomFilter.create( Funnels.stringFunnel(Charsets.UTF_8), diff --git a/src/main/scala/org/encryfoundation/generator/actors/Generator.scala b/src/main/scala/org/encryfoundation/generator/actors/Generator.scala index fe3e856..dd44afd 100644 --- a/src/main/scala/org/encryfoundation/generator/actors/Generator.scala +++ b/src/main/scala/org/encryfoundation/generator/actors/Generator.scala @@ -3,15 +3,15 @@ package org.encryfoundation.generator.actors import akka.actor.{Actor, ActorRef, Props} import com.typesafe.scalalogging.StrictLogging import org.encryfoundation.common.crypto.PrivateKey25519 -import org.encryfoundation.common.modifiers.mempool.transaction.{Proof, PubKeyLockedContract} +import org.encryfoundation.common.modifiers.mempool.transaction.{Proof, PubKeyLockedContract, Transaction} +import org.encryfoundation.common.modifiers.state.box.{AssetBox, Box, EncryBaseBox, MonetaryBox} import org.encryfoundation.common.utils.Algos import org.encryfoundation.generator.actors.BlockchainListener.{CheckTxMined, MultisigTxsInBlockchain} import org.encryfoundation.generator.actors.BoxesHolder._ import org.encryfoundation.generator.actors.Generator.TransactionForCommit -import org.encryfoundation.generator.modifiers.box.{AssetBox, Box, MonetaryBox} -import org.encryfoundation.generator.modifiers.{Transaction, TransactionsFactory} +import org.encryfoundation.generator.modifiers.TransactionsFactory import org.encryfoundation.generator.transaction.Contracts -import org.encryfoundation.generator.utils.{Mnemonic, Node, Settings} +import org.encryfoundation.generator.utils.{Node, Settings} import org.encryfoundation.prismlang.compiler.CompiledContract import org.encryfoundation.prismlang.core.wrapped.BoxedValue.MultiSignatureValue import scorex.crypto.hash.Blake2b256 @@ -41,16 +41,19 @@ class Generator(settings: Settings, settings.multisig.mnemonicKeys .take(3) .map(Some(_)) - .map(Mnemonic.createPrivKey) + .map(Mnemonic.createPrivateKey) else (1 to 3) .map(_ => Curve25519.createKeyPair(rBytes())) .map(pair => PrivateKey25519(pair._1, pair._2)) - var multisigBoxes: Map[String, Seq[Box]] = Map.empty + var multisigBoxes: Map[String, Seq[EncryBaseBox]] = Map.empty val blockchainListener: ActorRef = context.actorOf(Props(classOf[BlockchainListener], settings), "blockchainListener") + override def receive: Receive = { + case _ => + } override def receive: Receive = { case BoxesForGenerator(boxes, txType, None) if boxes.nonEmpty => generateAndSendTransaction(boxes, txType) @@ -136,7 +139,7 @@ class Generator(settings: Settings, } if (txsType == 3) { blockchainListener ! CheckTxMined(Algos.encode(transaction.id)) - multisigBoxes = multisigBoxes.updated(Algos.encode(transaction.id), transaction.newBoxes) + multisigBoxes = multisigBoxes.updated(Algos.encode(transaction.id), transaction.newBoxes.toSeq) } if (txsType == 4) { blockchainListener ! CheckTxMined(Algos.encode(transaction.id)) diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/Transaction.scala b/src/main/scala/org/encryfoundation/generator/modifiers/Transaction.scala deleted file mode 100644 index f2308d1..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/Transaction.scala +++ /dev/null @@ -1,95 +0,0 @@ -package org.encryfoundation.generator.modifiers - -import TransactionProto.TransactionProtoMessage -import com.google.protobuf.ByteString -import org.encryfoundation.generator.modifiers.box.Box -import org.encryfoundation.generator.modifiers.directives.{Directive, DirectiveProtoSerializer} -import scorex.crypto.hash.{Blake2b256, Digest32} -import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor} -import org.encryfoundation.common.modifiers.mempool.transaction.{Input, InputSerializer, Proof, ProofSerializer} -import org.encryfoundation.common.utils.Algos -import org.encryfoundation.prismlang.core.Types -import org.encryfoundation.prismlang.core.wrapped.{PObject, PValue} - -import scala.util.Try - -case class Transaction(fee: Long, - timestamp: Long, - inputs: IndexedSeq[Input], - directives: IndexedSeq[Directive], - defaultProofOpt: Option[Proof]) { - - val messageToSign: Array[Byte] = UnsignedEncryTransaction.bytesToSign(fee, timestamp, inputs, directives) - lazy val id: Array[Byte] = Blake2b256.hash(messageToSign) - lazy val newBoxes: IndexedSeq[Box] = - directives.zipWithIndex.flatMap { case (d, idx) => d.boxes(Digest32 !@@ id, idx) } - - val tpe: Types.Product = Types.EncryTransaction - - def asVal: PValue = PValue(PObject(Map( - "inputs" -> PValue(inputs.map(_.boxId.toList), Types.PCollection(Types.PCollection.ofByte)), - "outputs" -> PValue(newBoxes.map(_.asPrism), Types.PCollection(Types.EncryBox)), - "messageToSign" -> PValue(messageToSign, Types.PCollection.ofByte) - ), tpe), tpe) -} - -object Transaction { - - val modifierTypeId: Byte = 2.toByte - - implicit val jsonEncoder: Encoder[Transaction] = (tx: Transaction) => Map( - "id" -> Algos.encode(tx.id).asJson, - "fee" -> tx.fee.asJson, - "timestamp" -> tx.timestamp.asJson, - "inputs" -> tx.inputs.map(_.asJson).asJson, - "directives" -> tx.directives.map(_.asJson).asJson, - "defaultProofOpt" -> tx.defaultProofOpt.map(_.asJson).asJson - ).asJson - - implicit val jsonDecoder: Decoder[Transaction] = (c: HCursor) => { - for { - fee <- c.downField("fee").as[Long] - timestamp <- c.downField("timestamp").as[Long] - inputs <- c.downField("inputs").as[IndexedSeq[Input]] - directives <- c.downField("directives").as[IndexedSeq[Directive]] - defaultProofOpt <- c.downField("defaultProofOpt").as[Option[Proof]] - } yield Transaction( - fee, - timestamp, - inputs, - directives, - defaultProofOpt - ) - } -} - -trait ProtoTransactionSerializer[T] { - - def toProto(message: T): TransactionProtoMessage - - def fromProto(message: TransactionProtoMessage): Try[T] -} - -object TransactionProtoSerializer extends ProtoTransactionSerializer[Transaction] { - - override def toProto(message: Transaction): TransactionProtoMessage = { - val initialTx: TransactionProtoMessage = TransactionProtoMessage() - .withFee(message.fee) - .withTimestamp(message.timestamp) - .withInputs(message.inputs.map(input => ByteString.copyFrom(input.bytes)).to[scala.collection.immutable.IndexedSeq]) - .withDirectives(message.directives.map(_.toDirectiveProto).to[scala.collection.immutable.IndexedSeq]) - message.defaultProofOpt match { - case Some(value) => initialTx.withProof(ByteString.copyFrom(value.bytes)) - case None => initialTx - } - } - - override def fromProto(message: TransactionProtoMessage): Try[Transaction] = Try(Transaction( - message.fee, - message.timestamp, - message.inputs.map(element => InputSerializer.parseBytes(element.toByteArray).get), - message.directives.map(directive => DirectiveProtoSerializer.fromProto(directive).get), - ProofSerializer.parseBytes(message.proof.toByteArray).toOption - )) -} \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/TransactionsFactory.scala b/src/main/scala/org/encryfoundation/generator/modifiers/TransactionsFactory.scala index 02bfa25..c8ede28 100644 --- a/src/main/scala/org/encryfoundation/generator/modifiers/TransactionsFactory.scala +++ b/src/main/scala/org/encryfoundation/generator/modifiers/TransactionsFactory.scala @@ -3,13 +3,13 @@ package org.encryfoundation.generator.modifiers import com.google.common.primitives.{Bytes, Longs} import com.typesafe.scalalogging.StrictLogging import org.encryfoundation.common.crypto.{PrivateKey25519, PublicKey25519, Signature25519} -import org.encryfoundation.common.modifiers.mempool.transaction.{Input, Proof, PubKeyLockedContract} +import org.encryfoundation.common.modifiers.mempool.directive.{AssetIssuingDirective, DataDirective, Directive, ScriptedAssetDirective, TransferDirective} +import org.encryfoundation.common.modifiers.mempool.transaction.{Input, Proof, PubKeyLockedContract, Transaction} +import org.encryfoundation.common.modifiers.state.box.MonetaryBox import org.encryfoundation.prismlang.compiler.CompiledContract import org.encryfoundation.prismlang.core.wrapped.BoxedValue import scorex.crypto.hash.{Blake2b256, Digest32} -import org.encryfoundation.generator.modifiers.directives._ import org.encryfoundation.common.utils.TaggedTypes.ADKey -import org.encryfoundation.generator.modifiers.box.MonetaryBox import scala.util.Random diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/AssetBox.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/AssetBox.scala deleted file mode 100644 index 07bd4e0..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/AssetBox.scala +++ /dev/null @@ -1,84 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -import com.google.common.primitives.{Bytes, Longs, Shorts} -import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor} -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.Algos -import org.encryfoundation.generator.modifiers.box.TokenIssuingBox.TokenId - -import scala.util.Try - -/** Represents monetary asset of some type locked with some `proposition`. - * `tokenIdOpt = None` if the asset is of intrinsic type. */ -case class AssetBox(override val proposition: EncryProposition, - override val nonce: Long, - override val amount: Long, - tokenIdOpt: Option[TokenId] = None) extends EncryBox[EncryProposition] with MonetaryBox { - - override val typeId: Byte = AssetBox.TypeId - - lazy val isIntrinsic: Boolean = tokenIdOpt.isEmpty -} - -object AssetBox { - - val IntrinsicTokenId: String = Algos.encode(Algos.hash("intrinsic_token")) - - def apply(proposition: EncryProposition, - nonce: Long, - amount: Long, - tokenIdOpt: Option[TokenId] = None): AssetBox = new AssetBox( - proposition, - nonce, - amount, - tokenIdOpt match { - case Some(id) if Algos.encode(id) == IntrinsicTokenId => Option.empty[TokenId] - case ex => ex - } - ) - - val TypeId: Byte = 1.toByte - - implicit val jsonEncoder: Encoder[AssetBox] = (bx: AssetBox) => Map( - "type" -> TypeId.asJson, - "id" -> Algos.encode(bx.id).asJson, - "proposition" -> bx.proposition.asJson, - "nonce" -> bx.nonce.asJson, - "value" -> bx.amount.asJson, - "tokenId" -> bx.tokenIdOpt.map(id => Algos.encode(id)).asJson - ).asJson - - implicit val jsonDecoder: Decoder[AssetBox] = (c: HCursor) => for { - nonce <- c.downField("nonce").as[Long] - proposition <- c.downField("proposition").as[EncryProposition] - value <- c.downField("value").as[Long] - tokenIdOpt <- c.downField("tokenId") - .as[Option[TokenId]](Decoder.decodeOption(Decoder.decodeString.emapTry(Algos.decode))) - } yield AssetBox.apply(proposition, nonce, value, tokenIdOpt) -} - -object AssetBoxSerializer extends Serializer[AssetBox] { - - override def toBytes(obj: AssetBox): Array[Byte] = { - val propBytes = EncryPropositionSerializer.toBytes(obj.proposition) - Bytes.concat( - Shorts.toByteArray(propBytes.length.toShort), - propBytes, - Longs.toByteArray(obj.nonce), - Longs.toByteArray(obj.amount), - obj.tokenIdOpt.getOrElse(Array.empty) - ) - } - - override def parseBytes(bytes: Array[Byte]): Try[AssetBox] = Try { - val propositionLen: Short = Shorts.fromByteArray(bytes.take(2)) - val iBytes: Array[Byte] = bytes.drop(2) - val proposition: EncryProposition = EncryPropositionSerializer.parseBytes(iBytes.take(propositionLen)).get - val nonce: Long = Longs.fromByteArray(iBytes.slice(propositionLen, propositionLen + 8)) - val amount: Long = Longs.fromByteArray(iBytes.slice(propositionLen + 8, propositionLen + 8 + 8)) - val tokenIdOpt: Option[TokenId] = - if ((iBytes.length - (propositionLen + 8 + 8)) == 32) Some(iBytes.takeRight(32)) else None - AssetBox(proposition, nonce, amount, tokenIdOpt) - } -} \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/Box.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/Box.scala deleted file mode 100644 index 7f0291d..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/Box.scala +++ /dev/null @@ -1,55 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -import com.google.common.primitives.Longs -import io.circe.{Decoder, DecodingFailure, Encoder} -import org.encryfoundation.common.utils.Algos -import org.encryfoundation.common.utils.TaggedTypes.ADKey -import org.encryfoundation.prismlang.core.Types -import org.encryfoundation.prismlang.core.wrapped.{PObject, PValue} - -trait Box { - - val proposition: EncryProposition - - val typeId: Byte - - val nonce: Long - - lazy val id: ADKey = ADKey @@ Algos.hash(Longs.toByteArray(nonce)).updated(0, typeId) - - def isAmountCarrying: Boolean = this.isInstanceOf[MonetaryBox] - - val tpe: Types.Product = Types.EncryBox - - def asVal: PValue = PValue(asPrism, tpe) - - lazy val baseFields: Map[String, PValue] = Map( - "contractHash" -> PValue(proposition.contractHash, Types.PCollection.ofByte), - "typeId" -> PValue(typeId.toLong, Types.PInt), - "id" -> PValue(id, Types.PCollection.ofByte) - ) - - def asPrism: PObject = PObject(baseFields, tpe) -} - -object Box { - - implicit val jsonEncoder: Encoder[Box] = { - case ab: AssetBox => AssetBox.jsonEncoder(ab) - case db: DataBox => DataBox.jsonEncoder(db) - case aib: TokenIssuingBox => TokenIssuingBox.jsonEncoder(aib) - } - - implicit val jsonDecoder: Decoder[Box] = { - Decoder.instance { c => - c.downField("type").as[Byte] match { - case Right(s) => s match { - case AssetBox.TypeId => AssetBox.jsonDecoder(c) - case DataBox.TypeId => DataBox.jsonDecoder(c) - case _ => Left(DecodingFailure("Incorrect directive typeID", c.history)) - } - case Left(_) => Left(DecodingFailure("None typeId", c.history)) - } - } - } -} diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/DataBox.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/DataBox.scala deleted file mode 100644 index 644b42d..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/DataBox.scala +++ /dev/null @@ -1,66 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -import com.google.common.primitives.{Bytes, Longs, Shorts} -import io.circe.{Decoder, Encoder, HCursor} -import io.circe.syntax._ -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.Algos - -import scala.util.Try - -/** Stores arbitrary data in EncryTL binary format. */ -case class DataBox(override val proposition: EncryProposition, - override val nonce: Long, - data: Array[Byte]) extends EncryBox[EncryProposition] { - - override val typeId: Byte = DataBox.TypeId -} - -object DataBox { - - val TypeId: Byte = 4.toByte - - implicit val jsonEncoder: Encoder[DataBox] = (bx: DataBox) => Map( - "type" -> TypeId.asJson, - "id" -> Algos.encode(bx.id).asJson, - "proposition" -> bx.proposition.asJson, - "nonce" -> bx.nonce.asJson, - "data" -> Algos.encode(bx.data).asJson, - ).asJson - - implicit val jsonDecoder: Decoder[DataBox] = (c: HCursor) => { - for { - proposition <- c.downField("proposition").as[EncryProposition] - nonce <- c.downField("nonce").as[Long] - data <- c.downField("data").as[String] - } yield DataBox( - proposition, - nonce, - Algos.decode(data).getOrElse(Array.emptyByteArray) - ) - } -} - -object DataBoxSerializer extends Serializer[DataBox] { - - override def toBytes(obj: DataBox): Array[Byte] = { - val propBytes: Array[Byte] = EncryPropositionSerializer.toBytes(obj.proposition) - Bytes.concat( - Shorts.toByteArray(propBytes.length.toShort), - propBytes, - Longs.toByteArray(obj.nonce), - Shorts.toByteArray(obj.data.length.toShort), - obj.data - ) - } - - override def parseBytes(bytes: Array[Byte]): Try[DataBox] = Try { - val propositionLen: Short = Shorts.fromByteArray(bytes.take(2)) - val iBytes: Array[Byte] = bytes.drop(2) - val proposition: EncryProposition = EncryPropositionSerializer.parseBytes(iBytes.take(propositionLen)).get - val nonce: Long = Longs.fromByteArray(iBytes.slice(propositionLen, propositionLen + 8)) - val dataLen: Short = Shorts.fromByteArray(iBytes.slice(propositionLen + 8, propositionLen + 8 + 2)) - val data: Array[Byte] = iBytes.takeRight(dataLen) - DataBox(proposition, nonce, data) - } -} \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBaseBox.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBaseBox.scala deleted file mode 100644 index 409df44..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBaseBox.scala +++ /dev/null @@ -1,38 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -import com.google.common.primitives.Longs -import io.circe.Encoder -import org.encryfoundation.common.utils.Algos -import org.encryfoundation.common.utils.TaggedTypes.ADKey -import org.encryfoundation.prismlang.core.wrapped.{PObject, PValue} -import org.encryfoundation.prismlang.core.{PConvertible, Types} - -trait EncryBaseBox extends Box with PConvertible { - - val typeId: Byte - - val nonce: Long - - override lazy val id: ADKey = ADKey @@ Algos.hash(Longs.toByteArray(nonce)).updated(0, typeId) - - override val tpe: Types.Product = Types.EncryBox - - override def asVal: PValue = PValue(asPrism, tpe) - - override lazy val baseFields: Map[String, PValue] = Map( - "contractHash" -> PValue(proposition.contractHash, Types.PCollection.ofByte), - "typeId" -> PValue(typeId.toLong, Types.PInt), - "id" -> PValue(id, Types.PCollection.ofByte) - ) - - override def asPrism: PObject = PObject(baseFields, tpe) -} - -object EncryBaseBox { - - implicit val jsonEncoder: Encoder[EncryBaseBox] = { - case ab: AssetBox => AssetBox.jsonEncoder(ab) - case db: DataBox => DataBox.jsonEncoder(db) - case aib: TokenIssuingBox => TokenIssuingBox.jsonEncoder(aib) - } -} \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBox.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBox.scala deleted file mode 100644 index 1a612ab..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBox.scala +++ /dev/null @@ -1,14 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -trait EncryBox[P <: EncryProposition] extends EncryBaseBox { - - override val proposition: P - -} - -object EncryBox { - - type BxTypeId = Byte - - val BoxIdSize = 32 -} \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBoxStateChanges.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBoxStateChanges.scala deleted file mode 100644 index e76ceed..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryBoxStateChanges.scala +++ /dev/null @@ -1,23 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -import org.encryfoundation.common.utils.Algos -import org.encryfoundation.common.utils.TaggedTypes.ADKey - -abstract class EncryBoxStateChangeOperation - -case class Removal(boxId: ADKey) extends EncryBoxStateChangeOperation { - - override def toString: String = s"Removal(id: ${Algos.encode(boxId)})" -} - -case class Insertion(box: Box) extends EncryBoxStateChangeOperation { - - override def toString: String = s"Insertion(id: ${Algos.encode(box.id)})" -} - -case class EncryBoxStateChanges(operations: Seq[EncryBoxStateChangeOperation]){ - - def toAppend: Seq[EncryBoxStateChangeOperation] = operations.filter(_.isInstanceOf[Insertion]) - - def toRemove: Seq[EncryBoxStateChangeOperation] = operations.filter(_.isInstanceOf[Removal]) -} diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryProposition.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryProposition.scala deleted file mode 100644 index 0c89e19..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/EncryProposition.scala +++ /dev/null @@ -1,46 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -import io.circe.syntax._ -import io.circe.{Decoder, Encoder} -import org.encryfoundation.common.modifiers.mempool.transaction._ -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.Algos -import org.encryfoundation.prismlang.compiler.CompiledContract.ContractHash -import scorex.crypto.signatures.PublicKey - -import scala.util.{Failure, Success, Try} - -case class EncryProposition(contractHash: ContractHash) extends Proposition { - - override type M = EncryProposition - - override def serializer: Serializer[M] = EncryPropositionSerializer -} - -object EncryProposition { - - implicit val jsonEncoder: Encoder[EncryProposition] = (p: EncryProposition) => Map( - "contractHash" -> Algos.encode(p.contractHash).asJson - ).asJson - - implicit val jsonDecoder: Decoder[EncryProposition] = - Decoder.decodeString - .emapTry(Algos.decode) - .map(EncryProposition.apply) - .prepare(_.downField("contractHash")) - - def open: EncryProposition = EncryProposition(OpenContract.contract.hash) - def heightLocked(height: Int): EncryProposition = EncryProposition(HeightLockedContract(height).contract.hash) - def pubKeyLocked(pubKey: PublicKey): EncryProposition = EncryProposition(PubKeyLockedContract(pubKey).contract.hash) - def addressLocked(address: String): EncryProposition = EncryAddress.resolveAddress(address).map { - case p2pk: Pay2PubKeyAddress => EncryProposition(PubKeyLockedContract(p2pk.pubKey).contract.hash) - case p2sh: Pay2ContractHashAddress => EncryProposition(p2sh.contractHash) - }.getOrElse(throw EncryAddress.InvalidAddressException) -} - -object EncryPropositionSerializer extends Serializer[EncryProposition] { - def toBytes(obj: EncryProposition): Array[Byte] = obj.contractHash - def parseBytes(bytes: Array[Byte]): Try[EncryProposition] = - if (bytes.lengthCompare(32) == 0) Success(EncryProposition(bytes)) - else Failure(new Exception("Invalid contract hash length")) -} diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/MonetaryBox.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/MonetaryBox.scala deleted file mode 100644 index 343491d..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/MonetaryBox.scala +++ /dev/null @@ -1,3 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -trait MonetaryBox extends Box { val amount: Long } diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/box/TokenIssuingBox.scala b/src/main/scala/org/encryfoundation/generator/modifiers/box/TokenIssuingBox.scala deleted file mode 100644 index f3519fd..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/box/TokenIssuingBox.scala +++ /dev/null @@ -1,56 +0,0 @@ -package org.encryfoundation.generator.modifiers.box - -import com.google.common.primitives.{Bytes, Longs, Shorts} -import io.circe.Encoder -import io.circe.syntax._ -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.Algos -import org.encryfoundation.generator.modifiers.box.TokenIssuingBox.TokenId - -import scala.util.Try - -case class TokenIssuingBox(override val proposition: EncryProposition, - override val nonce: Long, - override val amount: Long, - tokenId: TokenId) extends EncryBox[EncryProposition] with MonetaryBox { - - override val typeId: Byte = AssetBox.TypeId -} - -object TokenIssuingBox { - - type TokenId = Array[Byte] - - val TypeId: Byte = 3.toByte - - implicit val jsonEncoder: Encoder[TokenIssuingBox] = (bx: TokenIssuingBox) => Map( - "type" -> TypeId.asJson, - "id" -> Algos.encode(bx.id).asJson, - "proposition" -> bx.proposition.asJson, - "nonce" -> bx.nonce.asJson - ).asJson -} - -object AssetIssuingBoxSerializer extends Serializer[TokenIssuingBox] { - - override def toBytes(obj: TokenIssuingBox): Array[Byte] = { - val propBytes: Array[Byte] = EncryPropositionSerializer.toBytes(obj.proposition) - Bytes.concat( - Shorts.toByteArray(propBytes.length.toShort), - propBytes, - Longs.toByteArray(obj.nonce), - Longs.toByteArray(obj.amount), - obj.tokenId - ) - } - - override def parseBytes(bytes: Array[Byte]): Try[TokenIssuingBox] = Try { - val propositionLen: Short = Shorts.fromByteArray(bytes.take(2)) - val iBytes: Array[Byte] = bytes.drop(2) - val proposition: EncryProposition = EncryPropositionSerializer.parseBytes(iBytes.take(propositionLen)).get - val nonce: Long = Longs.fromByteArray(iBytes.slice(propositionLen, propositionLen + 8)) - val amount: Long = Longs.fromByteArray(iBytes.slice(propositionLen + 8, propositionLen + 8 + 8)) - val creationId: TokenId = bytes.takeRight(32) - TokenIssuingBox(proposition, nonce, amount, creationId) - } -} \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/directives/AssetIssuingDirective.scala b/src/main/scala/org/encryfoundation/generator/modifiers/directives/AssetIssuingDirective.scala deleted file mode 100644 index 1609c1f..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/directives/AssetIssuingDirective.scala +++ /dev/null @@ -1,85 +0,0 @@ -package org.encryfoundation.generator.modifiers.directives - -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage.AssetIssuingDirectiveProtoMessage -import com.google.common.primitives.{Bytes, Ints, Longs} -import com.google.protobuf.ByteString -import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor} -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.{Algos, Utils} -import org.encryfoundation.common.utils.constants.TestNetConstants -import org.encryfoundation.prismlang.compiler.CompiledContract.ContractHash -import scorex.crypto.hash.Digest32 -import org.encryfoundation.generator.modifiers.box.{Box, EncryProposition, TokenIssuingBox} - -import scala.util.Try - -case class AssetIssuingDirective(contractHash: ContractHash, amount: Long) extends Directive { - - override type M = AssetIssuingDirective - override val typeId: Byte = AssetIssuingDirective.TypeId - override lazy val isValid: Boolean = amount > 0 - - override def boxes(digest: Digest32, idx: Int): Seq[Box] = - Seq(TokenIssuingBox( - EncryProposition(contractHash), - Utils.nonceFromDigest(digest ++ Ints.toByteArray(idx)), - amount, - Algos.hash(Ints.toByteArray(idx) ++ digest) - )) - - override def serializer: Serializer[M] = AssetIssuingDirectiveSerializer - - override def toDirectiveProto: DirectiveProtoMessage = AssetIssuingDirectiveProtoSerializer.toProto(this) - -} - -object AssetIssuingDirective { - - val TypeId: Byte = 2.toByte - - implicit val jsonEncoder: Encoder[AssetIssuingDirective] = (d: AssetIssuingDirective) => Map( - "typeId" -> d.typeId.asJson, - "contractHash" -> Algos.encode(d.contractHash).asJson, - "amount" -> d.amount.asJson - ).asJson - - implicit val jsonDecoder: Decoder[AssetIssuingDirective] = (c: HCursor) => { - for { - contractHash <- c.downField("contractHash").as[ContractHash](Decoder.decodeString.emapTry(Algos.decode)) - amount <- c.downField("amount").as[Long] - } yield AssetIssuingDirective(contractHash, amount) - } -} - -object AssetIssuingDirectiveProtoSerializer extends ProtoDirectiveSerializer[AssetIssuingDirective] { - - override def toProto(message: AssetIssuingDirective): DirectiveProtoMessage = - DirectiveProtoMessage().withAssetIssuingDirectiveProto(AssetIssuingDirectiveProtoMessage() - .withAmount(message.amount) - .withContractHash(ByteString.copyFrom(message.contractHash)) - ) - - override def fromProto(message: DirectiveProtoMessage): Option[AssetIssuingDirective] = - message.directiveProto.assetIssuingDirectiveProto match { - case Some(value) => Some(AssetIssuingDirective(value.contractHash.toByteArray, value.amount)) - case None => Option.empty[AssetIssuingDirective] - } -} - - -object AssetIssuingDirectiveSerializer extends Serializer[AssetIssuingDirective] { - - override def toBytes(obj: AssetIssuingDirective): Array[Byte] = - Bytes.concat( - obj.contractHash, - Longs.toByteArray(obj.amount) - ) - - override def parseBytes(bytes: Array[Byte]): Try[AssetIssuingDirective] = Try { - val contractHash: ContractHash = bytes.take(TestNetConstants.DigestLength) - val amount: Long = Longs.fromByteArray(bytes.slice(TestNetConstants.DigestLength, TestNetConstants.DigestLength + 8)) - AssetIssuingDirective(contractHash, amount) - } -} diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/directives/DataDirective.scala b/src/main/scala/org/encryfoundation/generator/modifiers/directives/DataDirective.scala deleted file mode 100644 index 6c75078..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/directives/DataDirective.scala +++ /dev/null @@ -1,89 +0,0 @@ -package org.encryfoundation.generator.modifiers.directives - -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage.DataDirectiveProtoMessage -import org.encryfoundation.prismlang.compiler.CompiledContract.ContractHash -import com.google.common.primitives.{Bytes, Ints} -import com.google.protobuf.ByteString -import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor} -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.{Algos, Utils} -import org.encryfoundation.common.utils.constants.TestNetConstants -import scorex.crypto.hash.Digest32 -import org.encryfoundation.generator.modifiers.box.{DataBox, EncryProposition} -import org.encryfoundation.generator.modifiers.directives.Directive.DTypeId - -import scala.util.Try - -case class DataDirective(contractHash: ContractHash, data: Array[Byte]) extends Directive { - - override type M = DataDirective - - override val typeId: DTypeId = DataDirective.TypeId - - override def boxes(digest: Digest32, idx: Int): Seq[DataBox] = - Seq(DataBox(EncryProposition(contractHash), - Utils.nonceFromDigest(digest ++ Ints.toByteArray(idx)), data)) - - val MaxDataLength: Int = 1000 - - override lazy val isValid: Boolean = data.length <= MaxDataLength - - override def serializer: Serializer[M] = DataDirectiveSerializer - - override def toDirectiveProto: DirectiveProtoMessage = DataDirectiveProtoSerializer.toProto(this) - -} - -object DataDirective { - - val TypeId: DTypeId = 5.toByte - - implicit val jsonEncoder: Encoder[DataDirective] = (d: DataDirective) => Map( - "typeId" -> d.typeId.asJson, - "contractHash" -> Algos.encode(d.contractHash).asJson, - "data" -> Algos.encode(d.data).asJson - ).asJson - - implicit val jsonDecoder: Decoder[DataDirective] = (c: HCursor) => { - for { - contractHash <- c.downField("contractHash").as[String] - dataEnc <- c.downField("data").as[String] - } yield Algos.decode(contractHash) - .flatMap(ch => Algos.decode(dataEnc).map(data => DataDirective(ch, data))) - .getOrElse(throw new Exception("Decoding failed")) - } -} - - -object DataDirectiveProtoSerializer extends ProtoDirectiveSerializer[DataDirective] { - - override def toProto(message: DataDirective): DirectiveProtoMessage = DirectiveProtoMessage() - .withDataDirectiveProto(DataDirectiveProtoMessage() - .withContractHash(ByteString.copyFrom(message.contractHash)) - .withData(ByteString.copyFrom(message.data))) - - override def fromProto(message: DirectiveProtoMessage): Option[DataDirective] = - message.directiveProto.dataDirectiveProto match { - case Some(value) => Some(DataDirective(value.contractHash.toByteArray, value.data.toByteArray)) - case None => Option.empty[DataDirective] - } -} - -object DataDirectiveSerializer extends Serializer[DataDirective] { - - override def toBytes(obj: DataDirective): Array[Byte] = - Bytes.concat( - obj.contractHash, - Ints.toByteArray(obj.data.length), - obj.data - ) - - override def parseBytes(bytes: Array[Byte]): Try[DataDirective] = Try { - val contractHash: ContractHash = bytes.take(TestNetConstants.DigestLength) - val dataLen: Int = Ints.fromByteArray(bytes.slice(TestNetConstants.DigestLength, TestNetConstants.DigestLength + 4)) - val data: Array[DTypeId] = bytes.slice(TestNetConstants.DigestLength + 4, TestNetConstants.DigestLength + 4 + dataLen) - DataDirective(contractHash, data) - } -} \ No newline at end of file diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/directives/Directive.scala b/src/main/scala/org/encryfoundation/generator/modifiers/directives/Directive.scala deleted file mode 100644 index 4a9f10b..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/directives/Directive.scala +++ /dev/null @@ -1,45 +0,0 @@ -package org.encryfoundation.generator.modifiers.directives - -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage -import io.circe._ -import org.encryfoundation.common.serialization.BytesSerializable -import scorex.crypto.hash.Digest32 -import org.encryfoundation.generator.modifiers.box.Box - -trait Directive extends BytesSerializable { - - val typeId: Byte - val isValid: Boolean - - def boxes(digest: Digest32, idx: Int): Seq[Box] - - def toDirectiveProto: DirectiveProtoMessage -} - -object Directive { - - type DTypeId = Byte - - implicit val jsonEncoder: Encoder[Directive] = { - case td: TransferDirective => TransferDirective.jsonEncoder(td) - case aid: AssetIssuingDirective => AssetIssuingDirective.jsonEncoder(aid) - case sad: ScriptedAssetDirective => ScriptedAssetDirective.jsonEncoder(sad) - case dad: DataDirective => DataDirective.jsonEncoder(dad) - case _ => throw new Exception("Incorrect directive type") - } - - implicit val jsonDecoder: Decoder[Directive] = { - Decoder.instance { c => - c.downField("typeId").as[DTypeId] match { - case Right(s) => s match { - case TransferDirective.TypeId => TransferDirective.jsonDecoder(c) - case AssetIssuingDirective.TypeId => AssetIssuingDirective.jsonDecoder(c) - case ScriptedAssetDirective.TypeId => ScriptedAssetDirective.jsonDecoder(c) - case DataDirective.TypeId => DataDirective.jsonDecoder(c) - case _ => Left(DecodingFailure("Incorrect directive typeID", c.history)) - } - case Left(_) => Left(DecodingFailure("None typeId", c.history)) - } - } - } -} diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/directives/DirectiveSerializer.scala b/src/main/scala/org/encryfoundation/generator/modifiers/directives/DirectiveSerializer.scala deleted file mode 100644 index d70cb63..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/directives/DirectiveSerializer.scala +++ /dev/null @@ -1,42 +0,0 @@ -package org.encryfoundation.generator.modifiers.directives - -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage.DirectiveProto - -import scala.util.{Failure, Try} -import org.encryfoundation.common.serialization.Serializer - - -trait ProtoDirectiveSerializer[T] { - - def toProto(message: T): DirectiveProtoMessage - - def fromProto(message: DirectiveProtoMessage): Option[T] -} - -object DirectiveProtoSerializer { - def fromProto(message: DirectiveProtoMessage): Option[Directive] = message.directiveProto match { - case DirectiveProto.AssetIssuingDirectiveProto(_) => AssetIssuingDirectiveProtoSerializer.fromProto(message) - case DirectiveProto.DataDirectiveProto(_) => DataDirectiveProtoSerializer.fromProto(message) - case DirectiveProto.TransferDirectiveProto(_) => TransferDirectiveProtoSerializer.fromProto(message) - case DirectiveProto.ScriptedAssetDirectiveProto(_) => ScriptedAssetDirectiveProtoSerializer.fromProto(message) - case DirectiveProto.Empty => None - } -} - -object DirectiveSerializer extends Serializer[Directive] { - - override def toBytes(obj: Directive): Array[Byte] = obj match { - case td: TransferDirective => TransferDirective.TypeId +: TransferDirectiveSerializer.toBytes(td) - case aid: AssetIssuingDirective => AssetIssuingDirective.TypeId +: AssetIssuingDirectiveSerializer.toBytes(aid) - case sad: ScriptedAssetDirective => ScriptedAssetDirective.TypeId +: ScriptedAssetDirectiveSerializer.toBytes(sad) - case m => throw new Exception(s"Serialization of unknown directive type: $m") - } - - override def parseBytes(bytes: Array[Byte]): Try[Directive] = Try(bytes.head).flatMap { - case TransferDirective.`TypeId` => TransferDirectiveSerializer.parseBytes(bytes.tail) - case AssetIssuingDirective.`TypeId` => AssetIssuingDirectiveSerializer.parseBytes(bytes.tail) - case ScriptedAssetDirective.`TypeId` => ScriptedAssetDirectiveSerializer.parseBytes(bytes.tail) - case t => Failure(new Exception(s"Got unknown typeId: $t")) - } -} diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/directives/ScriptedAssetDirective.scala b/src/main/scala/org/encryfoundation/generator/modifiers/directives/ScriptedAssetDirective.scala deleted file mode 100644 index 70d54c7..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/directives/ScriptedAssetDirective.scala +++ /dev/null @@ -1,100 +0,0 @@ -package org.encryfoundation.generator.modifiers.directives - -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage.{ADKeyProto, ScriptedAssetDirectiveProtoMessage} - -import scala.util.Try -import com.google.common.primitives.{Bytes, Ints, Longs} -import com.google.protobuf.ByteString -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.{Algos, Utils} -import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor} -import org.encryfoundation.common.utils.TaggedTypes.ADKey -import org.encryfoundation.common.utils.constants.TestNetConstants -import org.encryfoundation.prismlang.compiler.CompiledContract.ContractHash -import scorex.crypto.hash.Digest32 -import org.encryfoundation.generator.modifiers.box.{AssetBox, Box, EncryProposition} - -case class ScriptedAssetDirective(contractHash: ContractHash, - amount: Long, - tokenIdOpt: Option[ADKey] = None) extends Directive { - - override type M = ScriptedAssetDirective - - override val typeId: Byte = ScriptedAssetDirective.TypeId - - override def boxes(digest: Digest32, idx: Int): Seq[Box] = - Seq(AssetBox(EncryProposition(contractHash), - Utils.nonceFromDigest(digest ++ Ints.toByteArray(idx)), amount)) - - override lazy val isValid: Boolean = amount > 0 - - override def serializer: Serializer[M] = ScriptedAssetDirectiveSerializer - - lazy val isIntrinsic: Boolean = tokenIdOpt.isEmpty - - override def toDirectiveProto: DirectiveProtoMessage = ScriptedAssetDirectiveProtoSerializer.toProto(this) - -} - -object ScriptedAssetDirective { - - val TypeId: Byte = 3.toByte - - implicit val jsonEncoder: Encoder[ScriptedAssetDirective] = (d: ScriptedAssetDirective) => Map( - "typeId" -> d.typeId.asJson, - "contractHash" -> Algos.encode(d.contractHash).asJson, - "amount" -> d.amount.asJson, - "tokenId" -> d.tokenIdOpt.map(id => Algos.encode(id)).asJson - ).asJson - - implicit val jsonDecoder: Decoder[ScriptedAssetDirective] = (c: HCursor) => for { - contractHash <- c.downField("contractHash").as[ContractHash](Decoder.decodeString.emapTry(Algos.decode)) - amount <- c.downField("amount").as[Long] - tokenIdOpt <- c.downField("tokenId").as[Option[ADKey]](Decoder.decodeOption(Decoder.decodeString.emapTry(Algos.decode).map(ADKey @@ _))) - } yield ScriptedAssetDirective(contractHash, amount, tokenIdOpt) -} - -object ScriptedAssetDirectiveProtoSerializer extends ProtoDirectiveSerializer[ScriptedAssetDirective] { - - override def toProto(message: ScriptedAssetDirective): DirectiveProtoMessage ={ - val initialDirective: ScriptedAssetDirectiveProtoMessage = ScriptedAssetDirectiveProtoMessage() - .withContractHash(ByteString.copyFrom(message.contractHash)) - .withAmount(message.amount) - val saDirective: ScriptedAssetDirectiveProtoMessage = message.tokenIdOpt match { - case Some(value) => initialDirective.withTokenIdOpt( ADKeyProto().withTokenIdOpt(ByteString.copyFrom(value))) - case None => initialDirective - } - DirectiveProtoMessage().withScriptedAssetDirectiveProto(saDirective) - } - - override def fromProto(message: DirectiveProtoMessage): Option[ScriptedAssetDirective] = - message.directiveProto.scriptedAssetDirectiveProto match { - case Some(value) => Some(ScriptedAssetDirective( - value.contractHash.toByteArray, - value.amount, - value.tokenIdOpt.map(x => ADKey @@ x.tokenIdOpt.toByteArray)) - ) - case None => Option.empty[ScriptedAssetDirective] - } -} - -object ScriptedAssetDirectiveSerializer extends Serializer[ScriptedAssetDirective] { - - override def toBytes(obj: ScriptedAssetDirective): Array[Byte] = - Bytes.concat( - obj.contractHash, - Longs.toByteArray(obj.amount), - obj.tokenIdOpt.getOrElse(Array.empty) - ) - - override def parseBytes(bytes: Array[Byte]): Try[ScriptedAssetDirective] = Try { - val contractHash: ContractHash = bytes.take(TestNetConstants.DigestLength) - val amount: Long = Longs.fromByteArray(bytes.slice(TestNetConstants.DigestLength, TestNetConstants.DigestLength + 8)) - val tokenIdOpt: Option[ADKey] = if ((bytes.length - (TestNetConstants.DigestLength + 8)) == TestNetConstants.ModifierIdSize) { - Some(ADKey @@ bytes.takeRight(TestNetConstants.ModifierIdSize)) - } else None - ScriptedAssetDirective(contractHash, amount, tokenIdOpt) - } -} diff --git a/src/main/scala/org/encryfoundation/generator/modifiers/directives/TransferDirective.scala b/src/main/scala/org/encryfoundation/generator/modifiers/directives/TransferDirective.scala deleted file mode 100644 index 9db376f..0000000 --- a/src/main/scala/org/encryfoundation/generator/modifiers/directives/TransferDirective.scala +++ /dev/null @@ -1,112 +0,0 @@ -package org.encryfoundation.generator.modifiers.directives - -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage -import TransactionProto.TransactionProtoMessage.DirectiveProtoMessage.{ADKeyProto, TransferDirectiveProtoMessage} -import com.google.common.primitives.{Bytes, Ints, Longs} -import com.google.protobuf.ByteString -import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor} -import org.encryfoundation.common.modifiers.mempool.transaction.EncryAddress -import org.encryfoundation.common.modifiers.mempool.transaction.EncryAddress.Address -import org.encryfoundation.common.serialization.Serializer -import org.encryfoundation.common.utils.TaggedTypes.ADKey -import org.encryfoundation.common.utils.{Algos, Utils} -import org.encryfoundation.common.utils.constants.{Constants, TestNetConstants} -import scorex.crypto.hash.Digest32 -import org.encryfoundation.generator.modifiers.box.{AssetBox, Box, EncryProposition} - -import scala.util.Try - -case class TransferDirective(address: Address, - amount: Long, - tokenIdOpt: Option[ADKey] = None) extends Directive { - - override type M = TransferDirective - - override val typeId: Byte = TransferDirective.TypeId - - override def boxes(digest: Digest32, idx: Int): Seq[Box] = - Seq(AssetBox(EncryProposition.addressLocked(address), - Utils.nonceFromDigest(digest ++ Ints.toByteArray(idx)), amount, tokenIdOpt)) - - override lazy val isValid: Boolean = amount > 0 && EncryAddress.resolveAddress(address).isSuccess - - override def serializer: Serializer[M] = TransferDirectiveSerializer - - lazy val isIntrinsic: Boolean = tokenIdOpt.isEmpty - - override def toDirectiveProto: DirectiveProtoMessage = TransferDirectiveProtoSerializer.toProto(this) - -} - -object TransferDirective { - - val TypeId: Byte = 1.toByte - - implicit val jsonEncoder: Encoder[TransferDirective] = (d: TransferDirective) => Map( - "typeId" -> d.typeId.asJson, - "address" -> d.address.toString.asJson, - "amount" -> d.amount.asJson, - "tokenId" -> d.tokenIdOpt.map(id => Algos.encode(id)).getOrElse("null").asJson - ).asJson - - implicit val jsonDecoder: Decoder[TransferDirective] = (c: HCursor) => { - for { - address <- c.downField("address").as[String] - amount <- c.downField("amount").as[Long] - tokenIdOpt <- c.downField("tokenId").as[Option[String]] - } yield { - TransferDirective( - address, - amount, - tokenIdOpt.flatMap(id => Algos.decode(id).map(ADKey @@ _).toOption) - ) - } - } -} - -object TransferDirectiveProtoSerializer extends ProtoDirectiveSerializer[TransferDirective] { - - override def toProto(message: TransferDirective): DirectiveProtoMessage = { - val initialDirective: TransferDirectiveProtoMessage = TransferDirectiveProtoMessage() - .withAddress(message.address) - .withAmount(message.amount) - val transferDirective: TransferDirectiveProtoMessage = message.tokenIdOpt match { - case Some(value) => initialDirective.withTokenIdOpt(ADKeyProto().withTokenIdOpt(ByteString.copyFrom(value))) - case None => initialDirective - } - DirectiveProtoMessage().withTransferDirectiveProto(transferDirective) - } - - override def fromProto(message: DirectiveProtoMessage): Option[TransferDirective] = - message.directiveProto.transferDirectiveProto match { - case Some(value) => Some(TransferDirective( - value.address, - value.amount, - value.tokenIdOpt.map(x => ADKey @@ x.tokenIdOpt.toByteArray)) - ) - case None => Option.empty[TransferDirective] - } -} - -object TransferDirectiveSerializer extends Serializer[TransferDirective] { - - override def toBytes(obj: TransferDirective): Array[Byte] = { - val address: Array[Byte] = obj.address.getBytes(Algos.charset) - address.length.toByte +: Bytes.concat( - address, - Longs.toByteArray(obj.amount), - obj.tokenIdOpt.getOrElse(Array.empty) - ) - } - - override def parseBytes(bytes: Array[Byte]): Try[TransferDirective] = Try { - val addressLen: Int = bytes.head.toInt - val address: Address = new String(bytes.slice(1, 1 + addressLen), Algos.charset) - val amount: Long = Longs.fromByteArray(bytes.slice(1 + addressLen, 1 + addressLen + 8)) - val tokenIdOpt: Option[ADKey] = if ((bytes.length - (1 + addressLen + 8)) == TestNetConstants.ModifierIdSize) { - Some(ADKey @@ bytes.takeRight(TestNetConstants.ModifierIdSize)) - } else None - TransferDirective(address, amount, tokenIdOpt) - } -} diff --git a/src/main/scala/org/encryfoundation/generator/network/NetworkMessagesHandler.scala b/src/main/scala/org/encryfoundation/generator/network/NetworkMessagesHandler.scala index 023d2fa..2172aae 100644 --- a/src/main/scala/org/encryfoundation/generator/network/NetworkMessagesHandler.scala +++ b/src/main/scala/org/encryfoundation/generator/network/NetworkMessagesHandler.scala @@ -2,11 +2,11 @@ package org.encryfoundation.generator.network import akka.actor.{Actor, Props} import com.typesafe.scalalogging.StrictLogging +import org.encryfoundation.common.modifiers.mempool.transaction.{Transaction, TransactionProtoSerializer} import org.encryfoundation.common.utils.Algos import org.encryfoundation.generator.actors.Generator.TransactionForCommit import org.encryfoundation.generator.network.BasicMessagesRepo._ import org.encryfoundation.generator.network.NetworkMessagesHandler.BroadcastInvForTx -import org.encryfoundation.generator.modifiers.{Transaction, TransactionProtoSerializer} import org.encryfoundation.generator.utils.CoreTaggedTypes.{ModifierId, ModifierTypeId} import org.encryfoundation.generator.utils.{CoreTaggedTypes, Settings} import supertagged.@@ diff --git a/src/main/scala/org/encryfoundation/generator/network/NetworkServer.scala b/src/main/scala/org/encryfoundation/generator/network/NetworkServer.scala index 93fbe87..fb6e27e 100644 --- a/src/main/scala/org/encryfoundation/generator/network/NetworkServer.scala +++ b/src/main/scala/org/encryfoundation/generator/network/NetworkServer.scala @@ -7,15 +7,14 @@ import akka.io.Tcp.SO.KeepAlive import akka.io.Tcp._ import akka.io.{IO, Tcp} import com.typesafe.scalalogging.StrictLogging +import org.encryfoundation.common.modifiers.mempool.transaction.Transaction import org.encryfoundation.generator.actors.Generator import org.encryfoundation.generator.actors.Generator.TransactionForCommit import org.encryfoundation.generator.network.BasicMessagesRepo.{InvNetworkMessage, Outgoing} import org.encryfoundation.generator.network.NetworkMessagesHandler.BroadcastInvForTx import org.encryfoundation.generator.network.NetworkServer.{CheckConnection, ConnectionSetupSuccessfully} import org.encryfoundation.generator.network.PeerHandler._ -import org.encryfoundation.generator.modifiers.Transaction import org.encryfoundation.generator.utils.CoreTaggedTypes.{ModifierId, ModifierTypeId} -import org.encryfoundation.generator.utils.Mnemonic.createPrivKey import org.encryfoundation.generator.utils.{NetworkTimeProvider, Settings} import scala.concurrent.duration._ @@ -87,11 +86,11 @@ class NetworkServer(settings: Settings, logger.debug(s"Send inv message to remote.") case ConnectionSetupSuccessfully => - settings.peers.foreach { peer => - logger.info(s"Created generator actor for ${peer.explorerHost}:${peer.explorerPort}.") - system.actorOf( - Generator.props(settings, createPrivKey(Some(peer.mnemonicKey)), peer, influx, self), peer.explorerHost) - } +// settings.peers.foreach { peer => +// logger.info(s"Created generator actor for ${peer.explorerHost}:${peer.explorerPort}.") +// system.actorOf( +// Generator.props(settings, createPrivateKey(Some(peer.mnemonicKey)), peer, influx, self), peer.explorerHost) +// } case msg@TransactionForCommit(_) => messagesHandler ! msg diff --git a/src/main/scala/org/encryfoundation/generator/network/Protocol.scala b/src/main/scala/org/encryfoundation/generator/network/Protocol.scala new file mode 100644 index 0000000..6f2c1b8 --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/network/Protocol.scala @@ -0,0 +1,9 @@ +package org.encryfoundation.generator.network + +import NetworkMessagesProto.GeneralizedNetworkProtoMessage +import scodec.bits.BitVector +import scodec.{Attempt, Codec, DecodeResult, SizeBound} + +object Protocol { + +} diff --git a/src/main/scala/org/encryfoundation/generator/processors/HttpApiBoxesProcessor.scala b/src/main/scala/org/encryfoundation/generator/processors/HttpApiBoxesProcessor.scala new file mode 100644 index 0000000..319dc56 --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/processors/HttpApiBoxesProcessor.scala @@ -0,0 +1,102 @@ +package org.encryfoundation.generator.processors + +import cats.Parallel +import cats.effect.{ Async, Sync, Timer } +import cats.instances.list._ +import cats.instances.long._ +import cats.syntax.applicativeError._ +import cats.syntax.apply._ +import cats.syntax.flatMap._ +import cats.syntax.functor._ +import cats.syntax.parallel._ +import cats.syntax.semigroup._ +import fs2.Stream +import io.chrisdavenport.log4cats.Logger +import org.encryfoundation.common.modifiers.state.box.{ AssetBox, MonetaryBox } +import org.encryfoundation.generator.actors.BoxesBatch +import org.encryfoundation.generator.storage.BatchesStorage.{ BatchType, MonetaryBatch } +import org.encryfoundation.generator.storage.{ BatchesStorage, ContractHashStorage } +import org.http4s.Uri +import org.http4s.circe._ +import org.http4s.client.Client +import io.circe.generic.auto._ +import scala.collection.immutable.HashMap +import scala.concurrent.duration._ + +final class HttpApiBoxesProcessor[F[_]: Async: Timer: Parallel]( + client: Client[F], + logger: Logger[F], + batchesStorage: BatchesStorage[F], + contractHashStorage: ContractHashStorage[F] +) { + + def run: Stream[F, Unit] = + Stream(()).repeat + .covary[F] + .metered(10.seconds) + .evalMap[F, Unit](_ => requestNewBoxesForAllKeys) + + private def requestNewBoxesForAllKeys: F[Unit] = + for { + addresses <- contractHashStorage.getAllElements + _ <- logger.info(s"Start processing new boxes from api. Addresses storage size is ${addresses.size}") + _ <- addresses.map(address => requestNUtxos(address, 0, 10)).parSequence + } yield () + + private def requestUtxoApi(contractHash: String, from: Int, to: Int): F[List[MonetaryBox]] = + client + .expect[List[MonetaryBox]]( + Uri.unsafeFromString(s"http://172.16.10.58:9000/wallet/$contractHash/boxes/$from/$to") + )(jsonOf[F, List[MonetaryBox]]) + .handleErrorWith { f: Throwable => + logger.error(s"While request utxos error ${f.getMessage} has occurred") *> Sync[F].pure(List.empty) + } <* logger.info(s"Received new boxes from http api!") + + private def requestNUtxos(contractHash: String, from: Int, to: Int): F[Unit] = + for { + _ <- logger.info(s"Start requesting boxes for $contractHash") + boxes <- requestUtxoApi(contractHash, from, to) + batches <- collectBatches(boxes) + _ <- Sync[F].delay(batches.map { + case (batchType, batches) => + batchesStorage.insert(contractHash, batchType, batches) + }) + size <- batchesStorage.getSizeByKey(contractHash, MonetaryBatch) + _ <- if (size > 100 || boxes.isEmpty) + Sync[F].unit <* logger.info( + s"Current number of boxes is $size. Received boxes size is ${boxes.size} Stop requesting new boxes" + ) + else + requestNUtxos(contractHash, to + 1, to + 11) <* + logger.info( + s"Current batches number for key $contractHash is $size. Need 100. Going to request more boxes" + ) + } yield () + + private def collectBatches(boxes: List[MonetaryBox]): F[HashMap[BatchType, List[BoxesBatch]]] = + Sync[F].pure( + boxes + .foldLeft(HashMap.empty[BatchType, List[BoxesBatch]], BoxesBatch.empty) { + case ((storage, batch), box: AssetBox) if box.tokenIdOpt.isEmpty => + val newBatchBoxes: List[MonetaryBox] = box :: batch.boxes + val nexBatchBoxesAmount: Long = newBatchBoxes.foldLeft(0L)(_ |+| _.amount) + if (nexBatchBoxesAmount >= 100000) { + val currentBatches: List[BoxesBatch] = storage.getOrElse(MonetaryBatch, List.empty) + val updatedBatches: List[BoxesBatch] = BoxesBatch(newBatchBoxes) :: currentBatches + storage.updated(MonetaryBatch, updatedBatches) -> BoxesBatch.empty + } else (storage, BoxesBatch(newBatchBoxes)) + case ((storage, batch), _) => (storage, batch) //todo implement later + } + ._1 + ) +} + +object HttpApiBoxesProcessor { + def apply[F[_]: Async: Timer: Parallel]( + client: Client[F], + logger: Logger[F], + storage: BatchesStorage[F], + contractHashStorage: ContractHashStorage[F] + ): F[HttpApiBoxesProcessor[F]] = + Sync[F].pure(new HttpApiBoxesProcessor[F](client, logger, storage, contractHashStorage)) +} diff --git a/src/main/scala/org/encryfoundation/generator/processors/TransactionGenerator.scala b/src/main/scala/org/encryfoundation/generator/processors/TransactionGenerator.scala new file mode 100644 index 0000000..908bfab --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/processors/TransactionGenerator.scala @@ -0,0 +1,57 @@ +package org.encryfoundation.generator.processors + +import cats.Parallel +import cats.effect.{Async, Sync, Timer} +import cats.instances.list._ +import cats.syntax.flatMap._ +import cats.syntax.functor._ +import cats.syntax.parallel._ +import fs2.Stream +import io.chrisdavenport.log4cats.Logger +import org.encryfoundation.generator.actors.BoxesBatch +import org.encryfoundation.generator.storage.{BatchesStorage, ContractHashStorage, TransactionsStorage} + +import scala.concurrent.duration._ + +final class TransactionGenerator[F[_]: Async: Parallel: Timer] private ( + logger: Logger[F], + batchesStorage: BatchesStorage[F], + contractHashStorage: ContractHashStorage[F], + transactionsStorage: TransactionsStorage[F] +) { + + def run: Stream[F, Unit] = + Stream(()).repeat + .covary[F] + .metered(2.seconds) + .evalMap[F, Unit](_ => createNewTransaction) + + private def createNewTransaction: F[Unit] = + for { + _ <- logger.info("Init createNewTransaction function") + keys <- contractHashStorage.getAllElements + _ <- keys.map(key => processBatch(key)).parSequence + } yield () + + private def processBatch(key: String): F[Unit] = + for { + _ <- logger.info("Init processBatch function") + batch <- batchesStorage.getMany(key, 5) + _ <- logger.info(s"Got ${batch.size} batches from storage") + } yield () + +// private def createNewTransaction(batch: BoxesBatch) = +// for { +// +// } +} + +object TransactionGenerator { + def apply[F[_]: Async: Parallel: Timer]( + logger: Logger[F], + batchesStorage: BatchesStorage[F], + contractHashStorage: ContractHashStorage[F], + transactionsStorage: TransactionsStorage[F] + ): F[TransactionGenerator[F]] = + Sync[F].pure(new TransactionGenerator[F](logger, batchesStorage, contractHashStorage, transactionsStorage)) +} diff --git a/src/main/scala/org/encryfoundation/generator/storage/BatchesStorage.scala b/src/main/scala/org/encryfoundation/generator/storage/BatchesStorage.scala new file mode 100644 index 0000000..6656027 --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/storage/BatchesStorage.scala @@ -0,0 +1,59 @@ +package org.encryfoundation.generator.storage + +import cats.effect.Sync +import cats.effect.concurrent.Ref +import cats.syntax.functor._ +import cats.syntax.apply._ +import org.encryfoundation.generator.actors.BoxesBatch +import io.chrisdavenport.log4cats.Logger +import org.encryfoundation.generator.storage.BatchesStorage.BatchType +import scala.collection.immutable.HashMap + +final class BatchesStorage[F[_]: Sync] private ( + ref: Ref[F, HashMap[String, HashMap[BatchType, List[BoxesBatch]]]], + logger: Logger[F] +) { + + def insert(contractHash: String, batchType: BatchType, batches: List[BoxesBatch]): F[Unit] = + ref.update { storage: HashMap[String, HashMap[BatchType, List[BoxesBatch]]] => + val existBatches: HashMap[BatchType, List[BoxesBatch]] = storage.getOrElse(contractHash, HashMap.empty) + val newBatches: List[BoxesBatch] = existBatches + .get(batchType) + .map(batches ::: _) + .getOrElse(batches) + val updatedBatches: HashMap[BatchType, List[BoxesBatch]] = existBatches.updated(batchType, newBatches) + storage.updated(contractHash, updatedBatches) + } *> logger.info( + s"Batch for key $contractHash with type $batchType and ${batches.size} batches inserted into storage" + ) + + def getMany(contractHash: String, batchType: BatchType, quantity: Int): F[List[BoxesBatch]] = + ref.modify { storage: HashMap[String, HashMap[BatchType, List[BoxesBatch]]] => + val existBatches: HashMap[BatchType, List[BoxesBatch]] = storage.getOrElse(contractHash, HashMap.empty) + val batchesByType: List[BoxesBatch] = existBatches.getOrElse(batchType, List.empty) + val response: List[BoxesBatch] = batchesByType.take(quantity) + val updatedBatches: HashMap[BatchType, List[BoxesBatch]] = + existBatches.updated(batchType, batchesByType.drop(quantity)) + val newStorage: HashMap[String, HashMap[BatchType, List[BoxesBatch]]] = + storage.updated(contractHash, updatedBatches) + newStorage -> response + } <* logger.info(s"Took $quantity batches of type $batchType from storage") + + def getSizeByKey(contractHash: String, batchType: BatchType): F[Int] = + ref.get.map(_.get(contractHash).map(_.get(batchType).map(_.size)).getOrElse(0)) + + def clean: F[Unit] = + ref.set(HashMap.empty[String, HashMap[BatchType, List[BoxesBatch]]]) <* + logger.info(s"Batches storage cleaned up") +} + +object BatchesStorage { + def apply[F[_]: Sync](logger: Logger[F]): F[BatchesStorage[F]] = + Ref[F] + .of(HashMap.empty[String, HashMap[BatchType, List[BoxesBatch]]]) + .map(ref => new BatchesStorage(ref, logger)) + + sealed trait BatchType + case object MonetaryBatch extends BatchType + case object AssetBatch extends BatchType +} diff --git a/src/main/scala/org/encryfoundation/generator/storage/ContractHashStorage.scala b/src/main/scala/org/encryfoundation/generator/storage/ContractHashStorage.scala new file mode 100644 index 0000000..4ac7ba9 --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/storage/ContractHashStorage.scala @@ -0,0 +1,60 @@ +package org.encryfoundation.generator.storage + +import cats.Apply +import cats.syntax.functor._ +import cats.syntax.apply._ +import cats.syntax.flatMap._ +import cats.effect.Sync +import cats.effect.concurrent.Ref +import io.chrisdavenport.log4cats.Logger +import org.encryfoundation.common.crypto.PrivateKey25519 +import org.encryfoundation.common.modifiers.mempool.transaction.PubKeyLockedContract +import org.encryfoundation.common.utils.Algos +import scorex.crypto.hash.Blake2b256 +import scorex.crypto.signatures.{ Curve25519, PrivateKey, PublicKey } + +final class ContractHashStorage[F[_]: Apply: Sync] private ( + logger: Logger[F], + ref: Ref[F, List[String]] +) extends InMemoryStorage[F, String] { + + def createNewContactHash(mnemonic: String): F[Unit] = + for { + elem <- createContractHash(mnemonic) + _ <- insert(elem) + } yield () + + def insert(elem: String): F[Unit] = + ref.update(elem :: _) <* + logger.info("Inserted new element into contract hash storage") + + def clean: F[Unit] = + ref.set(List.empty[String]) <* + logger.info("Contract hash storage cleaned up") + + def getAllElements: F[List[String]] = + ref.get <* + logger.info("Received all addresses from contract hash storage") + + def init: F[Unit] = + (for { + _ <- createNewContactHash( + mnemonic = "boat culture ribbon wagon deposit decrease maid speak equal thunder have beauty" + ) + _ <- createNewContactHash( + mnemonic = "napkin they pyramid verb modify brave hurry agent will still easy great" + ) + } yield ()) *> logger.info("Init contract hash storage") + + private def createContractHash(mnemonic: String): F[String] = { + val (privateKey: PrivateKey, publicKey: PublicKey) = + Curve25519.createKeyPair(Blake2b256.hash(Algos.hash(mnemonic + "mnemonic="))) + val privateKey25519: PrivateKey25519 = PrivateKey25519(privateKey, publicKey) + Sync[F].pure(Algos.encode(PubKeyLockedContract(privateKey25519.publicImage.pubKeyBytes).contract.hash)) + } +} + +object ContractHashStorage { + def apply[F[_]: Sync](logger: Logger[F]): F[ContractHashStorage[F]] = + Ref[F].of(List.empty[String]).map(ref => new ContractHashStorage(logger, ref)) +} diff --git a/src/main/scala/org/encryfoundation/generator/storage/InMemoryStorage.scala b/src/main/scala/org/encryfoundation/generator/storage/InMemoryStorage.scala new file mode 100644 index 0000000..86b3cb9 --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/storage/InMemoryStorage.scala @@ -0,0 +1,6 @@ +package org.encryfoundation.generator.storage + +trait InMemoryStorage[F[_], T] { + def insert(elem: T): F[Unit] + def clean: F[Unit] +} diff --git a/src/main/scala/org/encryfoundation/generator/storage/ProcessedBoxesIdsStorage.scala b/src/main/scala/org/encryfoundation/generator/storage/ProcessedBoxesIdsStorage.scala new file mode 100644 index 0000000..29ffedf --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/storage/ProcessedBoxesIdsStorage.scala @@ -0,0 +1,37 @@ +package org.encryfoundation.generator.storage + +import cats.Functor +import cats.syntax.functor._ +import cats.effect.Sync +import cats.effect.concurrent.Ref +import com.google.common.base.Charsets +import com.google.common.hash.{BloomFilter, Funnels} + +final class ProcessedBoxesIdsStorage[F[_] : Functor] private( + ref: Ref[F, BloomFilter[String]] +) + //extends InMemoryStorage[F, String] +{ +// +// def insert(elem: String): F[Unit] = ref.update { filter => +// filter.put(elem) +// filter +// } +// +// def clean: F[Unit] = ref.set(ProcessedBoxesIdsStorage.initFilter) +// +// def ifContains(elem: String): F[Boolean] = ref.get.map(_.mightContain(elem)) +} + +object ProcessedBoxesIdsStorage { +// def apply[F[_]: Sync: Functor]: F[ProcessedBoxesIdsStorage[F]] = +// Ref[F] +// .of(initFilter) +// .map(new ProcessedBoxesIdsStorage(_)) +// +// def initFilter: BloomFilter[String] = BloomFilter.create( +// Funnels.stringFunnel(Charsets.UTF_8), +// 10000, +// Double.MinPositiveValue +// ) +} diff --git a/src/main/scala/org/encryfoundation/generator/storage/TransactionsStorage.scala b/src/main/scala/org/encryfoundation/generator/storage/TransactionsStorage.scala new file mode 100644 index 0000000..da08ecd --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/storage/TransactionsStorage.scala @@ -0,0 +1,24 @@ +package org.encryfoundation.generator.storage + +import cats.syntax.functor._ +import cats.effect.Sync +import cats.effect.concurrent.Ref +import org.encryfoundation.common.modifiers.mempool.transaction.Transaction +import scala.collection.immutable.HashMap + +final class TransactionsStorage[F[_]: Sync]( + ref: Ref[F, HashMap[String, Transaction]] +) extends InMemoryStorage[F, (String, Transaction)] { + def insert(elem: (String, Transaction)): F[Unit] = ref.update(_.updated(elem._1, elem._2)) + + def clean: F[Unit] = ref.set(HashMap.empty[String, Transaction]) + + def get(key: String): F[Option[Transaction]] = ref.modify(storage => storage - key -> storage.get(key)) +} + +object TransactionsStorage { + def apply[F[_]: Sync]: F[TransactionsStorage[F]] = + Ref[F] + .of(HashMap.empty[String, Transaction]) + .map(new TransactionsStorage(_)) +} diff --git a/src/main/scala/org/encryfoundation/generator/transaction/TransactionsFrame.scala b/src/main/scala/org/encryfoundation/generator/transaction/TransactionsFrame.scala new file mode 100644 index 0000000..09a3009 --- /dev/null +++ b/src/main/scala/org/encryfoundation/generator/transaction/TransactionsFrame.scala @@ -0,0 +1,66 @@ +package org.encryfoundation.generator.transaction + +import org.encryfoundation.common.crypto.{PrivateKey25519, PublicKey25519, Signature25519} +import org.encryfoundation.common.modifiers.mempool.directive.{Directive, TransferDirective} +import org.encryfoundation.common.modifiers.mempool.transaction.{Input, Proof, PubKeyLockedContract, Transaction} +import org.encryfoundation.common.modifiers.state.box.MonetaryBox +import org.encryfoundation.common.utils.TaggedTypes.ADKey +import org.encryfoundation.generator.modifiers.TransactionsFactory.{logger, prepareTransaction} +import org.encryfoundation.generator.modifiers.UnsignedEncryTransaction +import org.encryfoundation.prismlang.compiler.CompiledContract +import org.encryfoundation.prismlang.core.wrapped.BoxedValue + +import scala.util.Random + +object TransactionsFrame { + + def defaultPaymentTransaction(privKey: PrivateKey25519, + fee: Long, + timestamp: Long, + useOutputs: Seq[(MonetaryBox, Option[(CompiledContract, Seq[Proof])])], + recipient: String, + numberOfCreatedDirectives: Int = 1, + tokenIdOpt: Option[ADKey] = None): Transaction = { + val howMuchCanTransfer: Long = useOutputs.map(_._1.amount).sum - fee + val howMuchWillTransfer: Long = howMuchCanTransfer - Math.abs(Random.nextLong % howMuchCanTransfer) + val change: Long = howMuchCanTransfer - howMuchWillTransfer + val directives: IndexedSeq[TransferDirective] = + IndexedSeq(TransferDirective(recipient, howMuchWillTransfer, tokenIdOpt)) + prepareTransaction(privKey, fee, timestamp, useOutputs, directives, change, tokenIdOpt) + } + + private def prepareTransaction(privKey: PrivateKey25519, + fee: Long, + timestamp: Long, + useOutputs: Seq[(MonetaryBox, Option[(CompiledContract, Seq[Proof])])], + directivesSeq: IndexedSeq[Directive], + change: Long, + tokenIdOpt: Option[ADKey] = None): Transaction = { + + val pubKey: PublicKey25519 = privKey.publicImage + + val uInputs: IndexedSeq[Input] = useOutputs.toIndexedSeq.map { case (box, contractOpt) => + Input.unsigned( + box.id, + contractOpt match { + case Some((ct, _)) => Left(ct) + case None => Right(PubKeyLockedContract(pubKey.pubKeyBytes)) + } + ) + } + + if (change < 0) { + throw new RuntimeException("Transaction impossible: required amount is bigger than available") + } + + val directives: IndexedSeq[Directive] = + if (change > 0) directivesSeq ++: IndexedSeq(TransferDirective(pubKey.address.address, change, tokenIdOpt)) + else directivesSeq + + val uTransaction: UnsignedEncryTransaction = UnsignedEncryTransaction(fee, timestamp, uInputs, directives) + val signature: Signature25519 = privKey.sign(uTransaction.messageToSign) + val proofs: IndexedSeq[Seq[Proof]] = useOutputs.flatMap(_._2.map(_._2)).toIndexedSeq + + uTransaction.toSigned(proofs, Some(Proof(BoxedValue.Signature25519Value(signature.bytes.toList)))) + } +} diff --git a/src/main/scala/org/encryfoundation/generator/utils/Mnemonic.scala b/src/main/scala/org/encryfoundation/generator/utils/Mnemonic.scala deleted file mode 100644 index 7df5918..0000000 --- a/src/main/scala/org/encryfoundation/generator/utils/Mnemonic.scala +++ /dev/null @@ -1,40 +0,0 @@ -package org.encryfoundation.generator.utils - -import org.encryfoundation.common.crypto.PrivateKey25519 -import org.encryfoundation.common.utils.Algos -import scodec.bits.BitVector -import scorex.crypto.hash.Blake2b256 -import scorex.crypto.signatures.{Curve25519, PrivateKey, PublicKey} - -import scala.io.Source - -object Mnemonic { - - def createPrivKey(seed: Option[String]): PrivateKey25519 = { - val (privateKey: PrivateKey, publicKey: PublicKey) = Curve25519.createKeyPair( - Blake2b256.hash( - seed.map { - seedFromMnemonic(_) - } - .getOrElse { - val phrase: String = entropyToMnemonicCode(scorex.utils.Random.randomBytes(16)) - seedFromMnemonic(phrase) - }) - ) - PrivateKey25519(privateKey, publicKey) - } - - private def seedFromMnemonic(mnemonicCode: String, passPhrase: String = ""): Array[Byte] = - Algos.hash(mnemonicCode + "mnemonic=" + passPhrase) - - private def entropyToMnemonicCode(entropy: Array[Byte]): String = { - val words: Array[String] = - Source.fromInputStream(getClass.getResourceAsStream("/languages/english/words.txt")).getLines.toArray - val checkSum: BitVector = BitVector(Algos.hash(entropy)) - val entropyWithCheckSum: BitVector = BitVector(entropy) ++ checkSum.take(4) - - entropyWithCheckSum.grouped(11).map { i => - words(i.toInt(signed = false)) - }.mkString(" ") - } -} diff --git a/src/main/scala/org/encryfoundation/generator/utils/NetworkService.scala b/src/main/scala/org/encryfoundation/generator/utils/NetworkService.scala index e2dc2d5..3f26930 100644 --- a/src/main/scala/org/encryfoundation/generator/utils/NetworkService.scala +++ b/src/main/scala/org/encryfoundation/generator/utils/NetworkService.scala @@ -8,27 +8,27 @@ import com.typesafe.scalalogging.StrictLogging import io.circe.{Decoder, HCursor} import io.circe.parser.decode import org.encryfoundation.common.modifiers.mempool.transaction.PubKeyLockedContract +import org.encryfoundation.common.modifiers.state.box.AssetBox import org.encryfoundation.common.utils.Algos import org.encryfoundation.generator.GeneratorApp._ -import org.encryfoundation.generator.modifiers.box.Box import scala.concurrent.Future import scala.util.control.NonFatal object NetworkService extends StrictLogging { - def requestUtxos(node: Node, from: Int, to: Int): Future[List[Box]] = { - val privKey = Mnemonic.createPrivKey(Option(node.mnemonicKey)) - val contractHash: String = Algos.encode(PubKeyLockedContract(privKey.publicImage.pubKeyBytes).contract.hash) - Http().singleRequest(HttpRequest( - method = HttpMethods.GET, - uri = s"/wallet/$contractHash/boxes/$from/$to" - ).withEffectiveUri(securedConnection = false, Host(node.explorerHost, node.explorerPort))) - .flatMap(_.entity.dataBytes.runFold(ByteString.empty)(_ ++ _)) - .map(_.utf8String) - .map(decode[List[Box]]) - .flatMap(_.fold(Future.failed, Future.successful)) - } +// def requestUtxos(node: Node, from: Int, to: Int): Future[List[AssetBox]] = { +// val privKey = Mnemonic.createPrivateKey(Option(node.mnemonicKey)) +// val contractHash: String = Algos.encode(PubKeyLockedContract(privKey.publicImage.pubKeyBytes).contract.hash) +// Http().singleRequest(HttpRequest( +// method = HttpMethods.GET, +// uri = s"/wallet/$contractHash/boxes/$from/$to" +// ).withEffectiveUri(securedConnection = false, Host(node.explorerHost, node.explorerPort))) +// .flatMap(_.entity.dataBytes.runFold(ByteString.empty)(_ ++ _)) +// .map(_.utf8String) +// .map(decode[List[AssetBox]]) +// .flatMap(_.fold(Future.failed, Future.successful)) +// } def checkTxsInBlockchain(node: NetworkSettings, txsToCheck: Vector[String], numberOfBlocks: Int): Future[List[String]] = Http().singleRequest(HttpRequest( diff --git a/src/test/scala/utils/Helper.scala b/src/test/scala/utils/Helper.scala index cd94379..593b436 100644 --- a/src/test/scala/utils/Helper.scala +++ b/src/test/scala/utils/Helper.scala @@ -2,14 +2,13 @@ package utils import org.encryfoundation.common.modifiers.mempool.transaction.EncryAddress.Address import org.encryfoundation.common.utils.TaggedTypes.ADKey -import org.encryfoundation.generator.modifiers.box.{AssetBox, EncryProposition} import scala.util.Random object Helper { - def genAssetBox(address: Address, amount: Long = 100000, tokenIdOpt: Option[ADKey] = None): AssetBox = - AssetBox(EncryProposition.addressLocked(address), Random.nextLong(), amount, tokenIdOpt) - - def genNAssetBoxes(number: Int, address: Address): IndexedSeq[AssetBox] = (1 to number).map(_ => genAssetBox(address)) +// def genAssetBox(address: Address, amount: Long = 100000, tokenIdOpt: Option[ADKey] = None): AssetBox = +// AssetBox(EncryProposition.addressLocked(address), Random.nextLong(), amount, tokenIdOpt) +// +// def genNAssetBoxes(number: Int, address: Address): IndexedSeq[AssetBox] = (1 to number).map(_ => genAssetBox(address)) } \ No newline at end of file