diff --git a/build.sbt b/build.sbt index 7d892463b4a..e7aae6e0c4a 100644 --- a/build.sbt +++ b/build.sbt @@ -90,12 +90,8 @@ lazy val `grpc-server` = // lazy val `ride-runner` = project.dependsOn(node, `grpc-server`, `node-tests` % "test->test") lazy val `node-it` = project.dependsOn(`repl-jvm`, `grpc-server`, `lang-testkit` % "test->test", `node-testkit`) -// TODO: [scala3] enable -// lazy val `node-generator` = project -// .dependsOn(node, `node-testkit`, `node-tests` % "compile->test") -// .settings( -// libraryDependencies += "com.iheart" %% "ficus" % "1.5.2" -// ) +lazy val `node-generator` = project.dependsOn(node, `node-testkit`, `node-testkit`) + lazy val benchmark = project.dependsOn(node, `node-tests` % "test->test") lazy val repl = crossProject(JSPlatform, JVMPlatform) diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/DynamicWideTransactionGenerator.scala b/node-generator/src/main/scala/com/wavesplatform/generator/DynamicWideTransactionGenerator.scala index b3cc040fdba..1bfdd129c01 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/DynamicWideTransactionGenerator.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/DynamicWideTransactionGenerator.scala @@ -1,12 +1,13 @@ package com.wavesplatform.generator -import java.util.concurrent.atomic.AtomicReference - import cats.Show import com.wavesplatform.account.KeyPair import com.wavesplatform.generator.DynamicWideTransactionGenerator.Settings import com.wavesplatform.generator.utils.Gen import com.wavesplatform.transaction.Transaction +import pureconfig.ConfigReader + +import java.util.concurrent.atomic.AtomicReference class DynamicWideTransactionGenerator(settings: Settings, accounts: Seq[KeyPair]) extends TransactionGenerator { require(accounts.nonEmpty) @@ -28,13 +29,13 @@ class DynamicWideTransactionGenerator(settings: Settings, accounts: Seq[KeyPair] object DynamicWideTransactionGenerator { - case class Settings(start: Int, growAdder: Double, maxTxsPerRequest: Option[Int], limitDestAccounts: Option[Int], minFee: Long, maxFee: Long) { + case class Settings(start: Int, growAdder: Double, maxTxsPerRequest: Option[Int], limitDestAccounts: Option[Int], minFee: Long, maxFee: Long)derives ConfigReader { require(start >= 1) } object Settings { implicit val toPrintable: Show[Settings] = { x => - import x._ + import x.* s"""txs at start: $start |grow adder: $growAdder |max txs: $maxTxsPerRequest diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/GeneratorSettings.scala b/node-generator/src/main/scala/com/wavesplatform/generator/GeneratorSettings.scala index af54ed277a0..e9b1ed275ee 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/GeneratorSettings.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/GeneratorSettings.scala @@ -1,35 +1,50 @@ package com.wavesplatform.generator -import java.net.{InetSocketAddress, URL} -import java.nio.charset.StandardCharsets import cats.Show import cats.implicits.showInterpolator import com.google.common.primitives.{Bytes, Ints} import com.wavesplatform.account.{KeyPair, SeedKeyPair} import com.wavesplatform.generator.GeneratorSettings.NodeAddress +import com.wavesplatform.generator.config.FicusImplicits +import com.wavesplatform.settings.* +import pureconfig.ConfigReader +import pureconfig.generic.derivation.* + +import java.net.{InetSocketAddress, URI, URL} +import java.nio.charset.StandardCharsets +import scala.util.Try case class GeneratorSettings( chainId: String, accounts: Seq[String], sendTo: Seq[NodeAddress], worker: Worker.Settings, - mode: Mode.Value, + mode: Mode, narrow: NarrowTransactionGenerator.Settings, wide: WideTransactionGenerator.Settings, dynWide: DynamicWideTransactionGenerator.Settings, multisig: MultisigTransactionGenerator.Settings, oracle: OracleTransactionGenerator.Settings, swarm: SmartGenerator.Settings -) { + )derives ConfigReader { val addressScheme: Char = chainId.head val privateKeyAccounts: Seq[SeedKeyPair] = accounts.map(s => GeneratorSettings.toKeyPair(s)) } -object GeneratorSettings { - case class NodeAddress(networkAddress: InetSocketAddress, apiAddress: URL) +object GeneratorSettings extends FicusImplicits { + given ConfigReader[InetSocketAddress] = ConfigReader.fromStringTry(str => + Try { + val url = new URI(s"my://$str") + new InetSocketAddress(url.getHost, url.getPort) + } + ) + + given ConfigReader[URL] = ConfigReader[String].map(str => new URL(str)) + + case class NodeAddress(networkAddress: InetSocketAddress, apiAddress: URL)derives ConfigReader implicit val toPrintable: Show[GeneratorSettings] = { x => - import x._ + import x.* val modeSettings: String = (mode: @unchecked) match { case Mode.NARROW => show"$narrow" diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/Mode.scala b/node-generator/src/main/scala/com/wavesplatform/generator/Mode.scala index 2107178b07f..703f1702c06 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/Mode.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/Mode.scala @@ -1,6 +1,7 @@ package com.wavesplatform.generator -object Mode extends Enumeration { - type Mode = Value - val WIDE, NARROW, DYN_WIDE, MULTISIG, ORACLE, SWARM = Value +import pureconfig.generic.derivation.EnumConfigReader + +enum Mode derives EnumConfigReader { + case WIDE, NARROW, DYN_WIDE, MULTISIG, ORACLE, SWARM } diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/MultisigTransactionGenerator.scala b/node-generator/src/main/scala/com/wavesplatform/generator/MultisigTransactionGenerator.scala index 0eba076a00e..51f75123351 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/MultisigTransactionGenerator.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/MultisigTransactionGenerator.scala @@ -4,6 +4,7 @@ import cats.Show import com.wavesplatform.account.KeyPair import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.common.utils.EitherExt2.explicitGet import com.wavesplatform.crypto import com.wavesplatform.generator.utils.Gen import com.wavesplatform.generator.utils.Implicits.DoubleExt @@ -13,6 +14,7 @@ import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.smart.SetScriptTransaction import com.wavesplatform.transaction.transfer.TransferTransaction import com.wavesplatform.transaction.{Proofs, Transaction, TxPositiveAmount} +import pureconfig.ConfigReader import scala.util.Random @@ -67,7 +69,7 @@ class MultisigTransactionGenerator(settings: MultisigTransactionGenerator.Settin } object MultisigTransactionGenerator { - final case class Settings(transactions: Int, firstRun: Boolean) + final case class Settings(transactions: Int, firstRun: Boolean)derives ConfigReader object Settings { implicit val toPrintable: Show[Settings] = { x => diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/NarrowTransactionGenerator.scala b/node-generator/src/main/scala/com/wavesplatform/generator/NarrowTransactionGenerator.scala index cf889571e6c..7e89f54b127 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/NarrowTransactionGenerator.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/NarrowTransactionGenerator.scala @@ -1,12 +1,11 @@ package com.wavesplatform.generator -import java.nio.file.{Files, Paths} -import java.util.UUID -import java.util.concurrent.ThreadLocalRandom import cats.Show import com.wavesplatform.account.{KeyPair, SeedKeyPair} import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2.explicitGet import com.wavesplatform.common.utils.{Base58, EitherExt2} +import com.wavesplatform.generator.config.FicusImplicits import com.wavesplatform.generator.utils.{Gen, Universe} import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.v1.FunctionHeader @@ -14,22 +13,27 @@ import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.estimator.ScriptEstimator import com.wavesplatform.state.DataEntry.{MaxValueSize, Type} import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, IntegerDataEntry, StringDataEntry} +import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TransactionType.TransactionType -import com.wavesplatform.transaction.* import com.wavesplatform.transaction.assets.* import com.wavesplatform.transaction.assets.exchange.* import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction} -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer import com.wavesplatform.transaction.transfer.* +import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer import com.wavesplatform.transaction.utils.Signed import com.wavesplatform.utils.{LoggerFacade, NTP} import org.slf4j.LoggerFactory import org.web3j.crypto.Bip32ECKeyPair +import pureconfig.ConfigReader +import java.nio.file.{Files, Paths} +import java.util.UUID +import java.util.concurrent.ThreadLocalRandom import scala.concurrent.duration.* +import scala.reflect.ClassTag import scala.util.Random import scala.util.Random.* @@ -44,16 +48,16 @@ class NarrowTransactionGenerator( ) extends TransactionGenerator { import NarrowTransactionGenerator.* - private[this] val log = LoggerFacade(LoggerFactory.getLogger(getClass)) - private[this] val typeGen = DistributedRandomGenerator(settings.probabilities) + private val log = LoggerFacade(LoggerFactory.getLogger(getClass)) + private val typeGen = DistributedRandomGenerator(settings.probabilities) - private[this] def correctVersion(v: TxVersion): TxVersion = + private def correctVersion(v: TxVersion): TxVersion = if (settings.protobuf) (v + 1).toByte else v override def next(): Iterator[Transaction] = generate(settings.transactions).iterator - private[this] def generate(n: Int): Seq[Transaction] = { + private def generate(n: Int): Seq[Transaction] = { val now = System.currentTimeMillis() val generated = (0 until (n * 1.2).toInt).foldLeft( @@ -434,7 +438,7 @@ class NarrowTransactionGenerator( else ByteStr(Array.fill(random.nextInt(100))(random.nextInt().toByte)) } - private[this] def logOption[T <: Transaction](txE: Either[ValidationError, T])(implicit m: Manifest[T]): Option[T] = { + private def logOption[T <: Transaction](txE: Either[ValidationError, T])(implicit m: ClassTag[T]): Option[T] = { txE match { case Left(e) => log.warn(s"${m.runtimeClass.getName}: ${e.toString}") @@ -443,12 +447,12 @@ class NarrowTransactionGenerator( } } - private[this] def accountByAddress(address: String): Option[KeyPair] = + private def accountByAddress(address: String): Option[KeyPair] = accounts .find(_.toAddress.toString == address) .orElse(Universe.Accounts.map(_.keyPair).find(_.toAddress.toString == address)) - private[this] def randomSenderAndAsset(issueTxs: Seq[IssueTransaction]): Option[(KeyPair, Option[ByteStr])] = + private def randomSenderAndAsset(issueTxs: Seq[IssueTransaction]): Option[(KeyPair, Option[ByteStr])] = if (random.nextBoolean()) { (randomFrom(issueTxs) orElse randomFrom(Universe.IssuedAssets)).map { issue => val pk = (accounts ++ Universe.Accounts.map(_.keyPair)).find(_.publicKey == issue.sender).get @@ -467,21 +471,21 @@ class NarrowTransactionGenerator( } } -object NarrowTransactionGenerator { +object NarrowTransactionGenerator extends FicusImplicits { final case class ScriptSettings( dappAccount: String, paymentAssets: Set[String], functions: Seq[ScriptSettings.Function], scriptFile: Option[String] - ) { + )derives ConfigReader { def dappAccountKP = GeneratorSettings.toKeyPair(dappAccount) def dappAddress = dappAccountKP.toAddress } object ScriptSettings { - final case class Function(name: String, args: Seq[Function.Arg]) + final case class Function(name: String, args: Seq[Function.Arg])derives ConfigReader object Function { - final case class Arg(`type`: String, value: String) + final case class Arg(`type`: String, value: String)derives ConfigReader } } @@ -489,11 +493,12 @@ object NarrowTransactionGenerator { richAccount: String, accounts: SetScriptSettings.Accounts, assets: SetScriptSettings.Assets - ) + )derives ConfigReader object SetScriptSettings { - final case class Accounts(balance: Long, scriptFile: String, repeat: Int) + final case class Accounts(balance: Long, scriptFile: String, repeat: Int)derives ConfigReader final case class Assets(description: String, amount: Long, decimals: Int, reissuable: Boolean, scriptFile: String, repeat: Int) + derives ConfigReader } final case class Settings( @@ -502,11 +507,11 @@ object NarrowTransactionGenerator { scripts: Seq[ScriptSettings], setScript: Option[SetScriptSettings], protobuf: Boolean - ) + )derives ConfigReader object Settings { implicit val toPrintable: Show[Settings] = { x => - import x._ + import x.* s"""transactions per iteration: $transactions |probabilities: | ${probabilities.mkString("\n ")}""".stripMargin @@ -556,7 +561,7 @@ object NarrowTransactionGenerator { val (accountInitTxs, accountTailInitTxs, accounts) = ((1 to accountsSettings.repeat) foldLeft ((Seq.empty[Transaction], Seq.empty[Transaction], Seq.empty[KeyPair]))) { case ((initTxs, tailInitTxs, accounts), _) => - import accountsSettings._ + import accountsSettings.* val account = GeneratorSettings.toKeyPair(s"${UUID.randomUUID().toString}") @@ -573,7 +578,7 @@ object NarrowTransactionGenerator { val assetTailInitTxs = if (settings.probabilities.keySet.contains(TransactionType.SetAssetScript)) ((1 to assetsSettings.repeat) foldLeft Seq.empty[IssueTransaction]) { case (txs, i) => - import assetsSettings._ + import assetsSettings.* val issuer = randomFrom(accounts).get val script = ScriptCompiler.compile(new String(Files.readAllBytes(Paths.get(scriptFile))), estimator).explicitGet()._1 diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/OracleTransactionGenerator.scala b/node-generator/src/main/scala/com/wavesplatform/generator/OracleTransactionGenerator.scala index 5decf112423..cb64a1f9c53 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/OracleTransactionGenerator.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/OracleTransactionGenerator.scala @@ -4,15 +4,18 @@ import cats.Show import com.wavesplatform.account.KeyPair import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.common.utils.EitherExt2.explicitGet import com.wavesplatform.generator.OracleTransactionGenerator.Settings +import com.wavesplatform.generator.config.FicusImplicits import com.wavesplatform.generator.utils.Gen import com.wavesplatform.generator.utils.Implicits.DoubleExt import com.wavesplatform.lang.v1.estimator.ScriptEstimator -import com.wavesplatform.state._ +import com.wavesplatform.state.* import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.smart.SetScriptTransaction import com.wavesplatform.transaction.transfer.TransferTransaction import com.wavesplatform.transaction.{DataTransaction, Transaction} +import pureconfig.ConfigReader class OracleTransactionGenerator(settings: Settings, val accounts: Seq[KeyPair], estimator: ScriptEstimator) extends TransactionGenerator { override def next(): Iterator[Transaction] = generate(settings).iterator @@ -46,8 +49,8 @@ class OracleTransactionGenerator(settings: Settings, val accounts: Seq[KeyPair], } } -object OracleTransactionGenerator { - final case class Settings(transactions: Int, requiredData: Set[DataEntry[_]]) +object OracleTransactionGenerator extends FicusImplicits { + final case class Settings(transactions: Int, requiredData: Set[DataEntry[?]])derives ConfigReader object Settings { implicit val toPrintable: Show[Settings] = { x => diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/Preconditions.scala b/node-generator/src/main/scala/com/wavesplatform/generator/Preconditions.scala index 1d55c4f173e..ad35ee20b1e 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/Preconditions.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/Preconditions.scala @@ -1,11 +1,11 @@ package com.wavesplatform.generator -import java.nio.file.{Files, Paths} - +import com.google.common.primitives.{Bytes, Ints} import com.typesafe.config.Config import com.wavesplatform.account.{Address, KeyPair} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.common.utils.EitherExt2.explicitGet import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.estimator.ScriptEstimator import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} @@ -16,22 +16,40 @@ import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.transfer.TransferTransaction import com.wavesplatform.transaction.{Transaction, TxVersion} import com.wavesplatform.utils.Time -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ValueReader +import pureconfig.ConfigReader +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Paths} import scala.collection.Factory +import scala.util.Try object Preconditions { - private[this] val Fee = 1500000L + private val Fee = 1500000L - sealed abstract class PAction(val priority: Int) extends Product with Serializable + sealed trait PAction { + def priority: Int + } - final case class LeaseP(from: KeyPair, to: Address, amount: Long, repeat: Option[Int]) extends PAction(3) + final case class LeaseP(from: KeyPair, to: Address, amount: Long, repeat: Option[Int]) extends PAction derives ConfigReader { + override def priority: Int = 3 + } final case class IssueP(name: String, issuer: KeyPair, desc: String, amount: Long, decimals: Int, reissuable: Boolean, scriptFile: String) - extends PAction(2) - final case class CreateAccountP(seed: String, balance: Long, scriptFile: Option[String]) extends PAction(1) + extends PAction derives ConfigReader { + override def priority: Int = 2 + } - final case class PGenSettings(faucet: KeyPair, actions: List[PAction]) + final case class CreateAccountP(seed: String, balance: Long, scriptFile: Option[String]) extends PAction derives ConfigReader { + override def priority: Int = 1 + } + + given ConfigReader[KeyPair] = + ConfigReader[String].map(s => KeyPair(com.wavesplatform.crypto.secureHash(Bytes.concat(Ints.toByteArray(0), s.getBytes(StandardCharsets.UTF_8))))) + + given ConfigReader[Address] = ConfigReader.fromStringTry(str => Try(Address.fromString(str).explicitGet())) + + final case class PGenSettings(faucet: KeyPair, accounts: List[CreateAccountP], leases: List[LeaseP], assets: List[IssueP])derives ConfigReader { + val actions: List[PAction] = accounts ++ assets ++ leases + } final case class CreatedAccount(keyPair: KeyPair, balance: Long, script: Option[Script]) @@ -115,77 +133,4 @@ object Preconditions { (holder, headTransactions, tailTransactions) } - - private val accountSectionReader = new ValueReader[CreateAccountP] { - override def read(config: Config, path: String): CreateAccountP = { - val conf = config.getConfig(path) - - val seed = conf.as[String]("seed") - val balance = conf.as[Long]("balance") - val scriptFile = conf.as[Option[String]]("script-file").filter(_.nonEmpty) - - CreateAccountP(seed, balance, scriptFile) - } - } - - private val leasingSectionReader = new ValueReader[LeaseP] { - override def read(config: Config, path: String): LeaseP = { - val conf = config.getConfig(path) - - val from = conf.as[String]("from") - val to = conf.as[String]("to") - val amount = conf.as[Long]("amount") - val repeat = conf.as[Option[Int]]("repeat") - - LeaseP( - GeneratorSettings.toKeyPair(from), - GeneratorSettings.toKeyPair(to).toAddress, - amount, - repeat - ) - } - } - - private val assetSectionReader = new ValueReader[IssueP] { - override def read(config: Config, path: String): IssueP = { - val conf = config.getConfig(path) - val issuer = GeneratorSettings.toKeyPair(conf.getString("issuer")) - - val name = conf.as[Option[String]]("name").getOrElse("") - val description = conf.as[Option[String]]("description").getOrElse("") - val amount = conf.as[Long]("amount") - val decimals = conf.as[Option[Int]]("decimals").getOrElse(0) - val reissuable = conf.as[Option[Boolean]]("reissuable").getOrElse(false) - val scriptFile = conf.as[Option[String]]("script-file").getOrElse("") - - IssueP(name, issuer, description, amount, decimals, reissuable, scriptFile) - } - } - - implicit val preconditionsReader: ValueReader[PGenSettings] = (config: Config, path: String) => { - val faucet = GeneratorSettings.toKeyPair(config.as[String](s"$path.faucet")) - - val accounts = - config.as[List[CreateAccountP]](s"$path.accounts")( - traversableReader( - accountSectionReader, - implicitly[Factory[CreateAccountP, List[CreateAccountP]]] - ) - ) - val assets = config.as[List[IssueP]](s"$path.assets")( - traversableReader( - assetSectionReader, - implicitly[Factory[IssueP, List[IssueP]]] - ) - ) - val leases = config.as[List[LeaseP]](s"$path.leases")( - traversableReader( - leasingSectionReader, - implicitly[Factory[LeaseP, List[LeaseP]]] - ) - ) - - val actions = accounts ++ assets ++ leases - PGenSettings(faucet, actions) - } } diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/SmartGenerator.scala b/node-generator/src/main/scala/com/wavesplatform/generator/SmartGenerator.scala index 3bcee3399d1..4ef409cdd3b 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/SmartGenerator.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/SmartGenerator.scala @@ -1,11 +1,10 @@ package com.wavesplatform.generator -import java.util.concurrent.ThreadLocalRandom - import cats.Show import com.wavesplatform.account.KeyPair import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.common.utils.EitherExt2.explicitGet import com.wavesplatform.generator.utils.Gen import com.wavesplatform.generator.utils.Implicits.DoubleExt import com.wavesplatform.lang.script.Script @@ -15,8 +14,10 @@ import com.wavesplatform.transaction.assets.exchange.{AssetPair, ExchangeTransac import com.wavesplatform.transaction.smart.SetScriptTransaction import com.wavesplatform.transaction.transfer.TransferTransaction import com.wavesplatform.transaction.{Asset, Transaction, TxVersion} +import pureconfig.ConfigReader -import scala.concurrent.duration._ +import java.util.concurrent.ThreadLocalRandom +import scala.concurrent.duration.* class SmartGenerator(settings: SmartGenerator.Settings, val accounts: Seq[KeyPair], estimator: ScriptEstimator) extends TransactionGenerator { private def r = ThreadLocalRandom.current @@ -67,7 +68,7 @@ class SmartGenerator(settings: SmartGenerator.Settings, val accounts: Seq[KeyPai } object SmartGenerator { - final case class Settings(scripts: Int, transfers: Int, complexity: Boolean, exchange: Int, assets: Set[String]) { + final case class Settings(scripts: Int, transfers: Int, complexity: Boolean, exchange: Int, assets: Set[String])derives ConfigReader { require(scripts >= 0) require(transfers >= 0) require(exchange >= 0) @@ -75,7 +76,7 @@ object SmartGenerator { object Settings { implicit val toPrintable: Show[Settings] = { x => - import x._ + import x.* s""" | set-scripts = $scripts | transfers = $transfers diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala b/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala index 6854b83f448..0ff3132db24 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala @@ -1,13 +1,8 @@ package com.wavesplatform.generator -import java.io.File -import java.net.{InetSocketAddress, URI} -import java.util.concurrent.Executors -import scala.concurrent.* -import scala.concurrent.duration.* -import scala.util.{Failure, Random, Success} import cats.implicits.showInterpolator import com.typesafe.config.{Config, ConfigFactory} +import com.wavesplatform.Application import com.wavesplatform.account.AddressScheme import com.wavesplatform.features.EstimatorProvider.* import com.wavesplatform.generator.GeneratorSettings.NodeAddress @@ -18,26 +13,23 @@ import com.wavesplatform.generator.utils.Universe import com.wavesplatform.network.client.NetworkSender import com.wavesplatform.transaction.Transaction import com.wavesplatform.utils.{LoggerFacade, NTP} -import com.wavesplatform.Application import monix.execution.Scheduler -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.{EnumerationReader, NameMapper, ValueReader} -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* import org.asynchttpclient.AsyncHttpClient import org.asynchttpclient.Dsl.asyncHttpClient import org.slf4j.LoggerFactory +import pureconfig.* +import pureconfig.generic.derivation.* import scopt.OptionParser -object TransactionsGeneratorApp extends App with ScoptImplicits with FicusImplicits with EnumerationReader { - - implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = { (config: Config, path: String) => - val uri = new URI(s"my://${config.getString(path)}") - new InetSocketAddress(uri.getHost, uri.getPort) - } +import java.io.File +import java.net.{InetSocketAddress, URI} +import java.util.concurrent.Executors +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.util.{Failure, Random, Success} - // IDEA bugs - implicit val readConfigInHyphen: NameMapper = net.ceedubs.ficus.readers.namemappers.implicits.hyphenCase - implicit val httpClient: AsyncHttpClient = asyncHttpClient() +object TransactionsGeneratorApp extends App with ScoptImplicits { + implicit val httpClient: AsyncHttpClient = asyncHttpClient() val log = LoggerFacade(LoggerFactory.getLogger("generator")) @@ -160,8 +152,7 @@ object TransactionsGeneratorApp extends App with ScoptImplicits with FicusImplic val wavesSettings = Application.loadApplicationConfig(if (externalConf.isFile) Some(externalConf) else None) val defaultConfig = - wavesSettings.config - .as[GeneratorSettings]("waves.generator") + ConfigSource.fromConfig(wavesSettings.config).at("waves.generator").loadOrThrow[GeneratorSettings] parser.parse(args, defaultConfig) match { case None => parser.failure("Failed to parse command line parameters") @@ -175,9 +166,7 @@ object TransactionsGeneratorApp extends App with ScoptImplicits with FicusImplic val time = new NTP("pool.ntp.org") val preconditions = - ConfigFactory - .load("preconditions.conf") - .as[Option[PGenSettings]]("preconditions")(optionValueReader(Preconditions.preconditionsReader)) + ConfigSource.file("preconditions.conf").at("preconditions").loadOrThrow[Option[PGenSettings]] val estimator = wavesSettings.estimator @@ -195,7 +184,6 @@ object TransactionsGeneratorApp extends App with ScoptImplicits with FicusImplic case Mode.MULTISIG => new MultisigTransactionGenerator(finalConfig.multisig, finalConfig.privateKeyAccounts, estimator) case Mode.ORACLE => new OracleTransactionGenerator(finalConfig.oracle, finalConfig.privateKeyAccounts, estimator) case Mode.SWARM => new SmartGenerator(finalConfig.swarm, finalConfig.privateKeyAccounts, estimator) - case _ => ??? } val threadPool = Executors.newFixedThreadPool(Math.max(1, finalConfig.sendTo.size)) diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/WideTransactionGenerator.scala b/node-generator/src/main/scala/com/wavesplatform/generator/WideTransactionGenerator.scala index c7361064321..456174eab54 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/WideTransactionGenerator.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/WideTransactionGenerator.scala @@ -5,6 +5,7 @@ import com.wavesplatform.account.KeyPair import com.wavesplatform.generator.WideTransactionGenerator.Settings import com.wavesplatform.generator.utils.Gen import com.wavesplatform.transaction.Transaction +import pureconfig.ConfigReader class WideTransactionGenerator(settings: Settings, accounts: Seq[KeyPair]) extends TransactionGenerator { require(accounts.nonEmpty) @@ -19,14 +20,14 @@ class WideTransactionGenerator(settings: Settings, accounts: Seq[KeyPair]) exten object WideTransactionGenerator { - case class Settings(transactions: Int, limitDestAccounts: Option[Int], minFee: Long, maxFee: Long) { + case class Settings(transactions: Int, limitDestAccounts: Option[Int], minFee: Long, maxFee: Long)derives ConfigReader { require(transactions > 0) require(limitDestAccounts.forall(_ > 0)) } object Settings { implicit val toPrintable: Show[Settings] = { x => - import x._ + import x.* s"""transactions per iteration: $transactions |number of recipients is ${limitDestAccounts.map(x => s"limited by $x").getOrElse("not limited")} |min fee: $minFee diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/Worker.scala b/node-generator/src/main/scala/com/wavesplatform/generator/Worker.scala index 3ca5e8178f6..40885d239e4 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/Worker.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/Worker.scala @@ -1,9 +1,5 @@ package com.wavesplatform.generator -import java.net.{InetSocketAddress, URL} -import java.time.LocalDateTime -import java.time.temporal.ChronoUnit - import cats.Show import cats.effect.concurrent.Ref import cats.syntax.flatMap.* @@ -16,9 +12,13 @@ import monix.eval.Task import monix.execution.Scheduler import org.asynchttpclient.AsyncHttpClient import play.api.libs.json.Json +import pureconfig.ConfigReader +import java.net.{InetSocketAddress, URL} +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit import scala.compat.java8.FutureConverters -import scala.concurrent.duration.FiniteDuration +import scala.concurrent.duration.{Duration, FiniteDuration} import scala.concurrent.{ExecutionContext, Future} class Worker( @@ -37,7 +37,7 @@ class Worker( def run(): Future[Unit] = task.runAsyncLogErr(Scheduler(ec)) - private[this] val task = + private val task = for { state <- Ref.of[Task, State](EmptyState(settings.warmUp)) initState <- settings.initialWarmUp match { @@ -52,7 +52,7 @@ class Worker( _ <- closeChannel(channel) } yield () - private[this] val nodeUTXTransactionsToSendCount: Task[Int] = Task.defer { + private val nodeUTXTransactionsToSendCount: Task[Int] = Task.defer { import org.asynchttpclient.Dsl.* val request = get(s"$nodeRestAddress/transactions/unconfirmed/size").build() Task @@ -60,7 +60,7 @@ class Worker( .map(r => math.max(settings.utxLimit - (Json.parse(r.getResponseBody) \ "size").as[Int], 0)) } - private[this] val balanceOfRichAccount: Task[Map[String, Long]] = + private val balanceOfRichAccount: Task[Map[String, Long]] = Task .defer { import org.asynchttpclient.Dsl.* @@ -74,7 +74,7 @@ class Worker( } .onErrorFallbackTo(Task.now(Map())) - private[this] val retrieveBalances: Task[Unit] = + private val retrieveBalances: Task[Unit] = if (!canContinue()) Task.unit else @@ -83,7 +83,7 @@ class Worker( _ <- if (balances.nonEmpty) logInfo(s"Balances: ${balances.mkString("(", ", ", ")")}") else Task.unit } yield () - private[this] def writeInitial(channel: Channel, state: Ref[Task, State], txs: Seq[Transaction] = initial): Task[Channel] = + private def writeInitial(channel: Channel, state: Ref[Task, State], txs: Seq[Transaction] = initial): Task[Channel] = if (!canContinue()) Task.now(channel) else @@ -96,7 +96,7 @@ class Worker( else sleep(settings.delay) *> Task.defer(writeInitial(channel, state, txs.drop(cntToSend))) } yield r - private[this] def sleepOrWaitEmptyUtx(strategy: Either[FiniteDuration, FiniteDuration]): Task[Unit] = + private def sleepOrWaitEmptyUtx(strategy: Either[FiniteDuration, FiniteDuration]): Task[Unit] = strategy match { case Left(duration) => sleep(duration) case Right(duration) => @@ -106,7 +106,7 @@ class Worker( } yield () } - private[this] def writeTailInitial(channel: Channel, state: Ref[Task, State], txs: Seq[Transaction] = tailInitial): Task[Channel] = + private def writeTailInitial(channel: Channel, state: Ref[Task, State], txs: Seq[Transaction] = tailInitial): Task[Channel] = if (!canContinue()) Task.now(channel) else @@ -119,7 +119,7 @@ class Worker( else sleep(settings.delay) *> Task.defer(writeTailInitial(validChannel, state, txs.drop(cntToSend))) } yield r - private[this] def pullAndWrite(channel: Channel, state: Ref[Task, State], cnt: Int = 0): Task[Channel] = + private def pullAndWrite(channel: Channel, state: Ref[Task, State], cnt: Int = 0): Task[Channel] = if (!canContinue()) Task.now(channel) else @@ -135,7 +135,7 @@ class Worker( r <- Task.defer(pullAndWrite(validChannel, state, (cnt + 1) % 10)) } yield r - private[this] def calcAndSaveCntToSend(stateRef: Ref[Task, State]): Task[Int] = + private def calcAndSaveCntToSend(stateRef: Ref[Task, State]): Task[Int] = for { utxCnt <- nodeUTXTransactionsToSendCount state <- stateRef.get @@ -144,7 +144,7 @@ class Worker( _ <- stateRef.set(nextState) } yield nextState.cnt - private[this] def withReconnect[A](baseTask: Task[A]): Task[A] = + private def withReconnect[A](baseTask: Task[A]): Task[A] = baseTask.onErrorHandleWith { case error if settings.autoReconnect && canContinue() => logError(s"[$node] An error during sending transactions, reconnect", error) *> @@ -155,15 +155,19 @@ class Worker( Task.raiseError(error) } - private[this] def getChannel: Task[Channel] = Task.deferFuture(networkSender.connect(node)) - private[this] def closeChannel(channel: Channel): Task[Unit] = Task(channel.close()) - private[this] def validateChannel(channel: Channel): Task[Channel] = if (channel.isOpen) Task.now(channel) else getChannel + private def getChannel: Task[Channel] = Task.deferFuture(networkSender.connect(node)) + + private def closeChannel(channel: Channel): Task[Unit] = Task(channel.close()) + + private def validateChannel(channel: Channel): Task[Channel] = if (channel.isOpen) Task.now(channel) else getChannel + + private def logError(msg: => String, err: Throwable): Task[Unit] = Task(log.error(msg, err)) + + private def logInfo(msg: => String): Task[Unit] = Task(log.info(msg)) - private[this] def logError(msg: => String, err: Throwable): Task[Unit] = Task(log.error(msg, err)) - private[this] def logInfo(msg: => String): Task[Unit] = Task(log.info(msg)) - private[this] def logTrace(msg: => String): Task[Unit] = Task(log.trace(msg)) + private def logTrace(msg: => String): Task[Unit] = Task(log.trace(msg)) - private[this] def sleep(delay: FiniteDuration): Task[Unit] = logInfo(s"Sleeping for $delay") *> Task.sleep(delay) + private def sleep(delay: FiniteDuration): Task[Unit] = logInfo(s"Sleeping for $delay") *> Task.sleep(delay) } object Worker { diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/cli/ScoptImplicits.scala b/node-generator/src/main/scala/com/wavesplatform/generator/cli/ScoptImplicits.scala index 2b47d861955..83f986aa7cf 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/cli/ScoptImplicits.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/cli/ScoptImplicits.scala @@ -11,7 +11,7 @@ trait ScoptImplicits { case x => Option(r.reads(x)) } - implicit val modeRead: Read[Mode.Value] = Read.reads(Mode withName _.toUpperCase) + implicit val modeRead: Read[Mode] = Read.reads(s => Mode.valueOf(s.toUpperCase)) implicit val finiteDurationRead: Read[FiniteDuration] = Read.durationRead.map { x => if (x.isFinite) FiniteDuration(x.length, x.unit) diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/config/FicusImplicits.scala b/node-generator/src/main/scala/com/wavesplatform/generator/config/FicusImplicits.scala index 98ee50c9176..d27625cbecb 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/config/FicusImplicits.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/config/FicusImplicits.scala @@ -1,83 +1,75 @@ package com.wavesplatform.generator.config -import scala.concurrent.duration.{Duration, FiniteDuration} - import com.google.common.base.CaseFormat -import com.typesafe.config.{Config, ConfigRenderOptions} +import com.typesafe.config.* import com.wavesplatform.generator.Worker +import com.wavesplatform.settings.* import com.wavesplatform.state.DataEntry -import com.wavesplatform.transaction.{TransactionParser, TransactionParsers, TransactionType} import com.wavesplatform.transaction.TransactionType.TransactionType -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.{CollectionReaders, ValueReader} -import play.api.libs.json._ +import com.wavesplatform.transaction.{TransactionParser, TransactionParsers, TransactionType} +import play.api.libs.json.* +import pureconfig.* +import pureconfig.error.{CannotParse, ConfigReaderFailures, ThrowableFailure} + +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.{Duration, FiniteDuration} +import scala.util.control.NonFatal trait FicusImplicits { - private[this] val byName: Map[String, TransactionParser] = TransactionParsers.all.map { - case (_, builder) => builder.getClass.getSimpleName.replaceAll("\\$$", "") -> builder + private val byName: Map[String, TransactionParser] = TransactionParsers.all.map { case (_, builder) => + builder.getClass.getSimpleName.replaceAll("\\$$", "") -> builder } private def by(name: String): Option[TransactionParser] = byName.get(name) - implicit val distributionsReader: ValueReader[Map[TransactionParser, Double]] = { - val converter = CaseFormat.LOWER_HYPHEN.converterTo(CaseFormat.UPPER_CAMEL) - def toTxType(key: String): TransactionParser = by(converter.convert(key)).get - - CollectionReaders.mapValueReader[Double].map { xs => - xs.map { - case (k, v) => - toTxType(k) -> v - } - } - } - - implicit val newDistributionsReader: ValueReader[Map[TransactionType, Double]] = { + given ConfigReader[Map[TransactionType, Double]] = { val converter = CaseFormat.LOWER_HYPHEN.converterTo(CaseFormat.UPPER_CAMEL) def toTxType(key: String): TransactionType = TransactionType.withName(converter.convert(key).replace("Transaction", "")) - CollectionReaders.mapValueReader[Double].map { xs => - xs.map { - case (k, v) => - toTxType(k) -> v + CollectionReaders.mapReader[Double].map { xs => + xs.map { case (k, v) => + toTxType(k) -> v } } } - implicit val dataEntryReader: ValueReader[DataEntry[_]] = (config: Config, path: String) => - Json.parse(config.getConfig(path).root().render(ConfigRenderOptions.concise())).as[DataEntry[_]] - - implicit val workerSettingsReader: ValueReader[Worker.Settings] = (config: Config, path: String) => { - def readWaitUtxOrDelay(path: String, default: FiniteDuration): Either[FiniteDuration, FiniteDuration] = - if (config.hasPath(path)) { - val value = config.as[String](path) - if (value == "empty-utx") Right(default) - else { - val duration: Duration = Duration(value) - Left(FiniteDuration(duration.length, duration.unit)) - } - } else Right(default) - - val utxLimit = config.as[Int](s"$path.utx-limit") - val delay = config.as[FiniteDuration](s"$path.delay") - val tailInitialDelay = readWaitUtxOrDelay(s"$path.tail-initial-delay", delay) - val initialDelay = readWaitUtxOrDelay(s"$path.initial-delay", delay) - val workingTime = config.as[FiniteDuration](s"$path.working-time") - val autoReconnect = config.as[Boolean](s"$path.auto-reconnect") - val reconnectDelay = config.as[FiniteDuration](s"$path.reconnect-delay") + given ConfigReader[DataEntry[?]] = + ConfigReader.fromFunction(v => + try Right(Json.parse(v.render(ConfigRenderOptions.concise())).as[DataEntry[?]]) + catch { + case NonFatal(e) => ConfigReader.Result.fail(ThrowableFailure(e, Some(v.origin()))) + } + ) - def readWarmUp(warmUpConfig: Config): Worker.WarmUp = { - val warmUpStart = warmUpConfig.as[Int](s"start") - val warmUpEnd = warmUpConfig.as[Option[Int]](s"end").getOrElse(utxLimit) - val warmUpStep = warmUpConfig.as[Int](s"step") - val warmUpDuration = warmUpConfig.as[Option[FiniteDuration]](s"duration") - val warmUpOnce = warmUpConfig.as[Option[Boolean]](s"once").getOrElse(true) - Worker.WarmUp(warmUpStart, warmUpEnd, warmUpStep, warmUpDuration, warmUpOnce) - } + given ConfigReader[Worker.Settings] = ConfigReader.fromCursor { v => + def readInitialDelay(obj: ConfigObjectCursor, path: String, delay: FiniteDuration): ConfigReader.Result[Either[FiniteDuration, FiniteDuration]] = + for { + delayStr <- obj.optionalWithDefault[String]("initial-delay", "empty-utx") + delay <- if (delayStr == "empty-utx") Right(Right(delay)) else obj.required[FiniteDuration]("initial-delay").map(d => Left(d)) + } yield delay - val warmUp = readWarmUp(config.getConfig(s"$path.warm-up")) - val initWarmUp = if (config.hasPath(s"$path.initial-warm-up")) Some(readWarmUp(config.getConfig(s"$path.init-warm-up"))) else None + def readWarmUp(warmUpConfig: ConfigObjectCursor, utxLimit: Int): ConfigReader.Result[Worker.WarmUp] = for { + warmUpStart <- warmUpConfig.required[Int]("start") + warmUpEnd <- warmUpConfig.optionalWithDefault[Int]("end", utxLimit) + warmUpStep <- warmUpConfig.required[Int]("step") + warmUpDuration <- warmUpConfig.required[Option[FiniteDuration]](s"duration") + warmUpOnce <- warmUpConfig.optionalWithDefault[Boolean]("once", true) + } yield Worker.WarmUp(warmUpStart, warmUpEnd, warmUpStep, warmUpDuration, warmUpOnce) - Worker.Settings(utxLimit, delay, tailInitialDelay, initialDelay, workingTime, autoReconnect, reconnectDelay, warmUp, initWarmUp) + for { + obj <- v.asObjectCursor + utxLimit <- obj.required[Int]("utx-limit") + delay <- obj.required[FiniteDuration]("delay") + workingTime <- obj.required[FiniteDuration]("working-time") + autoReconnect <- obj.required[Boolean]("auto-reconnect") + reconnectDelay <- obj.required[FiniteDuration]("reconnect-delay") + warmUpObj <- obj.atKey("warm-up").flatMap(_.asObjectCursor) + warmUp <- readWarmUp(warmUpObj, utxLimit) + initWarmUpObj <- obj.atKeyOrUndefined("init-warm-up").asObjectCursor + initWarmUp <- readWarmUp(initWarmUpObj, utxLimit).fold(_ => Right(None), v => Right(Some(v))) + initialDelay <- readInitialDelay(obj, "initial-delay", delay) + tailInitialDelay <- readInitialDelay(obj, "tail-initial-delay", delay) + } yield Worker.Settings(utxLimit, delay, tailInitialDelay, initialDelay, workingTime, autoReconnect, reconnectDelay, warmUp, initWarmUp) } } diff --git a/node-generator/src/main/scala/com/wavesplatform/generator/utils/Gen.scala b/node-generator/src/main/scala/com/wavesplatform/generator/utils/Gen.scala index d9437c4be65..45150f0ac6a 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/utils/Gen.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/utils/Gen.scala @@ -1,22 +1,24 @@ package com.wavesplatform.generator.utils -import java.util.concurrent.ThreadLocalRandom import com.wavesplatform.account.{Address, KeyPair, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.common.utils.EitherExt2.explicitGet import com.wavesplatform.crypto.Curve25519.KeyLength import com.wavesplatform.generator.utils.Implicits.* import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.estimator.ScriptEstimator import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, DataEntry, EmptyDataEntry, IntegerDataEntry, StringDataEntry} import com.wavesplatform.transaction.Asset.Waves -import com.wavesplatform.transaction.{Transaction, TxNonNegativeAmount} import com.wavesplatform.transaction.smart.script.ScriptCompiler -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer import com.wavesplatform.transaction.transfer.* +import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer +import com.wavesplatform.transaction.{Transaction, TxNonNegativeAmount} import com.wavesplatform.utils.LoggerFacade import org.slf4j.LoggerFactory +import java.util.concurrent.ThreadLocalRandom + object Gen { private def random = ThreadLocalRandom.current diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index e33e7268262..c39b334a1b3 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -6,17 +6,17 @@ import com.wavesplatform.common.state.ByteStr import pureconfig.* import pureconfig.ConvertHelpers.catchReadError import pureconfig.configurable.genericMapReader -import pureconfig.error.CannotConvert +import pureconfig.error.{CannotConvert, ConfigReaderFailures} import supertagged.TaggedType import scala.util.Try package object settings { extension (objCur: ConfigObjectCursor) { - def required[T](key: String)(using reader: ConfigReader[T])= + def required[T](key: String)(using reader: ConfigReader[T]): Either[ConfigReaderFailures, T] = objCur.atKey(key).flatMap(ConfigReader[T].from) - - def optionalWithDefault[T](key: String, default: T)(using reader: ConfigReader[T])= + + def optionalWithDefault[T](key: String, default: T)(using reader: ConfigReader[T]): Either[ConfigReaderFailures, T] = ConfigReader[Option[T]].from(objCur.atKeyOrUndefined(key)).map(_.getOrElse(default)) }