From 674ef47e0158e67c342c8a1387d00fdf0e915e76 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Thu, 31 Oct 2024 12:34:44 +0400 Subject: [PATCH 01/31] Add ConfigReader instalce for NetworkSettings --- .../settings/NetworkSettings.scala | 69 +++++++++++++++++++ project/Dependencies.scala | 1 + 2 files changed, 70 insertions(+) diff --git a/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala b/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala index a7fd766a0d..4910896309 100644 --- a/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala @@ -6,6 +6,8 @@ import com.wavesplatform.utils.* import net.ceedubs.ficus.Ficus.* import net.ceedubs.ficus.readers.ArbitraryTypeReader.* import net.ceedubs.ficus.readers.ValueReader +import pureconfig.* +import pureconfig.generic.auto.* import java.io.File import java.net.{InetSocketAddress, URI} @@ -43,6 +45,73 @@ case class NetworkSettings( object NetworkSettings { private val MaxNodeNameBytesLength = 127 + implicit val configReader: ConfigReader[(NetworkSettings)] = ConfigReader.fromCursor[NetworkSettings] { cur => + for { + objCur <- cur.asObjectCursor + + file <- objCur.atKey("file").flatMap(ConfigReader[Option[File]].from) + bindAddress <- objCur.atKey("bind-address").flatMap(ConfigReader[Option[String]].from) + port <- objCur.atKey("port").flatMap(ConfigReader[Option[Int]].from) + bindAddress1 = for { + addr <- bindAddress + p <- port + } yield new InetSocketAddress(addr, p) + + declaredAddress <- objCur + .atKey("declared-address") + .flatMap(ConfigReader[Option[String]].from) + .map(_.map { address => + val uri = new URI(s"my://$address") + new InetSocketAddress(uri.getHost, uri.getPort) + }) + + nonce <- objCur.atKey("nonce").flatMap(ConfigReader[Option[Long]].from).map(_.getOrElse(randomNonce)) + nodeName <- objCur.atKey("node-name").flatMap(ConfigReader[Option[String]].from).map(_.getOrElse(s"Node-$nonce")) + knownPeers <- objCur.atKey("known-peers").flatMap(ConfigReader[Seq[String]].from) + peersDataResidenceTime <- objCur.atKey("peers-data-residence-time").flatMap(ConfigReader[FiniteDuration].from) + blackListResidenceTime <- objCur.atKey("black-list-residence-time").flatMap(ConfigReader[FiniteDuration].from) + breakIdleConnectionsTimeout <- objCur.atKey("break-idle-connections-timeout").flatMap(ConfigReader[FiniteDuration].from) + maxInboundConnections <- objCur.atKey("max-inbound-connections").flatMap(ConfigReader[Int].from) + maxOutboundConnections <- objCur.atKey("max-outbound-connections").flatMap(ConfigReader[Int].from) + maxConnectionsPerHost <- objCur.atKey("max-single-host-connections").flatMap(ConfigReader[Int].from) + minConnections <- objCur.atKey("min-connections").flatMap(ConfigReader[Option[Int]].from) + connectionTimeout <- objCur.atKey("connection-timeout").flatMap(ConfigReader[FiniteDuration].from) + maxUnverifiedPeers <- objCur.atKey("max-unverified-peers").flatMap(ConfigReader[Int].from) + enablePeersExchange <- objCur.atKey("enable-peers-exchange").flatMap(ConfigReader[Boolean].from) + enableBlacklisting <- objCur.atKey("enable-blacklisting").flatMap(ConfigReader[Boolean].from) + peersBroadcastInterval <- objCur.atKey("peers-broadcast-interval").flatMap(ConfigReader[FiniteDuration].from) + handshakeTimeout <- objCur.atKey("handshake-timeout").flatMap(ConfigReader[FiniteDuration].from) + suspensionResidenceTime <- objCur.atKey("suspension-residence-time").flatMap(ConfigReader[FiniteDuration].from) + receivedTxsCacheTimeout <- objCur.atKey("received-txs-cache-timeout").flatMap(ConfigReader[FiniteDuration].from) + uPnPSettings <- objCur.atKey("upnp").flatMap(ConfigReader[UPnPSettings].from) + trafficLogger <- objCur.atKey("traffic-logger").flatMap(ConfigReader[TrafficLogger.Settings].from) + } yield NetworkSettings( + file = file, + bindAddress = bindAddress1, + declaredAddress = declaredAddress, + nodeName = nodeName, + nonce = nonce, + knownPeers = knownPeers, + peersDataResidenceTime = peersDataResidenceTime, + blackListResidenceTime = blackListResidenceTime, + breakIdleConnectionsTimeout = breakIdleConnectionsTimeout, + maxInboundConnections = maxInboundConnections, + maxOutboundConnections = maxOutboundConnections, + maxConnectionsPerHost = maxConnectionsPerHost, + minConnections = minConnections, + connectionTimeout = connectionTimeout, + maxUnverifiedPeers = maxUnverifiedPeers, + enablePeersExchange = enablePeersExchange, + enableBlacklisting = enableBlacklisting, + peersBroadcastInterval = peersBroadcastInterval, + handshakeTimeout = handshakeTimeout, + suspensionResidenceTime = suspensionResidenceTime, + receivedTxsCacheTimeout = receivedTxsCacheTimeout, + uPnPSettings = uPnPSettings, + trafficLogger = trafficLogger + ) + } + implicit val valueReader: ValueReader[NetworkSettings] = (cfg: Config, path: String) => fromConfig(cfg.getConfig(path)) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ad232f5a97..c73718a67c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -109,6 +109,7 @@ object Dependencies { "commons-net" % "commons-net" % "3.11.1", "commons-io" % "commons-io" % "2.17.0", "com.iheart" %% "ficus" % "1.5.2", + "com.github.pureconfig" %% "pureconfig" % "0.17.7", "net.logstash.logback" % "logstash-logback-encoder" % "8.0" % Runtime, kamonCore, kamonModule("system-metrics"), From 08bc67ddb8291d403e4f753b02ed0db23502b15e Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 5 Nov 2024 09:37:50 +0400 Subject: [PATCH 02/31] Refactor some settings to pureconfig without affecting the client code --- .../settings/BlockchainSettings.scala | 30 ++- .../settings/CustomValueReaders.scala | 8 - .../wavesplatform/settings/DBSettings.scala | 22 ++- .../settings/NetworkSettings.scala | 184 ++++++------------ .../settings/RocksDBSettings.scala | 18 ++ .../settings/WavesSettings.scala | 39 ++-- .../BlacklistParallelSpecification.scala | 4 +- .../network/BlacklistSpecification.scala | 4 +- .../peer/PeerDatabaseImplSpecification.scala | 8 +- .../BlockchainSettingsSpecification.scala | 2 +- .../NetworkSettingsSpecification.scala | 79 ++++---- 11 files changed, 188 insertions(+), 210 deletions(-) delete mode 100644 node/src/main/scala/com/wavesplatform/settings/CustomValueReaders.scala diff --git a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala index d2c33fa514..cdb1f6d860 100644 --- a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala @@ -5,8 +5,11 @@ import cats.syntax.traverse.* import com.typesafe.config.Config import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.* +import pureconfig.generic.auto.* +import pureconfig.configurable.* +import pureconfig.ConvertHelpers.* +import pureconfig.error.CannotConvert import net.ceedubs.ficus.readers.ValueReader import scala.concurrent.duration.* @@ -248,11 +251,16 @@ object BlockchainSettings { implicit val valueReader: ValueReader[BlockchainSettings] = (cfg: Config, path: String) => fromConfig(cfg.getConfig(path)) - // @deprecated("Use config.as[BlockchainSettings]", "0.17.0") - def fromRootConfig(config: Config): BlockchainSettings = config.as[BlockchainSettings]("waves.blockchain") + def fromRootConfig(config: Config): BlockchainSettings = fromConfig(config.getConfig("waves.blockchain")) - private[this] def fromConfig(config: Config): BlockchainSettings = { - val blockchainType = config.as[String]("type").toUpperCase + def fromConfig(config: Config): BlockchainSettings = { + implicit val intMapReader: ConfigReader[Map[Short, Int]] = genericMapReader[Short, Int](catchReadError(_.toShort)) + + // Note: not sure if all ByteStr values are base58 encoded + implicit val byteStrReader: ConfigReader[ByteStr] = + ConfigReader.fromString[ByteStr](str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) + + val blockchainType = config.getString("type").toUpperCase val (addressSchemeCharacter, functionalitySettings, genesisSettings, rewardsSettings) = blockchainType match { case BlockchainType.STAGENET => ('S', FunctionalitySettings.STAGENET, GenesisSettings.STAGENET, RewardsSettings.STAGENET) @@ -261,10 +269,12 @@ object BlockchainSettings { case BlockchainType.MAINNET => ('W', FunctionalitySettings.MAINNET, GenesisSettings.MAINNET, RewardsSettings.MAINNET) case _ => // Custom - val networkId = config.as[String](s"custom.address-scheme-character").charAt(0) - val functionality = config.as[FunctionalitySettings](s"custom.functionality") - val genesis = config.as[GenesisSettings](s"custom.genesis") - val rewards = config.as[RewardsSettings](s"custom.rewards") + // Note: Mind the imperative approach to reading the config here. Be careful when refactoring. + val networkId = config.getString(s"custom.address-scheme-character").charAt(0) + val configSource = ConfigSource.fromConfig(config) + val functionality: FunctionalitySettings = configSource.at("custom.functionality").loadOrThrow[FunctionalitySettings] + val genesis = configSource.at("custom.genesis").loadOrThrow[GenesisSettings] + val rewards = configSource.at("custom.rewards").loadOrThrow[RewardsSettings] require(functionality.minBlockTime <= genesis.averageBlockDelay, "minBlockTime should be <= averageBlockDelay") (networkId, functionality, genesis, rewards) } diff --git a/node/src/main/scala/com/wavesplatform/settings/CustomValueReaders.scala b/node/src/main/scala/com/wavesplatform/settings/CustomValueReaders.scala deleted file mode 100644 index 19987e5d38..0000000000 --- a/node/src/main/scala/com/wavesplatform/settings/CustomValueReaders.scala +++ /dev/null @@ -1,8 +0,0 @@ -package com.wavesplatform.settings - -import net.ceedubs.ficus.readers.ValueReader - -trait CustomValueReaders { - implicit val networkSettingsValueReader: ValueReader[NetworkSettings] = NetworkSettings.valueReader - implicit val blockchainSettingsValueReader: ValueReader[BlockchainSettings] = BlockchainSettings.valueReader -} diff --git a/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala b/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala index 906e085557..2e2881b3bc 100644 --- a/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala @@ -1,5 +1,7 @@ package com.wavesplatform.settings +import com.typesafe.config.Config + case class DBSettings( directory: String, storeTransactionsByAddress: Boolean, @@ -9,5 +11,23 @@ case class DBSettings( maxCacheSize: Int, maxRollbackDepth: Int, cleanupInterval: Option[Int] = None, - rocksdb: RocksDBSettings, + rocksdb: RocksDBSettings ) + +object DBSettings { + def fromConfig(config: Config): DBSettings = DBSettings( + directory = config.getString("directory"), + storeTransactionsByAddress = config.getBoolean("store-transactions-by-address"), + storeLeaseStatesByAddress = config.getBoolean("store-lease-states-by-address"), + storeInvokeScriptResults = config.getBoolean("store-invoke-script-results"), + storeStateHashes = config.getBoolean("store-state-hashes"), + maxCacheSize = config.getInt("max-cache-size"), + maxRollbackDepth = config.getInt("max-rollback-depth"), + cleanupInterval = if (config.hasPath("cleanup-interval")) { + Option(config.getInt("cleanup-interval")) + } else { + None + }, + rocksdb = RocksDBSettings.fromConfig(config.getConfig("rocksdb")) + ) +} diff --git a/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala b/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala index 4910896309..6c46126cac 100644 --- a/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala @@ -1,11 +1,7 @@ package com.wavesplatform.settings -import com.typesafe.config.Config import com.wavesplatform.network.TrafficLogger import com.wavesplatform.utils.* -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* -import net.ceedubs.ficus.readers.ValueReader import pureconfig.* import pureconfig.generic.auto.* @@ -16,19 +12,20 @@ import scala.util.Random case class UPnPSettings(enable: Boolean, gatewayTimeout: FiniteDuration, discoverTimeout: FiniteDuration) -case class NetworkSettings( +case class NetworkSettingsDto( file: Option[File], - bindAddress: Option[InetSocketAddress], - declaredAddress: Option[InetSocketAddress], - nodeName: String, - nonce: Long, + bindAddress: Option[String], + port: Option[Int], + declaredAddress: Option[String], + nodeName: Option[String], + nonce: Option[Long], knownPeers: Seq[String], peersDataResidenceTime: FiniteDuration, blackListResidenceTime: FiniteDuration, breakIdleConnectionsTimeout: FiniteDuration, maxInboundConnections: Int, maxOutboundConnections: Int, - maxConnectionsPerHost: Int, + maxSingleHostConnections: Int, minConnections: Option[Int], connectionTimeout: FiniteDuration, maxUnverifiedPeers: Int, @@ -38,66 +35,41 @@ case class NetworkSettings( handshakeTimeout: FiniteDuration, suspensionResidenceTime: FiniteDuration, receivedTxsCacheTimeout: FiniteDuration, - uPnPSettings: UPnPSettings, + upnp: UPnPSettings, trafficLogger: TrafficLogger.Settings -) - -object NetworkSettings { - private val MaxNodeNameBytesLength = 127 - - implicit val configReader: ConfigReader[(NetworkSettings)] = ConfigReader.fromCursor[NetworkSettings] { cur => - for { - objCur <- cur.asObjectCursor - - file <- objCur.atKey("file").flatMap(ConfigReader[Option[File]].from) - bindAddress <- objCur.atKey("bind-address").flatMap(ConfigReader[Option[String]].from) - port <- objCur.atKey("port").flatMap(ConfigReader[Option[Int]].from) - bindAddress1 = for { - addr <- bindAddress - p <- port - } yield new InetSocketAddress(addr, p) +) { + def toNetworkSettings: NetworkSettings = { + def randomNonce: Long = { + val base = 1000 + (Random.nextInt(base) + base) * Random.nextInt(base) + Random.nextInt(base) + } + val MaxNodeNameBytesLength = 127 - declaredAddress <- objCur - .atKey("declared-address") - .flatMap(ConfigReader[Option[String]].from) - .map(_.map { address => - val uri = new URI(s"my://$address") - new InetSocketAddress(uri.getHost, uri.getPort) - }) + val declaredAddress1 = declaredAddress.map { address => + val uri = new URI(s"my://$address") + new InetSocketAddress(uri.getHost, uri.getPort) + } + val nonce1 = nonce.getOrElse(randomNonce) + val nodeName1 = nodeName.getOrElse(s"Node-$nonce1") + require(nodeName1.utf8Bytes.length <= MaxNodeNameBytesLength, s"Node name should have length less than $MaxNodeNameBytesLength bytes") + val bindAddress1 = for { + addr <- bindAddress + p <- port + } yield new InetSocketAddress(addr, p) - nonce <- objCur.atKey("nonce").flatMap(ConfigReader[Option[Long]].from).map(_.getOrElse(randomNonce)) - nodeName <- objCur.atKey("node-name").flatMap(ConfigReader[Option[String]].from).map(_.getOrElse(s"Node-$nonce")) - knownPeers <- objCur.atKey("known-peers").flatMap(ConfigReader[Seq[String]].from) - peersDataResidenceTime <- objCur.atKey("peers-data-residence-time").flatMap(ConfigReader[FiniteDuration].from) - blackListResidenceTime <- objCur.atKey("black-list-residence-time").flatMap(ConfigReader[FiniteDuration].from) - breakIdleConnectionsTimeout <- objCur.atKey("break-idle-connections-timeout").flatMap(ConfigReader[FiniteDuration].from) - maxInboundConnections <- objCur.atKey("max-inbound-connections").flatMap(ConfigReader[Int].from) - maxOutboundConnections <- objCur.atKey("max-outbound-connections").flatMap(ConfigReader[Int].from) - maxConnectionsPerHost <- objCur.atKey("max-single-host-connections").flatMap(ConfigReader[Int].from) - minConnections <- objCur.atKey("min-connections").flatMap(ConfigReader[Option[Int]].from) - connectionTimeout <- objCur.atKey("connection-timeout").flatMap(ConfigReader[FiniteDuration].from) - maxUnverifiedPeers <- objCur.atKey("max-unverified-peers").flatMap(ConfigReader[Int].from) - enablePeersExchange <- objCur.atKey("enable-peers-exchange").flatMap(ConfigReader[Boolean].from) - enableBlacklisting <- objCur.atKey("enable-blacklisting").flatMap(ConfigReader[Boolean].from) - peersBroadcastInterval <- objCur.atKey("peers-broadcast-interval").flatMap(ConfigReader[FiniteDuration].from) - handshakeTimeout <- objCur.atKey("handshake-timeout").flatMap(ConfigReader[FiniteDuration].from) - suspensionResidenceTime <- objCur.atKey("suspension-residence-time").flatMap(ConfigReader[FiniteDuration].from) - receivedTxsCacheTimeout <- objCur.atKey("received-txs-cache-timeout").flatMap(ConfigReader[FiniteDuration].from) - uPnPSettings <- objCur.atKey("upnp").flatMap(ConfigReader[UPnPSettings].from) - trafficLogger <- objCur.atKey("traffic-logger").flatMap(ConfigReader[TrafficLogger.Settings].from) - } yield NetworkSettings( + NetworkSettings( file = file, bindAddress = bindAddress1, - declaredAddress = declaredAddress, - nodeName = nodeName, - nonce = nonce, + declaredAddress = declaredAddress1, + nodeName = nodeName1, + nonce = nonce1, knownPeers = knownPeers, peersDataResidenceTime = peersDataResidenceTime, blackListResidenceTime = blackListResidenceTime, breakIdleConnectionsTimeout = breakIdleConnectionsTimeout, maxInboundConnections = maxInboundConnections, maxOutboundConnections = maxOutboundConnections, - maxConnectionsPerHost = maxConnectionsPerHost, + maxConnectionsPerHost = maxSingleHostConnections, minConnections = minConnections, connectionTimeout = connectionTimeout, maxUnverifiedPeers = maxUnverifiedPeers, @@ -107,74 +79,38 @@ object NetworkSettings { handshakeTimeout = handshakeTimeout, suspensionResidenceTime = suspensionResidenceTime, receivedTxsCacheTimeout = receivedTxsCacheTimeout, - uPnPSettings = uPnPSettings, + uPnPSettings = upnp, trafficLogger = trafficLogger ) } +} - implicit val valueReader: ValueReader[NetworkSettings] = - (cfg: Config, path: String) => fromConfig(cfg.getConfig(path)) - - private[this] def fromConfig(config: Config): NetworkSettings = { - val file = config.getAs[File]("file") - val bindAddress = config.getAs[String]("bind-address").map(addr => new InetSocketAddress(addr, config.as[Int]("port"))) - val nonce = config.getOrElse("nonce", randomNonce) - val nodeName = config.getOrElse("node-name", s"Node-$nonce") - require(nodeName.utf8Bytes.length <= MaxNodeNameBytesLength, s"Node name should have length less than $MaxNodeNameBytesLength bytes") - val declaredAddress = config.getAs[String]("declared-address").map { address => - val uri = new URI(s"my://$address") - new InetSocketAddress(uri.getHost, uri.getPort) - } - - val knownPeers = config.as[Seq[String]]("known-peers") - val peersDataResidenceTime = config.as[FiniteDuration]("peers-data-residence-time") - val blackListResidenceTime = config.as[FiniteDuration]("black-list-residence-time") - val breakIdleConnectionsTimeout = config.as[FiniteDuration]("break-idle-connections-timeout") - val maxInboundConnections = config.as[Int]("max-inbound-connections") - val maxOutboundConnections = config.as[Int]("max-outbound-connections") - val maxConnectionsFromSingleHost = config.as[Int]("max-single-host-connections") - val minConnections = config.getAs[Int]("min-connections") - val connectionTimeout = config.as[FiniteDuration]("connection-timeout") - val maxUnverifiedPeers = config.as[Int]("max-unverified-peers") - val enablePeersExchange = config.as[Boolean]("enable-peers-exchange") - val enableBlacklisting = config.as[Boolean]("enable-blacklisting") - val peersBroadcastInterval = config.as[FiniteDuration]("peers-broadcast-interval") - val handshakeTimeout = config.as[FiniteDuration]("handshake-timeout") - val suspensionResidenceTime = config.as[FiniteDuration]("suspension-residence-time") - val receivedTxsCacheTimeout = config.as[FiniteDuration]("received-txs-cache-timeout") - val uPnPSettings = config.as[UPnPSettings]("upnp") - val trafficLogger = config.as[TrafficLogger.Settings]("traffic-logger") - - NetworkSettings( - file, - bindAddress, - declaredAddress, - nodeName, - nonce, - knownPeers, - peersDataResidenceTime, - blackListResidenceTime, - breakIdleConnectionsTimeout, - maxInboundConnections, - maxOutboundConnections, - maxConnectionsFromSingleHost, - minConnections, - connectionTimeout, - maxUnverifiedPeers, - enablePeersExchange, - enableBlacklisting, - peersBroadcastInterval, - handshakeTimeout, - suspensionResidenceTime, - receivedTxsCacheTimeout, - uPnPSettings, - trafficLogger - ) - } - - private def randomNonce: Long = { - val base = 1000 +case class NetworkSettings( + file: Option[File], + bindAddress: Option[InetSocketAddress], + declaredAddress: Option[InetSocketAddress], + nodeName: String, + nonce: Long, + knownPeers: Seq[String], + peersDataResidenceTime: FiniteDuration, + blackListResidenceTime: FiniteDuration, + breakIdleConnectionsTimeout: FiniteDuration, + maxInboundConnections: Int, + maxOutboundConnections: Int, + maxConnectionsPerHost: Int, + minConnections: Option[Int], + connectionTimeout: FiniteDuration, + maxUnverifiedPeers: Int, + enablePeersExchange: Boolean, + enableBlacklisting: Boolean, + peersBroadcastInterval: FiniteDuration, + handshakeTimeout: FiniteDuration, + suspensionResidenceTime: FiniteDuration, + receivedTxsCacheTimeout: FiniteDuration, + uPnPSettings: UPnPSettings, + trafficLogger: TrafficLogger.Settings +) - (Random.nextInt(base) + base) * Random.nextInt(base) + Random.nextInt(base) - } +object NetworkSettings { + implicit val configReader: ConfigReader[NetworkSettings] = ConfigReader[NetworkSettingsDto].map(_.toNetworkSettings) } diff --git a/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala b/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala index 3692e164f2..5b47fce39d 100644 --- a/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala @@ -1,5 +1,7 @@ package com.wavesplatform.settings +import com.typesafe.config.Config + case class RocksDBSettings( mainCacheSize: SizeInBytes, txCacheSize: SizeInBytes, @@ -12,3 +14,19 @@ case class RocksDBSettings( parallelism: Int, maxOpenFiles: Int ) + +object RocksDBSettings { + def fromConfig(config: Config): RocksDBSettings = + RocksDBSettings( + mainCacheSize = SizeInBytes(config.getBytes("main-cache-size").toLong), + txCacheSize = SizeInBytes(config.getBytes("tx-cache-size").toLong), + txMetaCacheSize = SizeInBytes(config.getBytes("tx-meta-cache-size").toLong), + txSnapshotCacheSize = SizeInBytes(config.getBytes("tx-snapshot-cache-size").toLong), + apiCacheSize = SizeInBytes(config.getBytes("api-cache-size").toLong), + writeBufferSize = SizeInBytes(config.getBytes("write-buffer-size").toLong), + enableStatistics = config.getBoolean("enable-statistics"), + allowMmapReads = config.getBoolean("allow-mmap-reads"), + parallelism = config.getInt("parallelism"), + maxOpenFiles = config.getInt("max-open-files") + ) +} diff --git a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala index ff2cbdb55e..0ecd128789 100644 --- a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala @@ -6,6 +6,8 @@ import net.ceedubs.ficus.Ficus.* import net.ceedubs.ficus.readers.ArbitraryTypeReader.* import scala.concurrent.duration.FiniteDuration +import pureconfig.* +import pureconfig.generic.auto.* case class WavesSettings( directory: String, @@ -28,27 +30,28 @@ case class WavesSettings( config: Config ) -object WavesSettings extends CustomValueReaders { +object WavesSettings { def fromRootConfig(rootConfig: Config): WavesSettings = { - val waves = rootConfig.getConfig("waves") + val waves = rootConfig.getConfig("waves") + val wavesConfigSource = ConfigSource.fromConfig(waves) - val directory = waves.as[String]("directory") - val enableLightMode = waves.as[Boolean]("enable-light-mode") - val ntpServer = waves.as[String]("ntp-server") - val maxTxErrorLogSize = waves.as[Int]("max-tx-error-log-size") - val dbSettings = waves.as[DBSettings]("db") - val extensions = waves.as[Seq[String]]("extensions") - val extensionsShutdownTimeout = waves.as[FiniteDuration]("extensions-shutdown-timeout") - val networkSettings = waves.as[NetworkSettings]("network") - val walletSettings = waves.as[WalletSettings]("wallet") - val blockchainSettings = waves.as[BlockchainSettings]("blockchain") + val directory = wavesConfigSource.at("directory").loadOrThrow[String] + val enableLightMode = wavesConfigSource.at("enable-light-mode").loadOrThrow[Boolean] + val ntpServer = wavesConfigSource.at("ntp-server").loadOrThrow[String] + val maxTxErrorLogSize = wavesConfigSource.at("max-tx-error-log-size").loadOrThrow[Int] + val dbSettings = DBSettings.fromConfig(waves.getConfig("db")) + val extensions = wavesConfigSource.at("extensions").loadOrThrow[Seq[String]] + val extensionsShutdownTimeout = wavesConfigSource.at("extensions-shutdown-timeout").loadOrThrow[FiniteDuration] + val networkSettings = wavesConfigSource.at("network").loadOrThrow[NetworkSettings] + val walletSettings = wavesConfigSource.at("wallet").loadOrThrow[WalletSettings] + val blockchainSettings = BlockchainSettings.fromConfig(waves.getConfig("blockchain")) val minerSettings = waves.as[MinerSettings]("miner") - val restAPISettings = waves.as[RestAPISettings]("rest-api") - val synchronizationSettings = waves.as[SynchronizationSettings]("synchronization") - val utxSettings = waves.as[UtxSettings]("utx") - val featuresSettings = waves.as[FeaturesSettings]("features") - val rewardsSettings = waves.as[RewardsVotingSettings]("rewards") - val metrics = rootConfig.as[Metrics.Settings]("metrics") // TODO: Move to waves section + val restAPISettings = wavesConfigSource.at("rest-api").loadOrThrow[RestAPISettings] + val synchronizationSettings = wavesConfigSource.at("synchronization").loadOrThrow[SynchronizationSettings] + val utxSettings = wavesConfigSource.at("utx").loadOrThrow[UtxSettings] + val featuresSettings = wavesConfigSource.at("features").loadOrThrow[FeaturesSettings] + val rewardsSettings = wavesConfigSource.at("rewards").loadOrThrow[RewardsVotingSettings] + val metrics = ConfigSource.fromConfig(rootConfig).at("metrics").loadOrThrow[Metrics.Settings] // TODO: Move to waves section WavesSettings( directory, diff --git a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala index 4c86f05d1d..208c95c4fc 100644 --- a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala @@ -3,8 +3,8 @@ package com.wavesplatform.network import com.typesafe.config.ConfigFactory import com.wavesplatform.settings.{NetworkSettings, loadConfig} import com.wavesplatform.test.FeatureSpec -import net.ceedubs.ficus.Ficus.* import org.scalatest.{GivenWhenThen, ParallelTestExecution} +import pureconfig.ConfigSource import java.net.{InetAddress, InetSocketAddress} @@ -16,7 +16,7 @@ class BlacklistParallelSpecification extends FeatureSpec with GivenWhenThen with | black-list-residence-time: 1s |}""".stripMargin)) - private val networkSettings = config.as[NetworkSettings]("waves.network") + private val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] info("As a Peer") info("I want to blacklist other peers for certain time") diff --git a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala index 1aaf9696a9..eb836d6106 100644 --- a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala @@ -4,8 +4,8 @@ import com.google.common.base.Ticker import com.typesafe.config.ConfigFactory import com.wavesplatform.settings.NetworkSettings import com.wavesplatform.test.FeatureSpec -import net.ceedubs.ficus.Ficus.* import org.scalatest.GivenWhenThen +import pureconfig.ConfigSource import java.net.{InetAddress, InetSocketAddress} @@ -19,7 +19,7 @@ class BlacklistSpecification extends FeatureSpec with GivenWhenThen { .withFallback(ConfigFactory.load()) .resolve() - private val networkSettings = config.as[NetworkSettings]("waves.network") + private val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] private var timestamp = 0L info("As a Peer") diff --git a/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala index d6cbe832e2..f29b5055b6 100644 --- a/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala @@ -5,7 +5,7 @@ import com.typesafe.config.ConfigFactory import com.wavesplatform.network.{PeerDatabase, PeerDatabaseImpl} import com.wavesplatform.settings.NetworkSettings import com.wavesplatform.test.FreeSpec -import net.ceedubs.ficus.Ficus.* +import pureconfig.ConfigSource import java.net.InetSocketAddress import scala.concurrent.duration.* @@ -25,7 +25,7 @@ class PeerDatabaseImplSpecification extends FreeSpec { |}""".stripMargin) .withFallback(ConfigFactory.load()) .resolve() - private val settings1 = config1.as[NetworkSettings]("waves.network") + private val settings1 = ConfigSource.fromConfig(config1).at("waves.network").loadOrThrow[NetworkSettings] private val config2 = ConfigFactory .parseString("""waves.network { @@ -35,7 +35,7 @@ class PeerDatabaseImplSpecification extends FreeSpec { |}""".stripMargin) .withFallback(ConfigFactory.load()) .resolve() - private val settings2 = config2.as[NetworkSettings]("waves.network") + private val settings2 = ConfigSource.fromConfig(config2).at("waves.network").loadOrThrow[NetworkSettings] private var ts = 0L private def sleepLong(): Unit = { ts += 2200.millis.toNanos } @@ -136,7 +136,7 @@ class PeerDatabaseImplSpecification extends FreeSpec { |}""".stripMargin) .withFallback(ConfigFactory.load()) .resolve() - val settings = config.as[NetworkSettings]("waves.network") + val settings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] val database = new PeerDatabaseImpl(settings) database.blacklist(address1.getAddress, "I don't like it") diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/BlockchainSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/BlockchainSettingsSpecification.scala index 2aa3203082..c00415bdab 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/BlockchainSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/BlockchainSettingsSpecification.scala @@ -4,7 +4,7 @@ import com.typesafe.config.ConfigFactory import com.wavesplatform.common.state.ByteStr import com.wavesplatform.test.FlatSpec -import scala.concurrent.duration._ +import scala.concurrent.duration.* class BlockchainSettingsSpecification extends FlatSpec { "BlockchainSettings" should "read custom values" in { diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala index b2a7acda6c..8232d3ac3a 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala @@ -1,46 +1,45 @@ package com.wavesplatform.settings import java.net.InetSocketAddress - import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec -import net.ceedubs.ficus.Ficus._ - -import scala.concurrent.duration._ +import pureconfig.ConfigSource +import pureconfig.error.ConfigReaderException +import scala.concurrent.duration.* class NetworkSettingsSpecification extends FlatSpec { "NetworkSpecification" should "read values from config" in { - val config = loadConfig(ConfigFactory.parseString("""waves.network { - | bind-address: "127.0.0.1" - | port: 6868 - | node-name: "default-node-name" - | declared-address: "127.0.0.1:6868" - | nonce: 0 - | known-peers = ["8.8.8.8:6868", "4.4.8.8:6868"] - | local-only: no - | peers-data-residence-time: 1d - | black-list-residence-time: 10m - | break-idle-connections-timeout: 53s - | max-inbound-connections: 30 - | max-outbound-connections = 20 - | max-single-host-connections = 2 - | connection-timeout: 30s - | max-unverified-peers: 0 - | peers-broadcast-interval: 2m - | black-list-threshold: 50 - | unrequested-packets-threshold: 100 - | upnp { - | enable: yes - | gateway-timeout: 10s - | discover-timeout: 10s - | } - | traffic-logger { - | ignore-tx-messages = [28] - | ignore-rx-messages = [23] - | } - |}""".stripMargin)) - val networkSettings = config.as[NetworkSettings]("waves.network") + val config = loadConfig(ConfigFactory.parseString("""waves.network { + | bind-address: "127.0.0.1" + | port: 6868 + | node-name: "default-node-name" + | declared-address: "127.0.0.1:6868" + | nonce: 0 + | known-peers = ["8.8.8.8:6868", "4.4.8.8:6868"] + | local-only: no + | peers-data-residence-time: 1d + | black-list-residence-time: 10m + | break-idle-connections-timeout: 53s + | max-inbound-connections: 30 + | max-outbound-connections = 20 + | max-single-host-connections = 2 + | connection-timeout: 30s + | max-unverified-peers: 0 + | peers-broadcast-interval: 2m + | black-list-threshold: 50 + | unrequested-packets-threshold: 100 + | upnp { + | enable: yes + | gateway-timeout: 10s + | discover-timeout: 10s + | } + | traffic-logger { + | ignore-tx-messages = [28] + | ignore-rx-messages = [23] + | } + |}""".stripMargin)) + val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] networkSettings.bindAddress should be(Some(new InetSocketAddress("127.0.0.1", 6868))) networkSettings.nodeName should be("default-node-name") @@ -65,14 +64,14 @@ class NetworkSettingsSpecification extends FlatSpec { it should "generate random nonce" in { val config = loadConfig(ConfigFactory.empty()) - val networkSettings = config.as[NetworkSettings]("waves.network") + val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] networkSettings.nonce should not be 0 } it should "build node name using nonce" in { val config = loadConfig(ConfigFactory.parseString("waves.network.nonce = 12345")) - val networkSettings = config.as[NetworkSettings]("waves.network") + val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] networkSettings.nonce should be(12345) networkSettings.nodeName should be("Node-12345") @@ -80,20 +79,20 @@ class NetworkSettingsSpecification extends FlatSpec { it should "build node name using random nonce" in { val config = loadConfig(ConfigFactory.empty()) - val networkSettings = config.as[NetworkSettings]("waves.network") + val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] networkSettings.nonce should not be 0 networkSettings.nodeName should be(s"Node-${networkSettings.nonce}") } - it should "fail with IllegalArgumentException on too long node name" in { + it should "fail with ConfigReaderException on too long node name" in { val config = loadConfig( ConfigFactory.parseString( "waves.network.node-name = очень-длинное-название-в-многобайтной-кодировке-отличной-от-однобайтной-кодировки-американского-института-стандартов" ) ) - intercept[IllegalArgumentException] { - config.as[NetworkSettings]("waves.network") + intercept[ConfigReaderException[NetworkSettings]] { + ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] } } } From 05fa1a8194ceb37d3318b7035e553918dffc6467 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 5 Nov 2024 10:47:39 +0400 Subject: [PATCH 03/31] Refactor MinerSettings to pureconfig --- .../com/wavesplatform/account/PrivateKey.scala | 17 +++++++++++++++-- .../settings/BlockchainSettings.scala | 4 ++-- .../wavesplatform/settings/WavesSettings.scala | 5 +---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala b/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala index 5634d33cbc..a803cd515c 100644 --- a/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala +++ b/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala @@ -3,8 +3,10 @@ package com.wavesplatform.account import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto.KeyLength import play.api.libs.json.{Format, Writes} -import supertagged._ -import supertagged.postfix._ +import pureconfig.ConfigReader +import pureconfig.error.CannotConvert +import supertagged.* +import supertagged.postfix.* object PrivateKey extends TaggedType[ByteStr] { def apply(privateKey: ByteStr): PrivateKey = { @@ -22,4 +24,15 @@ object PrivateKey extends TaggedType[ByteStr] { com.wavesplatform.utils.byteStrFormat.map(this.apply), Writes(pk => com.wavesplatform.utils.byteStrFormat.writes(pk)) ) + + implicit val configReader: ConfigReader[PrivateKey] = + ConfigReader.fromString(str => + ByteStr + .decodeBase58(str) + .toEither + .map(PrivateKey(_)) + .left + .map(e => CannotConvert(str, "ByteStr", e.getMessage)) + ) + } diff --git a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala index cdb1f6d860..8597a9e5da 100644 --- a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala @@ -254,11 +254,11 @@ object BlockchainSettings { def fromRootConfig(config: Config): BlockchainSettings = fromConfig(config.getConfig("waves.blockchain")) def fromConfig(config: Config): BlockchainSettings = { - implicit val intMapReader: ConfigReader[Map[Short, Int]] = genericMapReader[Short, Int](catchReadError(_.toShort)) + implicit val intMapReader: ConfigReader[Map[Short, Int]] = genericMapReader(catchReadError(_.toShort)) // Note: not sure if all ByteStr values are base58 encoded implicit val byteStrReader: ConfigReader[ByteStr] = - ConfigReader.fromString[ByteStr](str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) + ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) val blockchainType = config.getString("type").toUpperCase val (addressSchemeCharacter, functionalitySettings, genesisSettings, rewardsSettings) = blockchainType match { diff --git a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala index 0ecd128789..3ae166eff9 100644 --- a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala @@ -2,9 +2,6 @@ package com.wavesplatform.settings import com.typesafe.config.{Config, ConfigFactory} import com.wavesplatform.metrics.Metrics -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* - import scala.concurrent.duration.FiniteDuration import pureconfig.* import pureconfig.generic.auto.* @@ -45,7 +42,7 @@ object WavesSettings { val networkSettings = wavesConfigSource.at("network").loadOrThrow[NetworkSettings] val walletSettings = wavesConfigSource.at("wallet").loadOrThrow[WalletSettings] val blockchainSettings = BlockchainSettings.fromConfig(waves.getConfig("blockchain")) - val minerSettings = waves.as[MinerSettings]("miner") + val minerSettings = wavesConfigSource.at("miner").loadOrThrow[MinerSettings] val restAPISettings = wavesConfigSource.at("rest-api").loadOrThrow[RestAPISettings] val synchronizationSettings = wavesConfigSource.at("synchronization").loadOrThrow[SynchronizationSettings] val utxSettings = wavesConfigSource.at("utx").loadOrThrow[UtxSettings] From 80258b6b84f3e57929db5553bb6f760d7657fb04 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 5 Nov 2024 11:27:29 +0400 Subject: [PATCH 04/31] Move some implicits to package object --- .../wavesplatform/account/PrivateKey.scala | 13 ------- .../settings/BlockchainSettings.scala | 9 ----- .../com/wavesplatform/settings/package.scala | 34 +++++++------------ .../settings/MinerSettingsSpecification.scala | 6 ++-- 4 files changed, 15 insertions(+), 47 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala b/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala index a803cd515c..80a68623ae 100644 --- a/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala +++ b/node/src/main/scala/com/wavesplatform/account/PrivateKey.scala @@ -3,8 +3,6 @@ package com.wavesplatform.account import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto.KeyLength import play.api.libs.json.{Format, Writes} -import pureconfig.ConfigReader -import pureconfig.error.CannotConvert import supertagged.* import supertagged.postfix.* @@ -24,15 +22,4 @@ object PrivateKey extends TaggedType[ByteStr] { com.wavesplatform.utils.byteStrFormat.map(this.apply), Writes(pk => com.wavesplatform.utils.byteStrFormat.writes(pk)) ) - - implicit val configReader: ConfigReader[PrivateKey] = - ConfigReader.fromString(str => - ByteStr - .decodeBase58(str) - .toEither - .map(PrivateKey(_)) - .left - .map(e => CannotConvert(str, "ByteStr", e.getMessage)) - ) - } diff --git a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala index 8597a9e5da..ddb73fba41 100644 --- a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala @@ -7,9 +7,6 @@ import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import pureconfig.* import pureconfig.generic.auto.* -import pureconfig.configurable.* -import pureconfig.ConvertHelpers.* -import pureconfig.error.CannotConvert import net.ceedubs.ficus.readers.ValueReader import scala.concurrent.duration.* @@ -254,12 +251,6 @@ object BlockchainSettings { def fromRootConfig(config: Config): BlockchainSettings = fromConfig(config.getConfig("waves.blockchain")) def fromConfig(config: Config): BlockchainSettings = { - implicit val intMapReader: ConfigReader[Map[Short, Int]] = genericMapReader(catchReadError(_.toShort)) - - // Note: not sure if all ByteStr values are base58 encoded - implicit val byteStrReader: ConfigReader[ByteStr] = - ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) - val blockchainType = config.getString("type").toUpperCase val (addressSchemeCharacter, functionalitySettings, genesisSettings, rewardsSettings) = blockchainType match { case BlockchainType.STAGENET => diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index b21278620e..72ac08a9e5 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -3,38 +3,28 @@ package com.wavesplatform import java.io.File import java.net.{InetSocketAddress, URI} import cats.data.NonEmptyList -import com.typesafe.config.{Config, ConfigException, ConfigFactory, ConfigValueType} +import com.typesafe.config.{Config, ConfigException, ConfigFactory} import com.wavesplatform.account.PrivateKey import com.wavesplatform.common.state.ByteStr import net.ceedubs.ficus.Ficus.traversableReader import net.ceedubs.ficus.readers.namemappers.HyphenNameMapper import net.ceedubs.ficus.readers.{NameMapper, ValueReader} +import pureconfig.ConfigReader +import pureconfig.ConvertHelpers.catchReadError +import pureconfig.configurable.genericMapReader +import pureconfig.error.CannotConvert import supertagged.TaggedType -import scala.jdk.CollectionConverters.* import scala.util.Try package object settings { implicit val hyphenCase: NameMapper = HyphenNameMapper - implicit val fileReader: ValueReader[File] = (cfg, path) => new File(cfg.getString(path)) - implicit val byteStrReader: ValueReader[ByteStr] = (cfg, path) => ByteStr.decodeBase58(cfg.getString(path)).get - implicit val shortValueReader: ValueReader[Short] = (cfg, path) => cfg.getLong(path).toShort - implicit val preactivatedFeaturesReader: ValueReader[Map[Short, Int]] = (config: Config, path: String) => - if (config.getIsNull(path)) Map.empty - else { - config.getValue(path).valueType() match { - case ConfigValueType.OBJECT => - val paf = config.getConfig(path) - (for { - featureId <- paf.root().keySet().asScala - } yield featureId.toShort -> paf.getInt(featureId)).toMap - case ConfigValueType.STRING if config.getString(path).isEmpty => - Map.empty - case other => - throw new ConfigException.WrongType(config.getValue(path).origin(), path, ConfigValueType.OBJECT.name(), other.name()) - } - } + implicit val fileReader: ValueReader[File] = (cfg, path) => new File(cfg.getString(path)) + implicit val byteStrReader: ConfigReader[ByteStr] = + ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) + implicit val shortValueReader: ValueReader[Short] = (cfg, path) => cfg.getLong(path).toShort + implicit val preactivatedFeaturesReader: ConfigReader[Map[Short, Int]] = genericMapReader(catchReadError(_.toShort)) implicit val byteReader: ValueReader[Byte] = { (cfg: Config, path: String) => val x = cfg.getInt(path) @@ -47,7 +37,7 @@ package object settings { new InetSocketAddress(uri.getHost, uri.getPort) } - implicit val privateKeyReader: ValueReader[PrivateKey] = byteStrReader.map(PrivateKey(_)) + implicit val privateKeyReader: ConfigReader[PrivateKey] = ConfigReader[ByteStr].map(PrivateKey(_)) implicit def nonEmptyListReader[T: ValueReader]: ValueReader[NonEmptyList[T]] = implicitly[ValueReader[List[T]]].map { case Nil => throw new IllegalArgumentException("Expected at least one element") @@ -57,7 +47,7 @@ package object settings { object SizeInBytes extends TaggedType[Long] type SizeInBytes = SizeInBytes.Type - implicit val sizeInBytesReader: ValueReader[SizeInBytes] = {(cfg: Config, path: String) => + implicit val sizeInBytesReader: ValueReader[SizeInBytes] = { (cfg: Config, path: String) => SizeInBytes(cfg.getBytes(path).toLong) } diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/MinerSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/MinerSettingsSpecification.scala index 6836adbbca..26d7a382a5 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/MinerSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/MinerSettingsSpecification.scala @@ -3,8 +3,8 @@ package com.wavesplatform.settings import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec import com.wavesplatform.transaction.TxHelpers -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import scala.concurrent.duration.* @@ -28,7 +28,7 @@ class MinerSettingsSpecification extends FlatSpec { """.stripMargin) .resolve() - val settings = config.as[MinerSettings]("waves.miner") + val settings = ConfigSource.fromConfig(config).at("waves.miner").loadOrThrow[MinerSettings] settings.enable should be(true) settings.quorum should be(1) From 168bb70e532208a3db96d2fcffe2f99a95d509d4 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 5 Nov 2024 11:42:38 +0400 Subject: [PATCH 05/31] Fix WalletSettingsSpecification --- .../settings/WalletSettingsSpecification.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/WalletSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/WalletSettingsSpecification.scala index a0f17b130b..78286bc852 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/WalletSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/WalletSettingsSpecification.scala @@ -3,16 +3,16 @@ package com.wavesplatform.settings import com.typesafe.config.ConfigFactory import com.wavesplatform.common.state.ByteStr import com.wavesplatform.test.FlatSpec -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +import pureconfig.ConfigSource +import pureconfig.generic.auto.* class WalletSettingsSpecification extends FlatSpec { "WalletSettings" should "read values from config" in { - val config = loadConfig(ConfigFactory.parseString("""waves.wallet { - | password: "some string as password" - | seed: "BASE58SEED" - |}""".stripMargin)) - val settings = config.as[WalletSettings]("waves.wallet") + val config = loadConfig(ConfigFactory.parseString("""waves.wallet { + | password: "some string as password" + | seed: "BASE58SEED" + |}""".stripMargin)) + val settings = ConfigSource.fromConfig(config).at("waves.wallet").loadOrThrow[WalletSettings] settings.seed should be(Some(ByteStr.decodeBase58("BASE58SEED").get)) settings.password should be(Some("some string as password")) From e433384783552cdf8e8b2dfdf2298edb56d6e51e Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 5 Nov 2024 12:59:12 +0400 Subject: [PATCH 06/31] Remove more ficus usage --- .../com/wavesplatform/settings/package.scala | 2 -- .../http/RestAPISettingsHelper.scala | 10 +++--- .../FeaturesSettingsSpecification.scala | 24 +++++++------ .../RestAPISettingsSpecification.scala | 6 ++-- ...SynchronizationSettingsSpecification.scala | 6 ++-- .../settings/UtxSettingsSpecification.scala | 36 ++++++++++--------- 6 files changed, 43 insertions(+), 41 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index 72ac08a9e5..d2a2e2c443 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -1,6 +1,5 @@ package com.wavesplatform -import java.io.File import java.net.{InetSocketAddress, URI} import cats.data.NonEmptyList import com.typesafe.config.{Config, ConfigException, ConfigFactory} @@ -20,7 +19,6 @@ import scala.util.Try package object settings { implicit val hyphenCase: NameMapper = HyphenNameMapper - implicit val fileReader: ValueReader[File] = (cfg, path) => new File(cfg.getString(path)) implicit val byteStrReader: ConfigReader[ByteStr] = ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) implicit val shortValueReader: ValueReader[Short] = (cfg, path) => cfg.getLong(path).toShort diff --git a/node/tests/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala b/node/tests/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala index 5e994ebd51..a8e7023b06 100644 --- a/node/tests/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala +++ b/node/tests/src/test/scala/com/wavesplatform/http/RestAPISettingsHelper.scala @@ -4,9 +4,9 @@ import com.typesafe.config.ConfigFactory import com.wavesplatform.api.http.`X-Api-Key` import com.wavesplatform.common.utils.Base58 import com.wavesplatform.crypto -import com.wavesplatform.settings._ -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +import com.wavesplatform.settings.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* trait RestAPISettingsHelper { private val apiKey: String = "test_api_key" @@ -20,7 +20,7 @@ trait RestAPISettingsHelper { lazy val restAPISettings = { val keyHash = Base58.encode(crypto.secureHash(apiKey.getBytes("UTF-8"))) - ConfigFactory + val config = ConfigFactory .parseString( s"""waves.rest-api { | api-key-hash = $keyHash @@ -32,6 +32,6 @@ trait RestAPISettingsHelper { """.stripMargin ) .withFallback(ConfigFactory.load()) - .as[RestAPISettings]("waves.rest-api") + ConfigSource.fromConfig(config).at("waves.rest-api").loadOrThrow[RestAPISettings] } } diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/FeaturesSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/FeaturesSettingsSpecification.scala index c313d209dd..762f6b636e 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/FeaturesSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/FeaturesSettingsSpecification.scala @@ -2,21 +2,23 @@ package com.wavesplatform.settings import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +import pureconfig.ConfigSource +import pureconfig.generic.auto.* class FeaturesSettingsSpecification extends FlatSpec { "FeaturesSettings" should "read values" in { - val config = ConfigFactory.parseString(""" - |waves { - | features { - | auto-shutdown-on-unsupported-feature = yes - | supported = [123,124,135] - | } - |} - """.stripMargin).resolve() + val config = ConfigFactory + .parseString(""" + |waves { + | features { + | auto-shutdown-on-unsupported-feature = yes + | supported = [123,124,135] + | } + |} + """.stripMargin) + .resolve() - val settings = config.as[FeaturesSettings]("waves.features") + val settings = ConfigSource.fromConfig(config).at("waves.features").loadOrThrow[FeaturesSettings] settings.autoShutdownOnUnsupportedFeature should be(true) settings.supported shouldEqual List(123, 124, 135) diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/RestAPISettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/RestAPISettingsSpecification.scala index eec3149053..4d0bc2b9e0 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/RestAPISettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/RestAPISettingsSpecification.scala @@ -3,8 +3,8 @@ package com.wavesplatform.settings import akka.http.scaladsl.model.HttpMethods.* import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* class RestAPISettingsSpecification extends FlatSpec { "RestAPISettings" should "read values" in { @@ -36,7 +36,7 @@ class RestAPISettingsSpecification extends FlatSpec { |} """.stripMargin ) - val settings = config.as[RestAPISettings]("waves.rest-api") + val settings = ConfigSource.fromConfig(config).at("waves.rest-api").loadOrThrow[RestAPISettings] settings.enable should be(true) settings.bindAddress should be("127.0.0.1") diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/SynchronizationSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/SynchronizationSettingsSpecification.scala index 4385b152af..19b1c6d3e0 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/SynchronizationSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/SynchronizationSettingsSpecification.scala @@ -4,8 +4,8 @@ import com.typesafe.config.ConfigFactory import com.wavesplatform.network.InvalidBlockStorageImpl.InvalidBlockStorageSettings import com.wavesplatform.settings.SynchronizationSettings.{HistoryReplierSettings, MicroblockSynchronizerSettings, UtxSynchronizerSettings} import com.wavesplatform.test.FlatSpec -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import scala.concurrent.duration.* @@ -50,7 +50,7 @@ class SynchronizationSettingsSpecification extends FlatSpec { """.stripMargin) .resolve() - val settings = config.as[SynchronizationSettings]("waves.synchronization") + val settings = ConfigSource.fromConfig(config).at("waves.synchronization").loadOrThrow[SynchronizationSettings] settings.maxRollback should be(100) settings.synchronizationTimeout should be(30.seconds) settings.processedBlocksCacheTimeout should be(3.minutes) diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/UtxSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/UtxSettingsSpecification.scala index 9535c11ff2..af2f906d6b 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/UtxSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/UtxSettingsSpecification.scala @@ -2,27 +2,29 @@ package com.wavesplatform.settings import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +import pureconfig.ConfigSource +import pureconfig.generic.auto.* class UtxSettingsSpecification extends FlatSpec { "UTXSettings" should "read values" in { - val config = ConfigFactory.parseString("""waves { - | utx { - | max-size = 100 - | max-bytes-size = 100 - | max-scripted-size = 100 - | blacklist-sender-addresses = ["a"] - | allow-blacklisted-transfer-to = ["b"] - | fast-lane-addresses = ["c"] - | allow-transactions-from-smart-accounts = false - | allow-skip-checks = false - | force-validate-in-cleanup = false - | always-unlimited-execution = true - | } - |}""".stripMargin).resolve() + val config = ConfigFactory + .parseString("""waves { + | utx { + | max-size = 100 + | max-bytes-size = 100 + | max-scripted-size = 100 + | blacklist-sender-addresses = ["a"] + | allow-blacklisted-transfer-to = ["b"] + | fast-lane-addresses = ["c"] + | allow-transactions-from-smart-accounts = false + | allow-skip-checks = false + | force-validate-in-cleanup = false + | always-unlimited-execution = true + | } + |}""".stripMargin) + .resolve() - val settings = config.as[UtxSettings]("waves.utx") + val settings = ConfigSource.fromConfig(config).at("waves.utx").loadOrThrow[UtxSettings] settings.maxSize shouldBe 100 settings.maxBytesSize shouldBe 100L settings.maxScriptedSize shouldBe 100 From eaf3e9eaca549bafc193c3eab9cfe987f33fedbe Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 5 Nov 2024 17:25:07 +0400 Subject: [PATCH 07/31] Remove more implicits --- .../scala/com/wavesplatform/settings/package.scala | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index d2a2e2c443..43b028f4f1 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -1,11 +1,9 @@ package com.wavesplatform import java.net.{InetSocketAddress, URI} -import cats.data.NonEmptyList import com.typesafe.config.{Config, ConfigException, ConfigFactory} import com.wavesplatform.account.PrivateKey import com.wavesplatform.common.state.ByteStr -import net.ceedubs.ficus.Ficus.traversableReader import net.ceedubs.ficus.readers.namemappers.HyphenNameMapper import net.ceedubs.ficus.readers.{NameMapper, ValueReader} import pureconfig.ConfigReader @@ -21,15 +19,16 @@ package object settings { implicit val byteStrReader: ConfigReader[ByteStr] = ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) - implicit val shortValueReader: ValueReader[Short] = (cfg, path) => cfg.getLong(path).toShort implicit val preactivatedFeaturesReader: ConfigReader[Map[Short, Int]] = genericMapReader(catchReadError(_.toShort)) + // used by BlockchainGeneratorApp and MinerChallengeSimulator implicit val byteReader: ValueReader[Byte] = { (cfg: Config, path: String) => val x = cfg.getInt(path) if (x.isValidByte) x.toByte else throw new ConfigException.WrongType(cfg.origin(), s"$path has an invalid value: '$x' expected to be a byte") } + // used by TransactionsGeneratorApp implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = { (config: Config, path: String) => val uri = new URI(s"my://${config.getString(path)}") new InetSocketAddress(uri.getHost, uri.getPort) @@ -37,18 +36,9 @@ package object settings { implicit val privateKeyReader: ConfigReader[PrivateKey] = ConfigReader[ByteStr].map(PrivateKey(_)) - implicit def nonEmptyListReader[T: ValueReader]: ValueReader[NonEmptyList[T]] = implicitly[ValueReader[List[T]]].map { - case Nil => throw new IllegalArgumentException("Expected at least one element") - case x :: xs => NonEmptyList(x, xs) - } - object SizeInBytes extends TaggedType[Long] type SizeInBytes = SizeInBytes.Type - implicit val sizeInBytesReader: ValueReader[SizeInBytes] = { (cfg: Config, path: String) => - SizeInBytes(cfg.getBytes(path).toLong) - } - def loadConfig(userConfig: Config): Config = { loadConfig(Some(userConfig)) } From ec3a70a00d36c6c0bad8d202904e553803ea8705 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Wed, 6 Nov 2024 00:32:08 +0400 Subject: [PATCH 08/31] Remove more ficus --- .../wavesplatform/GenesisBlockGenerator.scala | 9 ++++----- .../com/wavesplatform/settings/package.scala | 18 ++++++++---------- .../generator/BlockchainGeneratorApp.scala | 11 +++++++---- .../generator/MinerChallengeSimulator.scala | 10 +++++++--- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/GenesisBlockGenerator.scala b/node/src/main/scala/com/wavesplatform/GenesisBlockGenerator.scala index 51444fddf5..413f937a4f 100644 --- a/node/src/main/scala/com/wavesplatform/GenesisBlockGenerator.scala +++ b/node/src/main/scala/com/wavesplatform/GenesisBlockGenerator.scala @@ -9,12 +9,12 @@ import com.wavesplatform.consensus.PoSCalculator.{generationSignature, hit} import com.wavesplatform.consensus.{FairPoSCalculator, NxtPoSCalculator} import com.wavesplatform.crypto.* import com.wavesplatform.features.{BlockchainFeature, BlockchainFeatures} -import com.wavesplatform.settings.{FunctionalitySettings, GenesisSettings, GenesisTransactionSettings} +import com.wavesplatform.settings.* import com.wavesplatform.transaction.{GenesisTransaction, TxNonNegativeAmount} import com.wavesplatform.utils.* import com.wavesplatform.wallet.Wallet -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import java.io.{File, FileNotFoundException} import java.nio.file.Files @@ -102,8 +102,7 @@ object GenesisBlockGenerator { } def parseSettings(config: Config): Settings = { - import net.ceedubs.ficus.readers.namemappers.implicits.hyphenCase - config.as[Settings]("genesis-generator") + ConfigSource.fromConfig(config).at("genesis-generator").loadOrThrow[Settings] } def createConfig(settings: Settings): String = { diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index 43b028f4f1..2ff06e658a 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -1,7 +1,7 @@ package com.wavesplatform import java.net.{InetSocketAddress, URI} -import com.typesafe.config.{Config, ConfigException, ConfigFactory} +import com.typesafe.config.{Config, ConfigFactory} import com.wavesplatform.account.PrivateKey import com.wavesplatform.common.state.ByteStr import net.ceedubs.ficus.readers.namemappers.HyphenNameMapper @@ -20,20 +20,18 @@ package object settings { implicit val byteStrReader: ConfigReader[ByteStr] = ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) implicit val preactivatedFeaturesReader: ConfigReader[Map[Short, Int]] = genericMapReader(catchReadError(_.toShort)) - - // used by BlockchainGeneratorApp and MinerChallengeSimulator - implicit val byteReader: ValueReader[Byte] = { (cfg: Config, path: String) => - val x = cfg.getInt(path) - if (x.isValidByte) x.toByte - else throw new ConfigException.WrongType(cfg.origin(), s"$path has an invalid value: '$x' expected to be a byte") - } - - // used by TransactionsGeneratorApp + implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = { (config: Config, path: String) => val uri = new URI(s"my://${config.getString(path)}") new InetSocketAddress(uri.getHost, uri.getPort) } + implicit val inetSocketAddressConfigReader: ConfigReader[InetSocketAddress] = ConfigReader[String].map { str => + val uri = new URI(s"my://$str") + new InetSocketAddress(uri.getHost, uri.getPort) + } + + implicit val privateKeyReader: ConfigReader[PrivateKey] = ConfigReader[ByteStr].map(PrivateKey(_)) object SizeInBytes extends TaggedType[Long] diff --git a/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala b/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala index fe592d1246..9c6a2df381 100644 --- a/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala +++ b/node/src/main/scala/com/wavesplatform/utils/generator/BlockchainGeneratorApp.scala @@ -2,7 +2,6 @@ package com.wavesplatform.utils.generator import java.io.{File, FileOutputStream, PrintWriter} import java.util.concurrent.TimeUnit - import cats.implicits.* import com.typesafe.config.{ConfigFactory, ConfigParseOptions} import com.wavesplatform.{GenesisBlockGenerator, Version} @@ -22,8 +21,8 @@ import com.wavesplatform.utx.UtxPoolImpl import com.wavesplatform.wallet.Wallet import io.netty.channel.group.DefaultChannelGroup import monix.reactive.subjects.ConcurrentSubject -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import play.api.libs.json.Json import scopt.OParser @@ -96,7 +95,11 @@ object BlockchainGeneratorApp extends ScorexLogging { val config = readConfFile(options.genesisConfigFile) val genSettings = GenesisBlockGenerator.parseSettings(config) - val genesis = ConfigFactory.parseString(GenesisBlockGenerator.createConfig(genSettings)).as[GenesisSettings]("genesis") + val genesis = + ConfigSource + .fromConfig(ConfigFactory.parseString(GenesisBlockGenerator.createConfig(genSettings))) + .at("genesis") + .loadOrThrow[GenesisSettings] log.info(s"Initial base target is ${genesis.initialBaseTarget}") diff --git a/node/src/main/scala/com/wavesplatform/utils/generator/MinerChallengeSimulator.scala b/node/src/main/scala/com/wavesplatform/utils/generator/MinerChallengeSimulator.scala index aede189071..d59f235588 100644 --- a/node/src/main/scala/com/wavesplatform/utils/generator/MinerChallengeSimulator.scala +++ b/node/src/main/scala/com/wavesplatform/utils/generator/MinerChallengeSimulator.scala @@ -25,8 +25,8 @@ import io.netty.channel.group.DefaultChannelGroup import monix.eval.Task import monix.execution.schedulers.SchedulerService import monix.reactive.subjects.ConcurrentSubject -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import org.apache.commons.io.FileUtils import java.io.{File, FileNotFoundException} @@ -56,7 +56,11 @@ object MinerChallengeSimulator { val config = readConfFile(genesisConfFile) val genSettings = GenesisBlockGenerator.parseSettings(config) - val genesis = ConfigFactory.parseString(GenesisBlockGenerator.createConfig(genSettings)).as[GenesisSettings]("genesis") + val genesis = + ConfigSource + .fromConfig(ConfigFactory.parseString(GenesisBlockGenerator.createConfig(genSettings))) + .at("genesis") + .loadOrThrow[GenesisSettings] val blockchainSettings = BlockchainSettings( genSettings.chainId.toChar, From 6925916d2033d98422ff866db4cbe34f16c66538 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Wed, 6 Nov 2024 23:20:01 +0400 Subject: [PATCH 09/31] Remove more ficus code --- .../com/wavesplatform/state/Settings.scala | 6 ++-- .../api/grpc/GRPCServerExtension.scala | 7 ++--- .../events/BlockchainUpdates.scala | 7 ++--- .../generator/TransactionsGeneratorApp.scala | 29 +++++++++---------- .../scala/com/wavesplatform/it/Docker.scala | 21 +++++++++----- .../com/wavesplatform/settings/package.scala | 13 ++------- 6 files changed, 38 insertions(+), 45 deletions(-) diff --git a/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala b/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala index c5540ddc18..7539a6fdfc 100644 --- a/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala +++ b/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala @@ -1,7 +1,8 @@ package com.wavesplatform.state import com.typesafe.config.Config -import net.ceedubs.ficus.Ficus._ +import pureconfig.ConfigSource +import pureconfig.generic.auto.* case class Settings( networkConfigFile: String, @@ -15,7 +16,6 @@ case class Settings( object Settings { def fromConfig(config: Config): Settings = { - import net.ceedubs.ficus.readers.ArbitraryTypeReader._ - config.as[Settings]("waves.benchmark.state") + ConfigSource.fromConfig(config).at("waves.benchmark.state").loadOrThrow[Settings] } } diff --git a/grpc-server/src/main/scala/com/wavesplatform/api/grpc/GRPCServerExtension.scala b/grpc-server/src/main/scala/com/wavesplatform/api/grpc/GRPCServerExtension.scala index efd9aaa230..9fa7b00e64 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/api/grpc/GRPCServerExtension.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/api/grpc/GRPCServerExtension.scala @@ -8,16 +8,15 @@ import io.grpc.Server import io.grpc.netty.NettyServerBuilder import io.grpc.protobuf.services.ProtoReflectionService import monix.execution.Scheduler -import net.ceedubs.ficus.readers.namemappers.implicits.hyphenCase -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import java.net.InetSocketAddress import java.util.concurrent.Executors import scala.concurrent.Future class GRPCServerExtension(context: ExtensionContext) extends Extension with ScorexLogging { - private val settings = context.settings.config.as[GRPCSettings]("waves.grpc") + private val settings = ConfigSource.fromConfig(context.settings.config).at("waves.grpc").loadOrThrow[GRPCSettings] private val executor = Executors.newFixedThreadPool(settings.workerThreads, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("grpc-server-worker-%d").build()) private implicit val apiScheduler: Scheduler = Scheduler(executor) private val bindAddress = new InetSocketAddress(settings.host, settings.port) diff --git a/grpc-server/src/main/scala/com/wavesplatform/events/BlockchainUpdates.scala b/grpc-server/src/main/scala/com/wavesplatform/events/BlockchainUpdates.scala index 7cf25acde8..ab717ef7b4 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/events/BlockchainUpdates.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/events/BlockchainUpdates.scala @@ -12,10 +12,9 @@ import io.grpc.protobuf.services.ProtoReflectionService import io.grpc.{Metadata, Server, ServerStreamTracer, Status} import monix.execution.schedulers.SchedulerService import monix.execution.{ExecutionModel, Scheduler, UncaughtExceptionReporter} -import net.ceedubs.ficus.readers.namemappers.implicits.hyphenCase -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* import org.rocksdb.RocksDB +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import java.net.InetSocketAddress import java.util.concurrent.TimeUnit @@ -24,7 +23,7 @@ import scala.concurrent.duration.* import scala.util.Try class BlockchainUpdates(private val context: Context) extends Extension with ScorexLogging with BlockchainUpdateTriggers { - private[this] val settings = context.settings.config.as[BlockchainUpdatesSettings]("waves.blockchain-updates") + private[this] val settings = ConfigSource.fromConfig(context.settings.config).at("waves.blockchain-updates").loadOrThrow[BlockchainUpdatesSettings] private[this] implicit val scheduler: SchedulerService = Schedulers.fixedPool( settings.workerThreads, "blockchain-updates", 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 7042742176..50a651b639 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala @@ -228,21 +228,20 @@ object TransactionsGeneratorApp extends App with ScoptImplicits with FicusImplic log.info(s"Universe precondition tail transactions size: ${initialTailTransactions.size}") log.info(s"Generator precondition tail transactions size: ${initialGenTailTransactions.size}") - val workers = finalConfig.sendTo.map { - case NodeAddress(node, nodeRestUrl) => - log.info(s"Creating worker: ${node.getHostString}:${node.getPort}") - // new Worker(finalConfig.worker, sender, node, generator, initialTransactions.map(RawBytes.from)) - new Worker( - finalConfig.worker, - Iterator.continually(generator.next()).flatten, - sender, - node, - nodeRestUrl, - () => canContinue, - initialUniTransactions ++ initialGenTransactions, - finalConfig.privateKeyAccounts.map(_.toAddress.toString), - initialTailTransactions ++ initialGenTailTransactions - ) + val workers = finalConfig.sendTo.map { case NodeAddress(node, nodeRestUrl) => + log.info(s"Creating worker: ${node.getHostString}:${node.getPort}") + // new Worker(finalConfig.worker, sender, node, generator, initialTransactions.map(RawBytes.from)) + new Worker( + finalConfig.worker, + Iterator.continually(generator.next()).flatten, + sender, + node, + nodeRestUrl, + () => canContinue, + initialUniTransactions ++ initialGenTransactions, + finalConfig.privateKeyAccounts.map(_.toAddress.toString), + initialTailTransactions ++ initialGenTailTransactions + ) } def close(status: Int): Unit = { diff --git a/node-it/src/test/scala/com/wavesplatform/it/Docker.scala b/node-it/src/test/scala/com/wavesplatform/it/Docker.scala index 04f496b7c0..8f8ab85a5c 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/Docker.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/Docker.scala @@ -17,12 +17,12 @@ import com.wavesplatform.it.util.GlobalTimer.instance as timer import com.wavesplatform.settings.* import com.wavesplatform.utils.ScorexLogging import monix.eval.Coeval -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* import org.apache.commons.compress.archivers.ArchiveStreamFactory import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.io.IOUtils import org.asynchttpclient.Dsl.* +import pureconfig.ConfigSource +import pureconfig.generic.auto.* import java.io.{FileOutputStream, IOException} import java.net.{InetAddress, InetSocketAddress, URL} @@ -581,14 +581,19 @@ object Docker { |}""".stripMargin) val genesisConfig = timestampOverrides.withFallback(configTemplate) - val gs = genesisConfig.as[GenesisSettings]("waves.blockchain.custom.genesis") - val features = featuresConfig + val gs = ConfigSource.fromConfig(genesisConfig).at("waves.blockchain.custom.genesis").loadOrThrow[GenesisSettings] + val featuresConfigAdjusted = featuresConfig .map(_.withFallback(configTemplate)) .getOrElse(configTemplate) .resolve() - .getAs[Map[Short, Int]]("waves.blockchain.custom.functionality.pre-activated-features") - val isRideV6Activated = features.exists(_.get(BlockchainFeatures.RideV6.id).contains(0)) - val isTxStateSnapshotActivated = features.exists(_.get(BlockchainFeatures.LightNode.id).contains(0)) + val features = + ConfigSource + .fromConfig(featuresConfigAdjusted) + .at("waves.blockchain.custom.functionality.pre-activated-features") + .loadOrThrow[Map[Short, Int]] + + val isRideV6Activated = features.get(BlockchainFeatures.RideV6.id).contains(0) + val isTxStateSnapshotActivated = features.get(BlockchainFeatures.LightNode.id).contains(0) val genesisSignature = Block.genesis(gs, isRideV6Activated, isTxStateSnapshotActivated).explicitGet().id() @@ -596,7 +601,7 @@ object Docker { } AddressScheme.current = new AddressScheme { - override val chainId: Byte = configTemplate.as[String]("waves.blockchain.custom.address-scheme-character").charAt(0).toByte + override val chainId: Byte = ConfigSource.fromConfig(configTemplate).at("waves.blockchain.custom.address-scheme-character").loadOrThrow[String].charAt(0).toByte } def apply(owner: Class[?]): Docker = new Docker(tag = owner.getSimpleName) diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index 2ff06e658a..ce7c7e4a86 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -4,8 +4,7 @@ import java.net.{InetSocketAddress, URI} import com.typesafe.config.{Config, ConfigFactory} import com.wavesplatform.account.PrivateKey import com.wavesplatform.common.state.ByteStr -import net.ceedubs.ficus.readers.namemappers.HyphenNameMapper -import net.ceedubs.ficus.readers.{NameMapper, ValueReader} +import net.ceedubs.ficus.readers.ValueReader import pureconfig.ConfigReader import pureconfig.ConvertHelpers.catchReadError import pureconfig.configurable.genericMapReader @@ -15,23 +14,15 @@ import supertagged.TaggedType import scala.util.Try package object settings { - implicit val hyphenCase: NameMapper = HyphenNameMapper - implicit val byteStrReader: ConfigReader[ByteStr] = ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) implicit val preactivatedFeaturesReader: ConfigReader[Map[Short, Int]] = genericMapReader(catchReadError(_.toShort)) - + implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = { (config: Config, path: String) => val uri = new URI(s"my://${config.getString(path)}") new InetSocketAddress(uri.getHost, uri.getPort) } - implicit val inetSocketAddressConfigReader: ConfigReader[InetSocketAddress] = ConfigReader[String].map { str => - val uri = new URI(s"my://$str") - new InetSocketAddress(uri.getHost, uri.getPort) - } - - implicit val privateKeyReader: ConfigReader[PrivateKey] = ConfigReader[ByteStr].map(PrivateKey(_)) object SizeInBytes extends TaggedType[Long] From 7e3c31cb6708670f4e823bbbb48b88d6e153b414 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Mon, 11 Nov 2024 12:28:15 +0400 Subject: [PATCH 10/31] RideRunnerInputParser WIP --- .../runner/input/RideRunnerInputParser.scala | 83 +++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 9ca9dc4039..5467cbaa03 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -19,7 +19,10 @@ import com.wavesplatform.transaction.transfer.TransferTransactionLike import com.wavesplatform.transaction.{TransactionFactory, TxNonNegativeAmount, TxValidationError} import com.wavesplatform.utils.byteArrayFromString import net.ceedubs.ficus.Ficus.* +import pureconfig._ +import pureconfig.generic.auto.* import net.ceedubs.ficus.readers.{ArbitraryTypeReader, ValueReader} +import pureconfig.error.CannotConvert import play.api.libs.json.* import java.nio.charset.StandardCharsets @@ -36,9 +39,35 @@ object RideRunnerInputParser extends ArbitraryTypeReader { /** Use after "prepare" */ - def from(config: Config): RideRunnerInput = config.as[RideRunnerInput] + def from(config: Config): RideRunnerInput = { + val address = ConfigSource.fromConfig(config).at("address").loadOrThrow[Address] + val request = jsValueFromConfig[JsObject](config, "request") + val chainId = getChainId(config) + val intAsString = ConfigSource.fromConfig(config).at("intAsString").load[Boolean].getOrElse(false) + val trace = ConfigSource.fromConfig(config).at("trace").load[Boolean].getOrElse(false) + val evaluateScriptComplexityLimit = + ConfigSource.fromConfig(config).at("evaluateScriptComplexityLimit").load[Int].getOrElse(Int.MaxValue) + val maxTxErrorLogSize = ConfigSource.fromConfig(config).at("maxTxErrorLogSize").load[Int].getOrElse(1024) + // val state = RideRunnerBlockchainState.fromConfig(config.getConfig("state")) + val state = config.as[RideRunnerBlockchainState]("state") + val postProcessing = ConfigSource.fromConfig(config).at("postProcessing").load[List[RideRunnerPostProcessingMethod]].getOrElse(List.empty) + val test = Try(jsValueFromConfig[JsValue](config, "test")).map(RideRunnerTest.apply).toOption + + RideRunnerInput( + address = address, + request = request, + chainId = chainId, + intAsString = intAsString, + trace = trace, + evaluateScriptComplexityLimit = evaluateScriptComplexityLimit, + maxTxErrorLogSize = maxTxErrorLogSize, + state = state, + postProcessing = postProcessing, + test = test + ) + } - def getChainId(x: Config): Char = x.getAs[Char]("chainId").getOrElse(fail("chainId is not specified or wrong")) + def getChainId(x: Config): Char = ConfigSource.fromConfig(x).at("chainId").load[Char].getOrElse(fail("chainId is not specified or wrong")) implicit val shortMapKeyValueReader: MapKeyValueReader[Short] = { key => key.toShortOption.getOrElse(fail(s"Expected an integer value between ${Short.MinValue} and ${Short.MaxValue}")) @@ -58,10 +87,6 @@ object RideRunnerInputParser extends ArbitraryTypeReader { if (x.isEmpty) None else byteStrDefaultBase58FromString(x).some } - implicit val charValueReader: ValueReader[Char] = ValueReader[String].map { x => - if (x.length == 1) x.head else fail(s"Expected one char, got: $x") - } - implicit val byteValueReader: ValueReader[Byte] = ValueReader[Int].map { x => if (x.isValidByte) x.toByte else fail(s"Expected an integer value between ${Byte.MinValue} and ${Byte.MaxValue}") @@ -72,6 +97,24 @@ object RideRunnerInputParser extends ArbitraryTypeReader { else fail(s"Expected a value between ${Short.MinValue} and ${Short.MaxValue}") } + implicit val shortMapKeyConfigReader: MapKeyConfigReader[Short] = { key => + key.toShortOption.getOrElse(fail(s"Expected an integer value between ${Short.MinValue} and ${Short.MaxValue}")) + } + + implicit val intMapKeyConfigReader: MapKeyConfigReader[Int] = { key => + key.toIntOption.getOrElse(fail(s"Expected an integer value between ${Int.MinValue} and ${Int.MaxValue}")) + } + + implicit val byteStrMapKeyConfigReader: MapKeyConfigReader[ByteStr] = byteStrDefaultBase58FromString(_) + + implicit val addressMapKeyConfigReader: MapKeyConfigReader[Address] = Address.fromString(_).getOrFail + + implicit val issuedAssetMapKeyConfigReader: MapKeyConfigReader[IssuedAsset] = IssuedAsset.fromString(_, identity, fail(_)) + + implicit val optBlockIdMapKeyConfigReader: MapKeyConfigReader[Option[BlockId]] = { x => + if (x.isEmpty) None else byteStrDefaultBase58FromString(x).some + } + implicit val heightValueReader: ValueReader[Height] = ValueReader[Int].map(Height(_)) implicit val stdLibVersionValueReader: ValueReader[StdLibVersion] = ValueReader[Int].map(StdLibVersion.VersionDic.idMap.apply) @@ -81,6 +124,8 @@ object RideRunnerInputParser extends ArbitraryTypeReader { implicit val txNonNegativeAmountValueReader: ValueReader[TxNonNegativeAmount] = ValueReader[Long].map(TxNonNegativeAmount.unsafeFrom) + implicit val txNonNegativeAmountConfigReader: ConfigReader[TxNonNegativeAmount] = ConfigReader[Long].map(TxNonNegativeAmount.unsafeFrom) + implicit val byteArrayValueReader: ValueReader[Array[Byte]] = ValueReader[String].map(byteArrayFromString(_, identity, fail(_))) implicit val byteStringValueReader: ValueReader[ByteString] = byteArrayValueReader.map(UnsafeByteOperations.unsafeWrap) @@ -104,6 +149,9 @@ object RideRunnerInputParser extends ArbitraryTypeReader { implicit val addressValueReader: ValueReader[Address] = ValueReader[String].map(Address.fromString(_).getOrFail) + implicit val addressConfigReader: ConfigReader[Address] = + ConfigReader.fromString(s => Address.fromString(s).left.map(_ => CannotConvert(s, "Address", "invalid address"))) + implicit val aliasValueReader: ValueReader[Alias] = ValueReader[String].map { x => val chainId = AddressScheme.current.chainId @@ -130,6 +178,8 @@ object RideRunnerInputParser extends ArbitraryTypeReader { implicit val publicKeyValueReader: ValueReader[PublicKey] = ValueReader[ByteStr].map(PublicKey(_)) + implicit val publicKeyConfigReader: ConfigReader[PublicKey] = ConfigReader[ByteStr].map(PublicKey(_)) + implicit val transferTransactionLikeValueReader: ValueReader[TransferTransactionLike] = jsObjectValueReader.map { js => TransactionFactory .fromSignedRequest(js) @@ -207,12 +257,33 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } } + def jsValueFromConfig[T: Reads](config: Config, path: String): T = { + val fixedPath = if (path == "") "x" else s"x.$path" + val jsonStr = config.atPath("x").root().render(ConfigRenderOptions.concise()) + JsonManipulations + .pick(Json.parse(jsonStr), fixedPath) + .getOrElse(fail(s"Expected a value at $path")) + .validate[T] match { + case JsSuccess(value, _) => value + case JsError(errors) => fail(s"Can't parse: ${errors.mkString("\n")}") + } + } + private implicit final class ValidationErrorOps[E, T](private val self: Either[E, T]) extends AnyVal { def getOrFail: T = self.fold(e => fail(e.toString), identity) } private def fail(message: String, cause: Throwable = null) = throw new IllegalArgumentException(message, cause) + implicit def arbitraryKeyMapConfigReader[K, V: ConfigReader](implicit kReader: MapKeyConfigReader[K]): ConfigReader[Map[K, V]] = + ConfigReader[Map[String, V]].map { xs => + xs.map { case (k, v) => kReader.readKey(k) -> v } + } + + trait MapKeyConfigReader[T] { + def readKey(key: String): T + } + implicit def arbitraryKeyMapValueReader[K, V: ValueReader](implicit kReader: MapKeyValueReader[K]): ValueReader[Map[K, V]] = ValueReader[Map[String, V]].map { xs => xs.map { case (k, v) => kReader.readKey(k) -> v } From 86fa7b6d34a527099f25f626407e957766a16a8b Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 12 Nov 2024 12:03:28 +0400 Subject: [PATCH 11/31] RideRunnerInputParser WIP --- .../input/RideRunnerBlockchainState.scala | 22 ++++++++ .../runner/input/RideRunnerInputParser.scala | 52 +++++++++++++++++-- .../RideRunnerInputParserTestSuite.scala | 11 +++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala index fdb1c575ff..2fd703be43 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala @@ -1,9 +1,13 @@ package com.wavesplatform.ride.runner.input +import com.typesafe.config.Config import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.transaction.Asset.IssuedAsset +import net.ceedubs.ficus.Ficus.* +import pureconfig.* +import RideRunnerInputParser.* case class RideRunnerBlockchainState( height: Int = 3296626, @@ -13,3 +17,21 @@ case class RideRunnerBlockchainState( blocks: Map[Int, RideRunnerBlock] = Map.empty, transactions: Map[ByteStr, RideRunnerTransaction] = Map.empty ) + +object RideRunnerBlockchainState { + def fromConfig(config: Config): RideRunnerBlockchainState = { + val height = ConfigSource.fromConfig(config).at("height").load[Int].getOrElse(3296626) + val features = ConfigSource.fromConfig(config).at("features").load[Set[Short]].getOrElse(BlockchainFeatures.implemented) + val accounts = ConfigSource.fromConfig(config).at("accounts").load[Map[Address, RideRunnerAccount]].getOrElse(Map.empty) + // val assets = ConfigSource.fromConfig(config).at("assets").load[Map[IssuedAsset, RideRunnerAsset]].getOrElse(Map.empty) + // val blocks = ConfigSource.fromConfig(config).at("blocks").load[Map[Int, RideRunnerBlock]].getOrElse(Map.empty) + // val transactions = ConfigSource.fromConfig(config).at("transactions").load[Map[ByteStr, RideRunnerTransaction]].getOrElse(Map.empty) + + // ficus // TODO: remove + // val accounts = config.as[Option[Map[Address, RideRunnerAccount]]]("accounts").getOrElse(Map.empty) + val assets = config.as[Option[Map[IssuedAsset, RideRunnerAsset]]]("assets").getOrElse(Map.empty) + val blocks = config.as[Option[Map[Int, RideRunnerBlock]]]("blocks").getOrElse(Map.empty) + val transactions = config.as[Option[Map[ByteStr, RideRunnerTransaction]]]("transactions").getOrElse(Map.empty) + RideRunnerBlockchainState(height, features, accounts, assets, blocks, transactions) + } +} diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 5467cbaa03..a7a9098395 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -19,7 +19,7 @@ import com.wavesplatform.transaction.transfer.TransferTransactionLike import com.wavesplatform.transaction.{TransactionFactory, TxNonNegativeAmount, TxValidationError} import com.wavesplatform.utils.byteArrayFromString import net.ceedubs.ficus.Ficus.* -import pureconfig._ +import pureconfig.* import pureconfig.generic.auto.* import net.ceedubs.ficus.readers.{ArbitraryTypeReader, ValueReader} import pureconfig.error.CannotConvert @@ -48,10 +48,12 @@ object RideRunnerInputParser extends ArbitraryTypeReader { val evaluateScriptComplexityLimit = ConfigSource.fromConfig(config).at("evaluateScriptComplexityLimit").load[Int].getOrElse(Int.MaxValue) val maxTxErrorLogSize = ConfigSource.fromConfig(config).at("maxTxErrorLogSize").load[Int].getOrElse(1024) - // val state = RideRunnerBlockchainState.fromConfig(config.getConfig("state")) - val state = config.as[RideRunnerBlockchainState]("state") - val postProcessing = ConfigSource.fromConfig(config).at("postProcessing").load[List[RideRunnerPostProcessingMethod]].getOrElse(List.empty) - val test = Try(jsValueFromConfig[JsValue](config, "test")).map(RideRunnerTest.apply).toOption + val state = RideRunnerBlockchainState.fromConfig(config.getConfig("state")) + val postProcessing = ConfigSource.fromConfig(config).at("postProcessing").load[List[RideRunnerPostProcessingMethod]].getOrElse(List.empty) + // val test = Try(jsValueFromConfig[JsValue](config, "test")).map(RideRunnerTest.apply).toOption // pureconfig + + // ficus + val test = config.as[Option[RideRunnerTest]]("test") RideRunnerInput( address = address, @@ -147,6 +149,34 @@ object RideRunnerInputParser extends ArbitraryTypeReader { else x.asLeft } + implicit val srcOrCompiledScriptConfigReader: ConfigReader[SrcOrCompiledScript] = ConfigReader[String].map { x => + if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail.asRight + else x.asLeft + } + + implicit val accountConfigReader: ConfigReader[RideRunnerAccount] = ConfigReader.fromCursor(cur => + for { + objCur <- cur.asObjectCursor + assetBalances <- ConfigReader[Option[Map[IssuedAsset, TxNonNegativeAmount]]] + .from(objCur.atKeyOrUndefined("assetBalances")) + .map(_.getOrElse(Map.empty)) + regularBalance <- ConfigReader[Option[TxNonNegativeAmount]].from(objCur.atKeyOrUndefined("regularBalance")) + leasing <- ConfigReader[Option[RideRunnerLeaseBalance]].from(objCur.atKeyOrUndefined("leasing")) + generatingBalance <- ConfigReader[Option[TxNonNegativeAmount]].from(objCur.atKeyOrUndefined("generatingBalance")) + // data <- ConfigReader[Option[Map[String, RideRunnerDataEntry]]].from(objCur.atKeyOrUndefined("data")) // TODO: fix + // aliases <- ConfigReader[Option[List[Alias]]].from(objCur.atKeyOrUndefined("aliases")).map(_.getOrElse(Nil)) // TODO: fix + scriptInfo <- ConfigReader[Option[RideRunnerScriptInfo]].from(objCur.atKeyOrUndefined("scriptInfo")) + } yield RideRunnerAccount( + assetBalances = assetBalances, + regularBalance = regularBalance, + leasing = leasing, + generatingBalance = generatingBalance, + // data = data, + // aliases = aliases, + scriptInfo = scriptInfo + ) + ) + implicit val addressValueReader: ValueReader[Address] = ValueReader[String].map(Address.fromString(_).getOrFail) implicit val addressConfigReader: ConfigReader[Address] = @@ -229,6 +259,18 @@ object RideRunnerInputParser extends ArbitraryTypeReader { RideRunnerScriptInfo(pk.getOrElse(EmptyPublicKey), compiledScript) } + implicit val rideRunnerScriptInfoConfigReader: ConfigReader[RideRunnerScriptInfo] = ConfigReader.fromCursor { cur => + for { + objCur <- cur.asObjectCursor + pk <- ConfigReader[Option[PublicKey]].from(objCur.atKeyOrUndefined("publicKey")).map(_.getOrElse(EmptyPublicKey)) + imports <- ConfigReader[Option[Map[String, String]]].from(objCur.atKeyOrUndefined("imports")).map(_.getOrElse(Map.empty)) + compiledScript <- ConfigReader[SrcOrCompiledScript].from(objCur.atKeyOrUndefined("script")).map { + case Right(x) => x + case Left(src) => ScriptUtil.from(src, imports) + } + } yield RideRunnerScriptInfo(pk, compiledScript) + } + private def byteArrayDefaultUtf8FromString(x: String): Array[Byte] = Try { if (x.startsWith(Base58Prefix)) Base58.decode(x.substring(7)) else if (x.startsWith(Base64.Prefix)) Base64.decode(x) diff --git a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala index dae9677369..73c3e52aaa 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala @@ -368,7 +368,16 @@ func bar () = { ) val actual = RideRunnerInputParser.from(RideRunnerInputParser.prepare(ConfigFactory.parseResources("sample-input.conf"))) - actual shouldMatchTo expected + // --------------- // TODO: delete + actual.state.accounts.get(aliceAddr).map(_.assetBalances) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.assetBalances) + actual.state.accounts.get(aliceAddr).map(_.regularBalance) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.regularBalance) + actual.state.accounts.get(aliceAddr).map(_.leasing) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.leasing) + actual.state.accounts.get(aliceAddr).map(_.generatingBalance) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.generatingBalance) + // actual.state.accounts.get(aliceAddr).map(_.data) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.data) + // actual.state.accounts.get(aliceAddr).map(_.aliases) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.aliases) + actual.state.accounts.get(aliceAddr).map(_.scriptInfo) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.scriptInfo) + // --------------- + // actual shouldMatchTo expected } } From 3a4b343eb69941fd36628d20dfe0b9aabe94787b Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 12 Nov 2024 12:20:42 +0400 Subject: [PATCH 12/31] Fix ConfigReader for Alias --- .../runner/input/RideRunnerInputParser.scala | 20 +++++++++++++++---- .../RideRunnerInputParserTestSuite.scala | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index a7a9098395..0d4a32d773 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -154,7 +154,7 @@ object RideRunnerInputParser extends ArbitraryTypeReader { else x.asLeft } - implicit val accountConfigReader: ConfigReader[RideRunnerAccount] = ConfigReader.fromCursor(cur => + implicit val accountConfigReader: ConfigReader[RideRunnerAccount] = ConfigReader.fromCursor { cur => for { objCur <- cur.asObjectCursor assetBalances <- ConfigReader[Option[Map[IssuedAsset, TxNonNegativeAmount]]] @@ -164,7 +164,7 @@ object RideRunnerInputParser extends ArbitraryTypeReader { leasing <- ConfigReader[Option[RideRunnerLeaseBalance]].from(objCur.atKeyOrUndefined("leasing")) generatingBalance <- ConfigReader[Option[TxNonNegativeAmount]].from(objCur.atKeyOrUndefined("generatingBalance")) // data <- ConfigReader[Option[Map[String, RideRunnerDataEntry]]].from(objCur.atKeyOrUndefined("data")) // TODO: fix - // aliases <- ConfigReader[Option[List[Alias]]].from(objCur.atKeyOrUndefined("aliases")).map(_.getOrElse(Nil)) // TODO: fix + aliases <- ConfigReader[Option[List[Alias]]].from(objCur.atKeyOrUndefined("aliases")).map(_.getOrElse(Nil)) scriptInfo <- ConfigReader[Option[RideRunnerScriptInfo]].from(objCur.atKeyOrUndefined("scriptInfo")) } yield RideRunnerAccount( assetBalances = assetBalances, @@ -172,10 +172,10 @@ object RideRunnerInputParser extends ArbitraryTypeReader { leasing = leasing, generatingBalance = generatingBalance, // data = data, - // aliases = aliases, + aliases = aliases, scriptInfo = scriptInfo ) - ) + } implicit val addressValueReader: ValueReader[Address] = ValueReader[String].map(Address.fromString(_).getOrFail) @@ -194,6 +194,18 @@ object RideRunnerInputParser extends ArbitraryTypeReader { alias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail } + implicit val aliasConfigReader: ConfigReader[Alias] = ConfigReader[String].map { x => + val chainId = AddressScheme.current.chainId + + val separatorNumber = x.count(_ == ':') + val alias = + if (separatorNumber == 2) Alias.fromString(x) + else if (separatorNumber == 1) Alias.createWithChainId(x.substring(x.indexOf(":") + 1), chainId) + else Alias.createWithChainId(x, chainId) + + alias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail + } + implicit val addressOrAliasValueReader: ValueReader[AddressOrAlias] = ValueReader[String].map { x => val chainId = AddressScheme.current.chainId diff --git a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala index 73c3e52aaa..660a834464 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala @@ -374,7 +374,7 @@ func bar () = { actual.state.accounts.get(aliceAddr).map(_.leasing) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.leasing) actual.state.accounts.get(aliceAddr).map(_.generatingBalance) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.generatingBalance) // actual.state.accounts.get(aliceAddr).map(_.data) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.data) - // actual.state.accounts.get(aliceAddr).map(_.aliases) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.aliases) + actual.state.accounts.get(aliceAddr).map(_.aliases) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.aliases) actual.state.accounts.get(aliceAddr).map(_.scriptInfo) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.scriptInfo) // --------------- // actual shouldMatchTo expected From 6b6d66879b334e2263893ae0b3c057154ea11c79 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Tue, 12 Nov 2024 12:36:10 +0400 Subject: [PATCH 13/31] Fix RideRunnerDataEntry config reader --- .../runner/input/RideRunnerInputParser.scala | 31 ++++++++++++------- .../RideRunnerInputParserTestSuite.scala | 11 +------ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 0d4a32d773..617b678263 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -163,18 +163,10 @@ object RideRunnerInputParser extends ArbitraryTypeReader { regularBalance <- ConfigReader[Option[TxNonNegativeAmount]].from(objCur.atKeyOrUndefined("regularBalance")) leasing <- ConfigReader[Option[RideRunnerLeaseBalance]].from(objCur.atKeyOrUndefined("leasing")) generatingBalance <- ConfigReader[Option[TxNonNegativeAmount]].from(objCur.atKeyOrUndefined("generatingBalance")) - // data <- ConfigReader[Option[Map[String, RideRunnerDataEntry]]].from(objCur.atKeyOrUndefined("data")) // TODO: fix - aliases <- ConfigReader[Option[List[Alias]]].from(objCur.atKeyOrUndefined("aliases")).map(_.getOrElse(Nil)) - scriptInfo <- ConfigReader[Option[RideRunnerScriptInfo]].from(objCur.atKeyOrUndefined("scriptInfo")) - } yield RideRunnerAccount( - assetBalances = assetBalances, - regularBalance = regularBalance, - leasing = leasing, - generatingBalance = generatingBalance, - // data = data, - aliases = aliases, - scriptInfo = scriptInfo - ) + data <- ConfigReader[Option[Map[String, RideRunnerDataEntry]]].from(objCur.atKeyOrUndefined("data")) + aliases <- ConfigReader[Option[List[Alias]]].from(objCur.atKeyOrUndefined("aliases")).map(_.getOrElse(Nil)) + scriptInfo <- ConfigReader[Option[RideRunnerScriptInfo]].from(objCur.atKeyOrUndefined("scriptInfo")) + } yield RideRunnerAccount(assetBalances, regularBalance, leasing, generatingBalance, data, aliases, scriptInfo) } implicit val addressValueReader: ValueReader[Address] = ValueReader[String].map(Address.fromString(_).getOrFail) @@ -242,6 +234,21 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } } + implicit val rideRunnerDataEntryConfigReader: ConfigReader[RideRunnerDataEntry] = ConfigReader.fromCursor { cur => + for { + objCur <- cur.asObjectCursor + dataType <- objCur.atKey("type").flatMap(ConfigReader[String].from) + data <- dataType match { + case "integer" => objCur.atKey("value").flatMap(ConfigReader[Long].from).map(IntegerRideRunnerDataEntry.apply) + case "boolean" => objCur.atKey("value").flatMap(ConfigReader[Boolean].from).map(BooleanRideRunnerDataEntry.apply) + case "string" => objCur.atKey("value").flatMap(ConfigReader[String].from).map(StringRideRunnerDataEntry.apply) + case "binary" => + objCur.atKey("value").flatMap(ConfigReader[String].from).map(x => BinaryRideRunnerDataEntry(ByteStr(byteArrayDefaultUtf8FromString(x)))) + case x => fail(s"Expected one of types: integer, boolean, string, binary. Got $x") + } + } yield data + } + implicit val rideRunnerPostProcessingMethodValueReader: ValueReader[RideRunnerPostProcessingMethod] = ValueReader.relative[RideRunnerPostProcessingMethod] { config => config.getString("type") match { diff --git a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala index 660a834464..dae9677369 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala @@ -368,16 +368,7 @@ func bar () = { ) val actual = RideRunnerInputParser.from(RideRunnerInputParser.prepare(ConfigFactory.parseResources("sample-input.conf"))) - // --------------- // TODO: delete - actual.state.accounts.get(aliceAddr).map(_.assetBalances) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.assetBalances) - actual.state.accounts.get(aliceAddr).map(_.regularBalance) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.regularBalance) - actual.state.accounts.get(aliceAddr).map(_.leasing) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.leasing) - actual.state.accounts.get(aliceAddr).map(_.generatingBalance) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.generatingBalance) - // actual.state.accounts.get(aliceAddr).map(_.data) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.data) - actual.state.accounts.get(aliceAddr).map(_.aliases) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.aliases) - actual.state.accounts.get(aliceAddr).map(_.scriptInfo) shouldMatchTo expected.state.accounts.get(aliceAddr).map(_.scriptInfo) - // --------------- - // actual shouldMatchTo expected + actual shouldMatchTo expected } } From 02e169d0683bede34b9c333c5dbf72163c776feb Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Thu, 14 Nov 2024 11:59:06 +0400 Subject: [PATCH 14/31] Remove more ficus code --- .../input/RideRunnerBlockchainState.scala | 15 +-- .../runner/input/RideRunnerInputParser.scala | 113 ++++++++++++++---- .../RideRunnerInputParserTestSuite.scala | 15 ++- 3 files changed, 105 insertions(+), 38 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala index 2fd703be43..30f6575864 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala @@ -20,18 +20,15 @@ case class RideRunnerBlockchainState( object RideRunnerBlockchainState { def fromConfig(config: Config): RideRunnerBlockchainState = { - val height = ConfigSource.fromConfig(config).at("height").load[Int].getOrElse(3296626) - val features = ConfigSource.fromConfig(config).at("features").load[Set[Short]].getOrElse(BlockchainFeatures.implemented) - val accounts = ConfigSource.fromConfig(config).at("accounts").load[Map[Address, RideRunnerAccount]].getOrElse(Map.empty) - // val assets = ConfigSource.fromConfig(config).at("assets").load[Map[IssuedAsset, RideRunnerAsset]].getOrElse(Map.empty) - // val blocks = ConfigSource.fromConfig(config).at("blocks").load[Map[Int, RideRunnerBlock]].getOrElse(Map.empty) + val height = ConfigSource.fromConfig(config).at("height").load[Int].getOrElse(3296626) + val features = ConfigSource.fromConfig(config).at("features").load[Set[Short]].getOrElse(BlockchainFeatures.implemented) + val accounts = ConfigSource.fromConfig(config).at("accounts").load[Map[Address, RideRunnerAccount]].getOrElse(Map.empty) + val assets = ConfigSource.fromConfig(config).at("assets").load[Map[IssuedAsset, RideRunnerAsset]].getOrElse(Map.empty) + val blocks = ConfigSource.fromConfig(config).at("blocks").load[Map[Int, RideRunnerBlock]].getOrElse(Map.empty) // val transactions = ConfigSource.fromConfig(config).at("transactions").load[Map[ByteStr, RideRunnerTransaction]].getOrElse(Map.empty) // ficus // TODO: remove - // val accounts = config.as[Option[Map[Address, RideRunnerAccount]]]("accounts").getOrElse(Map.empty) - val assets = config.as[Option[Map[IssuedAsset, RideRunnerAsset]]]("assets").getOrElse(Map.empty) - val blocks = config.as[Option[Map[Int, RideRunnerBlock]]]("blocks").getOrElse(Map.empty) - val transactions = config.as[Option[Map[ByteStr, RideRunnerTransaction]]]("transactions").getOrElse(Map.empty) + val transactions = config.as[Option[Map[ByteStr, RideRunnerTransaction]]]("transactions").getOrElse(Map.empty) RideRunnerBlockchainState(height, features, accounts, assets, blocks, transactions) } } diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 617b678263..3994882074 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -79,16 +79,30 @@ object RideRunnerInputParser extends ArbitraryTypeReader { key.toIntOption.getOrElse(fail(s"Expected an integer value between ${Int.MinValue} and ${Int.MaxValue}")) } + implicit val intMapKeyConfigReader: MapKeyConfigReader[Int] = { key => + key.toIntOption.getOrElse(fail(s"Expected an integer value between ${Int.MinValue} and ${Int.MaxValue}")) + } + implicit val byteStrMapKeyValueReader: MapKeyValueReader[ByteStr] = byteStrDefaultBase58FromString(_) + implicit val byteStrMapKeyConfigReader: MapKeyConfigReader[ByteStr] = byteStrDefaultBase58FromString(_) + implicit val addressMapKeyValueReader: MapKeyValueReader[Address] = Address.fromString(_).getOrFail + implicit val addressMapKeyConfigReader: MapKeyConfigReader[Address] = Address.fromString(_).getOrFail + implicit val issuedAssetMapKeyValueReader: MapKeyValueReader[IssuedAsset] = IssuedAsset.fromString(_, identity, fail(_)) + implicit val issuedAssetMapKeyConfigReader: MapKeyConfigReader[IssuedAsset] = IssuedAsset.fromString(_, identity, fail(_)) + implicit val optBlockIdMapKeyValueReader: MapKeyValueReader[Option[BlockId]] = { x => if (x.isEmpty) None else byteStrDefaultBase58FromString(x).some } + implicit val optBlockIdMapKeyConfigReader: MapKeyConfigReader[Option[BlockId]] = { x => + if (x.isEmpty) None else byteStrDefaultBase58FromString(x).some + } + implicit val byteValueReader: ValueReader[Byte] = ValueReader[Int].map { x => if (x.isValidByte) x.toByte else fail(s"Expected an integer value between ${Byte.MinValue} and ${Byte.MaxValue}") @@ -103,20 +117,6 @@ object RideRunnerInputParser extends ArbitraryTypeReader { key.toShortOption.getOrElse(fail(s"Expected an integer value between ${Short.MinValue} and ${Short.MaxValue}")) } - implicit val intMapKeyConfigReader: MapKeyConfigReader[Int] = { key => - key.toIntOption.getOrElse(fail(s"Expected an integer value between ${Int.MinValue} and ${Int.MaxValue}")) - } - - implicit val byteStrMapKeyConfigReader: MapKeyConfigReader[ByteStr] = byteStrDefaultBase58FromString(_) - - implicit val addressMapKeyConfigReader: MapKeyConfigReader[Address] = Address.fromString(_).getOrFail - - implicit val issuedAssetMapKeyConfigReader: MapKeyConfigReader[IssuedAsset] = IssuedAsset.fromString(_, identity, fail(_)) - - implicit val optBlockIdMapKeyConfigReader: MapKeyConfigReader[Option[BlockId]] = { x => - if (x.isEmpty) None else byteStrDefaultBase58FromString(x).some - } - implicit val heightValueReader: ValueReader[Height] = ValueReader[Int].map(Height(_)) implicit val stdLibVersionValueReader: ValueReader[StdLibVersion] = ValueReader[Int].map(StdLibVersion.VersionDic.idMap.apply) @@ -134,15 +134,26 @@ object RideRunnerInputParser extends ArbitraryTypeReader { implicit val byteStrValueReader: ValueReader[ByteStr] = byteArrayValueReader.map(ByteStr(_)) + implicit val byteStrConfigReader: ConfigReader[ByteStr] = ConfigReader[String].map(byteStrDefaultBase58FromString) + implicit val stringOrBytesAsByteArratValueReader: ValueReader[StringOrBytesAsByteArray] = ValueReader[String].map { x => StringOrBytesAsByteArray(byteArrayDefaultUtf8FromString(x)) } + implicit val stringOrBytesAsByteArrayConfigReader: ConfigReader[StringOrBytesAsByteArray] = ConfigReader[String].map { x => + StringOrBytesAsByteArray(byteArrayDefaultUtf8FromString(x)) + } + implicit val scriptValueReader: ValueReader[Script] = ValueReader[String].map { x => if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail else ScriptUtil.from(x) } + implicit val scriptConfigReader: ConfigReader[Script] = ConfigReader[String].map { x => + if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail + else ScriptUtil.from(x) + } + type SrcOrCompiledScript = Either[String, Script] implicit val srcOrCompiledScriptValueReader: ValueReader[SrcOrCompiledScript] = ValueReader[String].map { x => if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail.asRight @@ -169,6 +180,39 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } yield RideRunnerAccount(assetBalances, regularBalance, leasing, generatingBalance, data, aliases, scriptInfo) } + implicit val assetConfigReader: ConfigReader[RideRunnerAsset] = ConfigReader.fromCursor { cur => + for { + objCur <- cur.asObjectCursor + issuerPublicKey <- ConfigReader[Option[PublicKey]].from(objCur.atKeyOrUndefined("issuerPublicKey")).map(_.getOrElse(EmptyPublicKey)) + name <- ConfigReader[Option[StringOrBytesAsByteArray]].from(objCur.atKeyOrUndefined("name")).map(_.getOrElse(RideRunnerAsset.DefaultName)) + description <- ConfigReader[Option[StringOrBytesAsByteArray]] + .from(objCur.atKeyOrUndefined("description")) + .map(_.getOrElse(RideRunnerAsset.DefaultDescription)) + decimals <- ConfigReader[Option[Int]].from(objCur.atKeyOrUndefined("decimals")).map(_.getOrElse(8)) + reissuable <- ConfigReader[Option[Boolean]].from(objCur.atKeyOrUndefined("reissuable")).map(_.getOrElse(false)) + quantity <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("quantity")).map(_.getOrElse(9007199254740991L)) + script <- ConfigReader[Option[Script]].from(objCur.atKeyOrUndefined("script")) + minSponsoredAssetFee <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("minSponsoredAssetFee")).map(_.getOrElse(0L)) + } yield RideRunnerAsset(issuerPublicKey, name, description, decimals, reissuable, quantity, script, minSponsoredAssetFee) + } + + implicit val blockConfigReader: ConfigReader[RideRunnerBlock] = ConfigReader.fromCursor { cur => + for { + objCur <- cur.asObjectCursor + // Note: `System.currentTimeMillis()` is a side effect, as well as the default value for the `timestamp` field in case class is. + // It would be a good idea not to use side effects in the default values of case class fields. + timestamp <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("timestamp")).map(_.getOrElse(System.currentTimeMillis())) + baseTarget <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("baseTarget")).map(_.getOrElse(130L)) + generationSignature <- ConfigReader[Option[ByteStr]] + .from(objCur.atKeyOrUndefined("generationSignature")) + .map(_.getOrElse(ByteStr(new Array[Byte](64)))) + generatorPublicKey <- ConfigReader[Option[PublicKey]].from(objCur.atKeyOrUndefined("generatorPublicKey")).map(_.getOrElse(EmptyPublicKey)) + + vrf <- ConfigReader[Option[ByteStr]].from(objCur.atKeyOrUndefined("VRF")) + blockReward <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("blockReward")).map(_.getOrElse(600_000_000L)) + } yield RideRunnerBlock(timestamp, baseTarget, generationSignature, generatorPublicKey, vrf, blockReward) + } + implicit val addressValueReader: ValueReader[Address] = ValueReader[String].map(Address.fromString(_).getOrFail) implicit val addressConfigReader: ConfigReader[Address] = @@ -210,6 +254,18 @@ object RideRunnerInputParser extends ArbitraryTypeReader { addressOrAlias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail } + implicit val addressOrAliasConfigReader: ConfigReader[AddressOrAlias] = ConfigReader[String].map { x => + val chainId = AddressScheme.current.chainId + + val separatorNumber = x.count(_ == ':') + val addressOrAlias = + if (separatorNumber == 2) Alias.fromString(x) + else if (separatorNumber == 1) Alias.createWithChainId(x.substring(x.indexOf(":") + 1), chainId) + else Address.fromString(x) + + addressOrAlias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail + } + implicit val publicKeyValueReader: ValueReader[PublicKey] = ValueReader[ByteStr].map(PublicKey(_)) implicit val publicKeyConfigReader: ConfigReader[PublicKey] = ConfigReader[ByteStr].map(PublicKey(_)) @@ -224,16 +280,6 @@ object RideRunnerInputParser extends ArbitraryTypeReader { .getOrFail } - implicit val rideRunnerDataEntryValueReader: ValueReader[RideRunnerDataEntry] = ValueReader.relative[RideRunnerDataEntry] { config => - config.getString("type") match { - case "integer" => IntegerRideRunnerDataEntry(config.getLong("value")) - case "boolean" => BooleanRideRunnerDataEntry(config.getBoolean("value")) - case "string" => StringRideRunnerDataEntry(config.getString("value")) - case "binary" => BinaryRideRunnerDataEntry(ByteStr(byteArrayDefaultUtf8FromString(config.getString("value")))) - case x => fail(s"Expected one of types: integer, boolean, string, binary. Got $x") - } - } - implicit val rideRunnerDataEntryConfigReader: ConfigReader[RideRunnerDataEntry] = ConfigReader.fromCursor { cur => for { objCur <- cur.asObjectCursor @@ -265,6 +311,25 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } } + implicit val rideRunnerPostProcessingMethodConfigReader: ConfigReader[RideRunnerPostProcessingMethod] = ConfigReader.fromCursor { cur => + for { + objCur <- cur.asObjectCursor + method <- objCur.atKey("type").flatMap(ConfigReader[String].from) + data <- method match { + case "pick" => objCur.atKey("path").flatMap(ConfigReader[String].from).map(RideRunnerPostProcessingMethod.Pick.apply) + case "pickAll" => objCur.atKey("paths").flatMap(ConfigReader[List[String]].from).map(RideRunnerPostProcessingMethod.PickAll.apply) + case "prune" => objCur.atKey("paths").flatMap(ConfigReader[List[String]].from).map(RideRunnerPostProcessingMethod.Prune.apply) + case "regex" => + for { + path <- objCur.atKey("path").flatMap(ConfigReader[String].from) + find <- objCur.atKey("find").flatMap(ConfigReader[String].from) + replace <- objCur.atKey("replace").flatMap(ConfigReader[String].from) + } yield RideRunnerPostProcessingMethod.Regex(path, find, replace) + case x => fail(s"Expected one of types: pick, pickAll, prune. Got $x") + } + } yield data + } + implicit val rideRunnerScriptInfoValueReader: ValueReader[RideRunnerScriptInfo] = ValueReader.relative[RideRunnerScriptInfo] { config => val pk = config.as[Option[PublicKey]]("publicKey") val script = config.as[SrcOrCompiledScript]("script") diff --git a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala index dae9677369..22deb5a433 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala @@ -17,8 +17,10 @@ import net.ceedubs.ficus.Ficus.toFicusConfig import net.ceedubs.ficus.readers.ValueReader import org.scalatest.prop.TableDrivenPropertyChecks import play.api.libs.json.* +import pureconfig.* import java.nio.charset.StandardCharsets +import scala.reflect.ClassTag import scala.util.{Success, Try} class RideRunnerInputParserTestSuite extends BaseTestSuite with TableDrivenPropertyChecks with HasTestAccounts with DiffXInstances { @@ -38,13 +40,13 @@ class RideRunnerInputParserTestSuite extends BaseTestSuite with TableDrivenPrope """ { "foo": 1 } """ -> Json.obj("foo" -> 1) ) ) { (rawContent, expected) => - parseAs[JsValue](rawContent) shouldBe expected + parseAsFicus[JsValue](rawContent) shouldBe expected } } "JsObject" in { - parseAs[JsObject](""" { "foo": 1 } """) shouldBe Json.obj("foo" -> 1) - Try(parseAs[JsObject]("1")).isFailure shouldBe true + parseAsFicus[JsObject](""" { "foo": 1 } """) shouldBe Json.obj("foo" -> 1) + Try(parseAsFicus[JsObject]("1")).isFailure shouldBe true } "StringOrBytesAsByteArray" - { @@ -372,6 +374,9 @@ func bar () = { } } - private def parseQuotedStringAs[T: ValueReader](s: String): T = ConfigFactory.parseString(s"""x = \"\"\"$s\"\"\"""").as[T]("x") - private def parseAs[T: ValueReader](rawContent: String): T = ConfigFactory.parseString(s"""x = $rawContent""").as[T]("x") + private def parseQuotedStringAs[T: ConfigReader: ClassTag](s: String): T = + ConfigSource.fromConfig(ConfigFactory.parseString(s"""x = \"\"\"$s\"\"\"""")).at("x").loadOrThrow[T] + private def parseAs[T: ConfigReader: ClassTag](rawContent: String): T = + ConfigSource.fromConfig(ConfigFactory.parseString(s"""x = $rawContent""")).at("x").loadOrThrow[T] + private def parseAsFicus[T: ValueReader](rawContent: String): T = ConfigFactory.parseString(s"""x = $rawContent""").as[T]("x") } From 7b0f00119e7963eecc4f90134140c2d3a552ef97 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Thu, 14 Nov 2024 12:37:35 +0400 Subject: [PATCH 15/31] Remove more Ficus code --- .../com/wavesplatform/transaction/Asset.scala | 5 ++++ .../input/RideRunnerBlockchainState.scala | 6 +---- .../runner/input/RideRunnerInputParser.scala | 25 +++++++++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/transaction/Asset.scala b/node/src/main/scala/com/wavesplatform/transaction/Asset.scala index 4b2d8dbe32..a917a8d64f 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/Asset.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/Asset.scala @@ -6,6 +6,8 @@ import com.wavesplatform.common.utils.Base58 import com.wavesplatform.transaction.assets.exchange.AssetPair import net.ceedubs.ficus.readers.ValueReader import play.api.libs.json.* +import pureconfig.ConfigReader +import pureconfig.error.CannotConvert import scala.util.Success @@ -54,6 +56,9 @@ object Asset { AssetPair.extractAssetId(cfg getString path).fold(ex => throw new Exception(ex.getMessage), identity) } + implicit val assetConfigReader: ConfigReader[Asset] = + ConfigReader[String].emap(s => AssetPair.extractAssetId(s).fold(ex => Left(CannotConvert(s, "Asset", ex.getMessage)), Right(_))) + def fromString(maybeStr: Option[String]): Asset = { maybeStr.map(x => IssuedAsset(ByteStr.decodeBase58(x).get)).getOrElse(Waves) } diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala index 30f6575864..b82b472611 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala @@ -5,7 +5,6 @@ import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.transaction.Asset.IssuedAsset -import net.ceedubs.ficus.Ficus.* import pureconfig.* import RideRunnerInputParser.* @@ -25,10 +24,7 @@ object RideRunnerBlockchainState { val accounts = ConfigSource.fromConfig(config).at("accounts").load[Map[Address, RideRunnerAccount]].getOrElse(Map.empty) val assets = ConfigSource.fromConfig(config).at("assets").load[Map[IssuedAsset, RideRunnerAsset]].getOrElse(Map.empty) val blocks = ConfigSource.fromConfig(config).at("blocks").load[Map[Int, RideRunnerBlock]].getOrElse(Map.empty) - // val transactions = ConfigSource.fromConfig(config).at("transactions").load[Map[ByteStr, RideRunnerTransaction]].getOrElse(Map.empty) - - // ficus // TODO: remove - val transactions = config.as[Option[Map[ByteStr, RideRunnerTransaction]]]("transactions").getOrElse(Map.empty) + val transactions = ConfigSource.fromConfig(config).at("transactions").load[Map[ByteStr, RideRunnerTransaction]].getOrElse(Map.empty) RideRunnerBlockchainState(height, features, accounts, assets, blocks, transactions) } } diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 3994882074..2190ee83bc 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -14,9 +14,9 @@ import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.script.{Script, ScriptReader} import com.wavesplatform.ride.ScriptUtil import com.wavesplatform.state.Height -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.transfer.TransferTransactionLike -import com.wavesplatform.transaction.{TransactionFactory, TxNonNegativeAmount, TxValidationError} +import com.wavesplatform.transaction.{Asset, TransactionFactory, TxNonNegativeAmount, TxValidationError} import com.wavesplatform.utils.byteArrayFromString import net.ceedubs.ficus.Ficus.* import pureconfig.* @@ -213,6 +213,27 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } yield RideRunnerBlock(timestamp, baseTarget, generationSignature, generatorPublicKey, vrf, blockReward) } + implicit val transactionConfigReader: ConfigReader[RideRunnerTransaction] = ConfigReader.fromCursor { cur => + for { + objCur <- cur.asObjectCursor + amount <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("amount")).map(_.getOrElse(1L)) + assetId <- ConfigReader[Option[Asset]].from(objCur.atKeyOrUndefined("assetId")).map(_.getOrElse(Waves)) + fee <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("fee")).map(_.getOrElse(100_000L)) + feeAssetId <- ConfigReader[Option[Asset]].from(objCur.atKeyOrUndefined("feeAssetId")).map(_.getOrElse(Waves)) + recipient <- objCur.atKey("recipient").flatMap(ConfigReader[AddressOrAlias].from) + senderPublicKey <- ConfigReader[Option[PublicKey]].from(objCur.atKeyOrUndefined("senderPublicKey")).map(_.getOrElse(EmptyPublicKey)) + height <- ConfigReader[Option[Int]].from(objCur.atKeyOrUndefined("height")) + // Note: `System.currentTimeMillis()` is a side effect, as well as the default value for the `timestamp` field in case class is. + // It would be a good idea not to use side effects in the default values of case class fields. + timestamp <- ConfigReader[Option[Long]].from(objCur.atKeyOrUndefined("timestamp")).map(_.getOrElse(System.currentTimeMillis())) + proofs <- ConfigReader[Option[List[StringOrBytesAsByteArray]]].from(objCur.atKeyOrUndefined("proofs")).map(_.getOrElse(Nil)) + version <- ConfigReader[Option[Byte]].from(objCur.atKeyOrUndefined("version")).map(_.getOrElse(3: Byte)) + attachment <- ConfigReader[Option[StringOrBytesAsByteArray]] + .from(objCur.atKeyOrUndefined("attachment")) + .map(_.getOrElse(StringOrBytesAsByteArray(Array.empty[Byte]))) + } yield RideRunnerTransaction(amount, assetId, fee, feeAssetId, recipient, senderPublicKey, height, timestamp, proofs, version, attachment) + } + implicit val addressValueReader: ValueReader[Address] = ValueReader[String].map(Address.fromString(_).getOrFail) implicit val addressConfigReader: ConfigReader[Address] = From e6d23191bf5fb5bb5a26fc3475bb1fbb173914e4 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Thu, 14 Nov 2024 13:04:11 +0400 Subject: [PATCH 16/31] Fix config reader for the `test` field --- .../ride/runner/input/RideRunnerInputParser.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 2190ee83bc..eab576a592 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -50,10 +50,7 @@ object RideRunnerInputParser extends ArbitraryTypeReader { val maxTxErrorLogSize = ConfigSource.fromConfig(config).at("maxTxErrorLogSize").load[Int].getOrElse(1024) val state = RideRunnerBlockchainState.fromConfig(config.getConfig("state")) val postProcessing = ConfigSource.fromConfig(config).at("postProcessing").load[List[RideRunnerPostProcessingMethod]].getOrElse(List.empty) - // val test = Try(jsValueFromConfig[JsValue](config, "test")).map(RideRunnerTest.apply).toOption // pureconfig - - // ficus - val test = config.as[Option[RideRunnerTest]]("test") + val test = Try(jsValueFromConfig[JsValue](config, "test.expected")).map(RideRunnerTest.apply).toOption RideRunnerInput( address = address, From 305d7da603629da395a860396e0c5808e3300098 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Thu, 14 Nov 2024 13:43:23 +0400 Subject: [PATCH 17/31] Remove more Ficus code --- .../runner/input/RideRunnerInputParser.scala | 160 +----------------- .../RideRunnerInputParserTestSuite.scala | 11 +- 2 files changed, 8 insertions(+), 163 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index eab576a592..49d6ed088c 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -2,7 +2,6 @@ package com.wavesplatform.ride.runner.input import cats.syntax.either.* import cats.syntax.option.* -import com.google.protobuf.{ByteString, UnsafeByteOperations} import com.typesafe.config.{Config, ConfigFactory, ConfigRenderOptions} import com.wavesplatform.account.* import com.wavesplatform.account.PublicKeys.EmptyPublicKey @@ -10,26 +9,19 @@ import com.wavesplatform.block.Block.BlockId import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, Base64} import com.wavesplatform.json.JsonManipulations -import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.script.{Script, ScriptReader} import com.wavesplatform.ride.ScriptUtil -import com.wavesplatform.state.Height import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} -import com.wavesplatform.transaction.transfer.TransferTransactionLike -import com.wavesplatform.transaction.{Asset, TransactionFactory, TxNonNegativeAmount, TxValidationError} -import com.wavesplatform.utils.byteArrayFromString -import net.ceedubs.ficus.Ficus.* +import com.wavesplatform.transaction.{Asset, TxNonNegativeAmount, TxValidationError} import pureconfig.* import pureconfig.generic.auto.* -import net.ceedubs.ficus.readers.{ArbitraryTypeReader, ValueReader} import pureconfig.error.CannotConvert import play.api.libs.json.* import java.nio.charset.StandardCharsets -import scala.jdk.CollectionConverters.CollectionHasAsScala import scala.util.Try -object RideRunnerInputParser extends ArbitraryTypeReader { +object RideRunnerInputParser { val Base58Prefix = "base58:" def prepare(config: Config): Config = @@ -50,7 +42,7 @@ object RideRunnerInputParser extends ArbitraryTypeReader { val maxTxErrorLogSize = ConfigSource.fromConfig(config).at("maxTxErrorLogSize").load[Int].getOrElse(1024) val state = RideRunnerBlockchainState.fromConfig(config.getConfig("state")) val postProcessing = ConfigSource.fromConfig(config).at("postProcessing").load[List[RideRunnerPostProcessingMethod]].getOrElse(List.empty) - val test = Try(jsValueFromConfig[JsValue](config, "test.expected")).map(RideRunnerTest.apply).toOption + val test = Try(jsValueFromConfig[JsValue](config, "test.expected")).map(RideRunnerTest.apply).toOption RideRunnerInput( address = address, @@ -68,94 +60,38 @@ object RideRunnerInputParser extends ArbitraryTypeReader { def getChainId(x: Config): Char = ConfigSource.fromConfig(x).at("chainId").load[Char].getOrElse(fail("chainId is not specified or wrong")) - implicit val shortMapKeyValueReader: MapKeyValueReader[Short] = { key => - key.toShortOption.getOrElse(fail(s"Expected an integer value between ${Short.MinValue} and ${Short.MaxValue}")) - } - - implicit val intMapKeyValueReader: MapKeyValueReader[Int] = { key => - key.toIntOption.getOrElse(fail(s"Expected an integer value between ${Int.MinValue} and ${Int.MaxValue}")) - } - implicit val intMapKeyConfigReader: MapKeyConfigReader[Int] = { key => key.toIntOption.getOrElse(fail(s"Expected an integer value between ${Int.MinValue} and ${Int.MaxValue}")) } - implicit val byteStrMapKeyValueReader: MapKeyValueReader[ByteStr] = byteStrDefaultBase58FromString(_) - implicit val byteStrMapKeyConfigReader: MapKeyConfigReader[ByteStr] = byteStrDefaultBase58FromString(_) - implicit val addressMapKeyValueReader: MapKeyValueReader[Address] = Address.fromString(_).getOrFail - implicit val addressMapKeyConfigReader: MapKeyConfigReader[Address] = Address.fromString(_).getOrFail - implicit val issuedAssetMapKeyValueReader: MapKeyValueReader[IssuedAsset] = IssuedAsset.fromString(_, identity, fail(_)) - implicit val issuedAssetMapKeyConfigReader: MapKeyConfigReader[IssuedAsset] = IssuedAsset.fromString(_, identity, fail(_)) - implicit val optBlockIdMapKeyValueReader: MapKeyValueReader[Option[BlockId]] = { x => - if (x.isEmpty) None else byteStrDefaultBase58FromString(x).some - } - implicit val optBlockIdMapKeyConfigReader: MapKeyConfigReader[Option[BlockId]] = { x => if (x.isEmpty) None else byteStrDefaultBase58FromString(x).some } - implicit val byteValueReader: ValueReader[Byte] = ValueReader[Int].map { x => - if (x.isValidByte) x.toByte - else fail(s"Expected an integer value between ${Byte.MinValue} and ${Byte.MaxValue}") - } - - implicit val shortValueReader: ValueReader[Short] = ValueReader[Int].map { x => - if (x.isValidShort) x.toShort - else fail(s"Expected a value between ${Short.MinValue} and ${Short.MaxValue}") - } - implicit val shortMapKeyConfigReader: MapKeyConfigReader[Short] = { key => key.toShortOption.getOrElse(fail(s"Expected an integer value between ${Short.MinValue} and ${Short.MaxValue}")) } - implicit val heightValueReader: ValueReader[Height] = ValueReader[Int].map(Height(_)) - - implicit val stdLibVersionValueReader: ValueReader[StdLibVersion] = ValueReader[Int].map(StdLibVersion.VersionDic.idMap.apply) - - implicit val jsValueValueReader: ValueReader[JsValue] = jsValueReader - implicit val jsObjectValueReader: ValueReader[JsObject] = jsValueReader - - implicit val txNonNegativeAmountValueReader: ValueReader[TxNonNegativeAmount] = ValueReader[Long].map(TxNonNegativeAmount.unsafeFrom) - implicit val txNonNegativeAmountConfigReader: ConfigReader[TxNonNegativeAmount] = ConfigReader[Long].map(TxNonNegativeAmount.unsafeFrom) - implicit val byteArrayValueReader: ValueReader[Array[Byte]] = ValueReader[String].map(byteArrayFromString(_, identity, fail(_))) - - implicit val byteStringValueReader: ValueReader[ByteString] = byteArrayValueReader.map(UnsafeByteOperations.unsafeWrap) - - implicit val byteStrValueReader: ValueReader[ByteStr] = byteArrayValueReader.map(ByteStr(_)) - implicit val byteStrConfigReader: ConfigReader[ByteStr] = ConfigReader[String].map(byteStrDefaultBase58FromString) - implicit val stringOrBytesAsByteArratValueReader: ValueReader[StringOrBytesAsByteArray] = ValueReader[String].map { x => - StringOrBytesAsByteArray(byteArrayDefaultUtf8FromString(x)) - } - implicit val stringOrBytesAsByteArrayConfigReader: ConfigReader[StringOrBytesAsByteArray] = ConfigReader[String].map { x => StringOrBytesAsByteArray(byteArrayDefaultUtf8FromString(x)) } - implicit val scriptValueReader: ValueReader[Script] = ValueReader[String].map { x => - if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail - else ScriptUtil.from(x) - } - implicit val scriptConfigReader: ConfigReader[Script] = ConfigReader[String].map { x => if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail else ScriptUtil.from(x) } type SrcOrCompiledScript = Either[String, Script] - implicit val srcOrCompiledScriptValueReader: ValueReader[SrcOrCompiledScript] = ValueReader[String].map { x => - if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail.asRight - else x.asLeft - } implicit val srcOrCompiledScriptConfigReader: ConfigReader[SrcOrCompiledScript] = ConfigReader[String].map { x => if (x.startsWith(Base64.Prefix)) ScriptReader.fromBytes(Base64.decode(x)).getOrFail.asRight @@ -231,23 +167,9 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } yield RideRunnerTransaction(amount, assetId, fee, feeAssetId, recipient, senderPublicKey, height, timestamp, proofs, version, attachment) } - implicit val addressValueReader: ValueReader[Address] = ValueReader[String].map(Address.fromString(_).getOrFail) - implicit val addressConfigReader: ConfigReader[Address] = ConfigReader.fromString(s => Address.fromString(s).left.map(_ => CannotConvert(s, "Address", "invalid address"))) - implicit val aliasValueReader: ValueReader[Alias] = ValueReader[String].map { x => - val chainId = AddressScheme.current.chainId - - val separatorNumber = x.count(_ == ':') - val alias = - if (separatorNumber == 2) Alias.fromString(x) - else if (separatorNumber == 1) Alias.createWithChainId(x.substring(x.indexOf(":") + 1), chainId) - else Alias.createWithChainId(x, chainId) - - alias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail - } - implicit val aliasConfigReader: ConfigReader[Alias] = ConfigReader[String].map { x => val chainId = AddressScheme.current.chainId @@ -260,18 +182,6 @@ object RideRunnerInputParser extends ArbitraryTypeReader { alias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail } - implicit val addressOrAliasValueReader: ValueReader[AddressOrAlias] = ValueReader[String].map { x => - val chainId = AddressScheme.current.chainId - - val separatorNumber = x.count(_ == ':') - val addressOrAlias = - if (separatorNumber == 2) Alias.fromString(x) - else if (separatorNumber == 1) Alias.createWithChainId(x.substring(x.indexOf(":") + 1), chainId) - else Address.fromString(x) - - addressOrAlias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail - } - implicit val addressOrAliasConfigReader: ConfigReader[AddressOrAlias] = ConfigReader[String].map { x => val chainId = AddressScheme.current.chainId @@ -284,20 +194,8 @@ object RideRunnerInputParser extends ArbitraryTypeReader { addressOrAlias.flatMap { x => Either.cond(x.chainId == chainId, x, TxValidationError.WrongChain(chainId, x.chainId)) }.getOrFail } - implicit val publicKeyValueReader: ValueReader[PublicKey] = ValueReader[ByteStr].map(PublicKey(_)) - implicit val publicKeyConfigReader: ConfigReader[PublicKey] = ConfigReader[ByteStr].map(PublicKey(_)) - implicit val transferTransactionLikeValueReader: ValueReader[TransferTransactionLike] = jsObjectValueReader.map { js => - TransactionFactory - .fromSignedRequest(js) - .flatMap { - case tx: TransferTransactionLike => Right(tx) - case _ => Left(TxValidationError.UnsupportedTransactionType) - } - .getOrFail - } - implicit val rideRunnerDataEntryConfigReader: ConfigReader[RideRunnerDataEntry] = ConfigReader.fromCursor { cur => for { objCur <- cur.asObjectCursor @@ -313,22 +211,6 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } yield data } - implicit val rideRunnerPostProcessingMethodValueReader: ValueReader[RideRunnerPostProcessingMethod] = - ValueReader.relative[RideRunnerPostProcessingMethod] { config => - config.getString("type") match { - case "pick" => RideRunnerPostProcessingMethod.Pick(config.getString("path")) - case "pickAll" => RideRunnerPostProcessingMethod.PickAll(config.getStringList("paths").asScala.toList) - case "prune" => RideRunnerPostProcessingMethod.Prune(config.getStringList("paths").asScala.toList) - case "regex" => - RideRunnerPostProcessingMethod.Regex( - path = config.getString("path"), - find = config.getString("find"), - replace = config.getString("replace") - ) - case x => fail(s"Expected one of types: pick, pickAll, prune. Got $x") - } - } - implicit val rideRunnerPostProcessingMethodConfigReader: ConfigReader[RideRunnerPostProcessingMethod] = ConfigReader.fromCursor { cur => for { objCur <- cur.asObjectCursor @@ -348,19 +230,6 @@ object RideRunnerInputParser extends ArbitraryTypeReader { } yield data } - implicit val rideRunnerScriptInfoValueReader: ValueReader[RideRunnerScriptInfo] = ValueReader.relative[RideRunnerScriptInfo] { config => - val pk = config.as[Option[PublicKey]]("publicKey") - val script = config.as[SrcOrCompiledScript]("script") - val imports = config.as[Option[Map[String, String]]]("imports") - - val compiledScript = script match { - case Right(x) => x - case Left(src) => ScriptUtil.from(src, imports.getOrElse(Map.empty)) - } - - RideRunnerScriptInfo(pk.getOrElse(EmptyPublicKey), compiledScript) - } - implicit val rideRunnerScriptInfoConfigReader: ConfigReader[RideRunnerScriptInfo] = ConfigReader.fromCursor { cur => for { objCur <- cur.asObjectCursor @@ -387,20 +256,6 @@ object RideRunnerInputParser extends ArbitraryTypeReader { else Base58.tryDecodeWithLimit(x).fold(e => fail(s"Error parsing base58: ${e.getMessage}"), identity) } - private def jsValueReader[T: Reads]: ValueReader[T] = { (config: Config, path: String) => - // config.getObject(path) doesn't work for primitive values. - // atPath("x") allows a consistent rendering for all types of content at specified path. - val fixedPath = if (path == "") "x" else s"x.$path" - val jsonStr = config.atPath("x").root().render(ConfigRenderOptions.concise()) - JsonManipulations - .pick(Json.parse(jsonStr), fixedPath) - .getOrElse(fail(s"Expected a value at $path")) - .validate[T] match { - case JsSuccess(value, _) => value - case JsError(errors) => fail(s"Can't parse: ${errors.mkString("\n")}") - } - } - def jsValueFromConfig[T: Reads](config: Config, path: String): T = { val fixedPath = if (path == "") "x" else s"x.$path" val jsonStr = config.atPath("x").root().render(ConfigRenderOptions.concise()) @@ -427,13 +282,4 @@ object RideRunnerInputParser extends ArbitraryTypeReader { trait MapKeyConfigReader[T] { def readKey(key: String): T } - - implicit def arbitraryKeyMapValueReader[K, V: ValueReader](implicit kReader: MapKeyValueReader[K]): ValueReader[Map[K, V]] = - ValueReader[Map[String, V]].map { xs => - xs.map { case (k, v) => kReader.readKey(k) -> v } - } - - trait MapKeyValueReader[T] { - def readKey(key: String): T - } } diff --git a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala index 22deb5a433..7e24f3a35b 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala @@ -13,8 +13,6 @@ import com.wavesplatform.ride.{DiffXInstances, ScriptUtil} import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.TxNonNegativeAmount import com.wavesplatform.{BaseTestSuite, HasTestAccounts} -import net.ceedubs.ficus.Ficus.toFicusConfig -import net.ceedubs.ficus.readers.ValueReader import org.scalatest.prop.TableDrivenPropertyChecks import play.api.libs.json.* import pureconfig.* @@ -40,13 +38,13 @@ class RideRunnerInputParserTestSuite extends BaseTestSuite with TableDrivenPrope """ { "foo": 1 } """ -> Json.obj("foo" -> 1) ) ) { (rawContent, expected) => - parseAsFicus[JsValue](rawContent) shouldBe expected + parseValue[JsValue](rawContent) shouldBe expected } } "JsObject" in { - parseAsFicus[JsObject](""" { "foo": 1 } """) shouldBe Json.obj("foo" -> 1) - Try(parseAsFicus[JsObject]("1")).isFailure shouldBe true + parseValue[JsObject](""" { "foo": 1 } """) shouldBe Json.obj("foo" -> 1) + Try(parseValue[JsObject]("1")).isFailure shouldBe true } "StringOrBytesAsByteArray" - { @@ -378,5 +376,6 @@ func bar () = { ConfigSource.fromConfig(ConfigFactory.parseString(s"""x = \"\"\"$s\"\"\"""")).at("x").loadOrThrow[T] private def parseAs[T: ConfigReader: ClassTag](rawContent: String): T = ConfigSource.fromConfig(ConfigFactory.parseString(s"""x = $rawContent""")).at("x").loadOrThrow[T] - private def parseAsFicus[T: ValueReader](rawContent: String): T = ConfigFactory.parseString(s"""x = $rawContent""").as[T]("x") + private def parseValue[T: Reads: ClassTag](rawContent: String): T = + jsValueFromConfig(ConfigFactory.parseString(s"""x = $rawContent"""), "x") } From aa8f5a34633ebe4c4fdbcf41f5917f4a25ba852e Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Thu, 14 Nov 2024 13:54:07 +0400 Subject: [PATCH 18/31] Remove more Ficus code --- .../src/main/scala/com/wavesplatform/transaction/Asset.scala | 5 ----- .../transaction/assets/exchange/AssetPair.scala | 3 --- 2 files changed, 8 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/transaction/Asset.scala b/node/src/main/scala/com/wavesplatform/transaction/Asset.scala index a917a8d64f..d33b80456a 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/Asset.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/Asset.scala @@ -4,7 +4,6 @@ import com.google.common.collect.Interners import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.Base58 import com.wavesplatform.transaction.assets.exchange.AssetPair -import net.ceedubs.ficus.readers.ValueReader import play.api.libs.json.* import pureconfig.ConfigReader import pureconfig.error.CannotConvert @@ -52,10 +51,6 @@ object Asset { implicit val assetIdJsonFormat: Format[Asset] = Format(assetIdReads, assetIdWrites) } - implicit val assetReader: ValueReader[Asset] = { (cfg, path) => - AssetPair.extractAssetId(cfg getString path).fold(ex => throw new Exception(ex.getMessage), identity) - } - implicit val assetConfigReader: ConfigReader[Asset] = ConfigReader[String].emap(s => AssetPair.extractAssetId(s).fold(ex => Left(CannotConvert(s, "Asset", ex.getMessage)), Right(_))) diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/AssetPair.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/AssetPair.scala index 27ec45bd67..99c8c4d9b1 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/AssetPair.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/AssetPair.scala @@ -6,7 +6,6 @@ import com.wavesplatform.serialization.Deser import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves, WavesName} import com.wavesplatform.transaction.assets.exchange.Validation.booleanOperators -import net.ceedubs.ficus.readers.ValueReader import play.api.libs.json.{JsObject, Json} import scala.util.{Failure, Success, Try} @@ -66,6 +65,4 @@ object AssetPair { case Array(amtAssetStr, prcAssetStr) => AssetPair.createAssetPair(amtAssetStr, prcAssetStr) case xs => Failure(new Exception(s"$s (incorrect assets count, expected 2 but got ${xs.size}: ${xs.mkString(", ")})")) } - - implicit val assetPairReader: ValueReader[AssetPair] = (cfg, path) => fromString(cfg.getString(path)).get } From 2f1a82a878de15122e093a5a76f80f94283aea2c Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 10:32:28 +0400 Subject: [PATCH 19/31] Simplify the pureconfig code --- .../input/RideRunnerBlockchainState.scala | 15 --------- .../runner/input/RideRunnerInputParser.scala | 33 ++++++++++++------- .../RideRunnerInputParserTestSuite.scala | 8 ++--- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala index b82b472611..fdb1c575ff 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerBlockchainState.scala @@ -1,12 +1,9 @@ package com.wavesplatform.ride.runner.input -import com.typesafe.config.Config import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.transaction.Asset.IssuedAsset -import pureconfig.* -import RideRunnerInputParser.* case class RideRunnerBlockchainState( height: Int = 3296626, @@ -16,15 +13,3 @@ case class RideRunnerBlockchainState( blocks: Map[Int, RideRunnerBlock] = Map.empty, transactions: Map[ByteStr, RideRunnerTransaction] = Map.empty ) - -object RideRunnerBlockchainState { - def fromConfig(config: Config): RideRunnerBlockchainState = { - val height = ConfigSource.fromConfig(config).at("height").load[Int].getOrElse(3296626) - val features = ConfigSource.fromConfig(config).at("features").load[Set[Short]].getOrElse(BlockchainFeatures.implemented) - val accounts = ConfigSource.fromConfig(config).at("accounts").load[Map[Address, RideRunnerAccount]].getOrElse(Map.empty) - val assets = ConfigSource.fromConfig(config).at("assets").load[Map[IssuedAsset, RideRunnerAsset]].getOrElse(Map.empty) - val blocks = ConfigSource.fromConfig(config).at("blocks").load[Map[Int, RideRunnerBlock]].getOrElse(Map.empty) - val transactions = ConfigSource.fromConfig(config).at("transactions").load[Map[ByteStr, RideRunnerTransaction]].getOrElse(Map.empty) - RideRunnerBlockchainState(height, features, accounts, assets, blocks, transactions) - } -} diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 49d6ed088c..31d982776a 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -33,16 +33,16 @@ object RideRunnerInputParser { */ def from(config: Config): RideRunnerInput = { val address = ConfigSource.fromConfig(config).at("address").loadOrThrow[Address] - val request = jsValueFromConfig[JsObject](config, "request") + val request = ConfigSource.fromConfig(config).at("request").loadOrThrow[JsObject] val chainId = getChainId(config) val intAsString = ConfigSource.fromConfig(config).at("intAsString").load[Boolean].getOrElse(false) val trace = ConfigSource.fromConfig(config).at("trace").load[Boolean].getOrElse(false) val evaluateScriptComplexityLimit = ConfigSource.fromConfig(config).at("evaluateScriptComplexityLimit").load[Int].getOrElse(Int.MaxValue) val maxTxErrorLogSize = ConfigSource.fromConfig(config).at("maxTxErrorLogSize").load[Int].getOrElse(1024) - val state = RideRunnerBlockchainState.fromConfig(config.getConfig("state")) + val state = ConfigSource.fromConfig(config).at("state").loadOrThrow[RideRunnerBlockchainState] val postProcessing = ConfigSource.fromConfig(config).at("postProcessing").load[List[RideRunnerPostProcessingMethod]].getOrElse(List.empty) - val test = Try(jsValueFromConfig[JsValue](config, "test.expected")).map(RideRunnerTest.apply).toOption + val test = ConfigSource.fromConfig(config).at("test.expected").load[JsValue].map(RideRunnerTest.apply).toOption RideRunnerInput( address = address, @@ -256,15 +256,24 @@ object RideRunnerInputParser { else Base58.tryDecodeWithLimit(x).fold(e => fail(s"Error parsing base58: ${e.getMessage}"), identity) } - def jsValueFromConfig[T: Reads](config: Config, path: String): T = { - val fixedPath = if (path == "") "x" else s"x.$path" - val jsonStr = config.atPath("x").root().render(ConfigRenderOptions.concise()) - JsonManipulations - .pick(Json.parse(jsonStr), fixedPath) - .getOrElse(fail(s"Expected a value at $path")) - .validate[T] match { - case JsSuccess(value, _) => value - case JsError(errors) => fail(s"Can't parse: ${errors.mkString("\n")}") + implicit val jsObjectConfigReader: ConfigReader[JsObject] = playJsonConfigReader + + implicit val jsValueConfigReader: ConfigReader[JsValue] = playJsonConfigReader + + private def playJsonConfigReader[T: Reads]: ConfigReader[T] = ConfigReader.fromCursor { cur => + for { + configValue <- cur.asConfigValue + stubKey = "stubKey" + config = ConfigFactory.empty().withValue(stubKey, configValue) + } yield { + val jsonStr = config.root().render(ConfigRenderOptions.concise()) + JsonManipulations + .pick(Json.parse(jsonStr), stubKey) + .getOrElse(fail(s"Expected a value")) + .validate[T] match { + case JsSuccess(value, _) => value + case JsError(errors) => fail(s"Can't parse: ${errors.mkString("\n")}") + } } } diff --git a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala index 7e24f3a35b..8893c0caf5 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala @@ -38,13 +38,13 @@ class RideRunnerInputParserTestSuite extends BaseTestSuite with TableDrivenPrope """ { "foo": 1 } """ -> Json.obj("foo" -> 1) ) ) { (rawContent, expected) => - parseValue[JsValue](rawContent) shouldBe expected + parseAs[JsValue](rawContent) shouldBe expected } } "JsObject" in { - parseValue[JsObject](""" { "foo": 1 } """) shouldBe Json.obj("foo" -> 1) - Try(parseValue[JsObject]("1")).isFailure shouldBe true + parseAs[JsObject](""" { "foo": 1 } """) shouldBe Json.obj("foo" -> 1) + Try(parseAs[JsObject]("1")).isFailure shouldBe true } "StringOrBytesAsByteArray" - { @@ -376,6 +376,4 @@ func bar () = { ConfigSource.fromConfig(ConfigFactory.parseString(s"""x = \"\"\"$s\"\"\"""")).at("x").loadOrThrow[T] private def parseAs[T: ConfigReader: ClassTag](rawContent: String): T = ConfigSource.fromConfig(ConfigFactory.parseString(s"""x = $rawContent""")).at("x").loadOrThrow[T] - private def parseValue[T: Reads: ClassTag](rawContent: String): T = - jsValueFromConfig(ConfigFactory.parseString(s"""x = $rawContent"""), "x") } From 00077440e13a4411685acd7287810f8f6497a9f9 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 11:11:28 +0400 Subject: [PATCH 20/31] Remove more ficus from the ride runner --- .../settings/RideRunnerGlobalSettings.scala | 7 ++-- .../runner/input/PureconfigImplicits.scala | 38 +++++++++++++++++++ .../runner/input/RideRunnerInputParser.scala | 29 +------------- .../RideRunnerInputParserTestSuite.scala | 1 + 4 files changed, 45 insertions(+), 30 deletions(-) create mode 100644 ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/RideRunnerGlobalSettings.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/RideRunnerGlobalSettings.scala index f6051c6ac7..3adf204580 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/RideRunnerGlobalSettings.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/RideRunnerGlobalSettings.scala @@ -7,8 +7,9 @@ import com.wavesplatform.ride.runner.caches.mem.MemBlockchainDataCache import com.wavesplatform.ride.runner.entrypoints.{Heights, WavesRideRunnerCompareService} import com.wavesplatform.ride.runner.requests.DefaultRequestService import com.wavesplatform.settings.* -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* +import com.wavesplatform.ride.runner.input.PureconfigImplicits.* +import pureconfig.* +import pureconfig.generic.auto.* import scala.concurrent.duration.DurationInt @@ -50,5 +51,5 @@ case class RideRunnerGlobalSettings( } object RideRunnerGlobalSettings { - def fromRootConfig(config: Config): RideRunnerGlobalSettings = config.getConfig("waves").as[RideRunnerGlobalSettings] + def fromRootConfig(config: Config): RideRunnerGlobalSettings = ConfigSource.fromConfig(config).at("waves").loadOrThrow[RideRunnerGlobalSettings] } diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala new file mode 100644 index 0000000000..9ee5cf2420 --- /dev/null +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala @@ -0,0 +1,38 @@ +package com.wavesplatform.ride.runner.input + +import com.typesafe.config.{ConfigFactory, ConfigRenderOptions} +import com.wavesplatform.account.Address +import com.wavesplatform.json.JsonManipulations +import play.api.libs.json.{JsError, JsObject, JsSuccess, JsValue, Json, Reads} +import pureconfig.ConfigReader +import pureconfig.error.CannotConvert + +object PureconfigImplicits { + + implicit val jsObjectConfigReader: ConfigReader[JsObject] = playJsonConfigReader + + implicit val jsValueConfigReader: ConfigReader[JsValue] = playJsonConfigReader + + private def playJsonConfigReader[T: Reads]: ConfigReader[T] = ConfigReader.fromCursor { cur => + for { + configValue <- cur.asConfigValue + stubKey = "stubKey" + config = ConfigFactory.empty().withValue(stubKey, configValue) + } yield { + val jsonStr = config.root().render(ConfigRenderOptions.concise()) + JsonManipulations + .pick(Json.parse(jsonStr), stubKey) + .getOrElse(fail(s"Expected a value")) + .validate[T] match { + case JsSuccess(value, _) => value + case JsError(errors) => fail(s"Can't parse: ${errors.mkString("\n")}") + } + } + } + + + implicit val addressConfigReader: ConfigReader[Address] = + ConfigReader.fromString(s => Address.fromString(s).left.map(_ => CannotConvert(s, "Address", "invalid address"))) + + private def fail(message: String, cause: Throwable = null) = throw new IllegalArgumentException(message, cause) +} diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala index 31d982776a..da1689d530 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParser.scala @@ -2,21 +2,20 @@ package com.wavesplatform.ride.runner.input import cats.syntax.either.* import cats.syntax.option.* -import com.typesafe.config.{Config, ConfigFactory, ConfigRenderOptions} +import com.typesafe.config.{Config, ConfigFactory} import com.wavesplatform.account.* import com.wavesplatform.account.PublicKeys.EmptyPublicKey import com.wavesplatform.block.Block.BlockId import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, Base64} -import com.wavesplatform.json.JsonManipulations import com.wavesplatform.lang.script.{Script, ScriptReader} import com.wavesplatform.ride.ScriptUtil import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.{Asset, TxNonNegativeAmount, TxValidationError} import pureconfig.* import pureconfig.generic.auto.* -import pureconfig.error.CannotConvert import play.api.libs.json.* +import com.wavesplatform.ride.runner.input.PureconfigImplicits.* import java.nio.charset.StandardCharsets import scala.util.Try @@ -167,9 +166,6 @@ object RideRunnerInputParser { } yield RideRunnerTransaction(amount, assetId, fee, feeAssetId, recipient, senderPublicKey, height, timestamp, proofs, version, attachment) } - implicit val addressConfigReader: ConfigReader[Address] = - ConfigReader.fromString(s => Address.fromString(s).left.map(_ => CannotConvert(s, "Address", "invalid address"))) - implicit val aliasConfigReader: ConfigReader[Alias] = ConfigReader[String].map { x => val chainId = AddressScheme.current.chainId @@ -256,27 +252,6 @@ object RideRunnerInputParser { else Base58.tryDecodeWithLimit(x).fold(e => fail(s"Error parsing base58: ${e.getMessage}"), identity) } - implicit val jsObjectConfigReader: ConfigReader[JsObject] = playJsonConfigReader - - implicit val jsValueConfigReader: ConfigReader[JsValue] = playJsonConfigReader - - private def playJsonConfigReader[T: Reads]: ConfigReader[T] = ConfigReader.fromCursor { cur => - for { - configValue <- cur.asConfigValue - stubKey = "stubKey" - config = ConfigFactory.empty().withValue(stubKey, configValue) - } yield { - val jsonStr = config.root().render(ConfigRenderOptions.concise()) - JsonManipulations - .pick(Json.parse(jsonStr), stubKey) - .getOrElse(fail(s"Expected a value")) - .validate[T] match { - case JsSuccess(value, _) => value - case JsError(errors) => fail(s"Can't parse: ${errors.mkString("\n")}") - } - } - } - private implicit final class ValidationErrorOps[E, T](private val self: Either[E, T]) extends AnyVal { def getOrFail: T = self.fold(e => fail(e.toString), identity) } diff --git a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala index 8893c0caf5..8ebd68a41f 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/ride/runner/input/RideRunnerInputParserTestSuite.scala @@ -14,6 +14,7 @@ import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.TxNonNegativeAmount import com.wavesplatform.{BaseTestSuite, HasTestAccounts} import org.scalatest.prop.TableDrivenPropertyChecks +import com.wavesplatform.ride.runner.input.PureconfigImplicits.* import play.api.libs.json.* import pureconfig.* From dbe8da93b2143e13af608f9072573dbfc799b322 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 11:24:58 +0400 Subject: [PATCH 21/31] Remove more ficus code --- .../runner/entrypoints/settings/package.scala | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/package.scala diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/package.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/package.scala deleted file mode 100644 index 00c70384f9..0000000000 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/entrypoints/settings/package.scala +++ /dev/null @@ -1,37 +0,0 @@ -package com.wavesplatform.ride.runner.entrypoints - -import com.typesafe.config.* -import com.wavesplatform.account.Address -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.{CollectionReaders, ValueReader} -import play.api.libs.json.{JsObject, Json} - -package object settings { - implicit val configMemorySizeValueReader: ValueReader[ConfigMemorySize] = (config: Config, path: String) => config.getMemorySize(path) - - implicit val testRequestsValueReader: ValueReader[List[(Address, JsObject)]] = CollectionReaders - .traversableReader[List, ConfigValue] - .map { xs => - xs.map { - case xs: ConfigList if xs.unwrapped().size() == 2 => - val key = xs.get(0).unwrapped() match { - case k: String => - Address.fromString(k) match { - case Right(x) => x - case Left(e) => throw new RuntimeException(s"Can't parse '$k' as Address: ${e.toString}") - } - case k => throw new RuntimeException(s"Can't parse as Address: $k") - } - - val strV = xs.get(1).render(ConfigRenderOptions.concise()) - val value = Json.parse(strV) match { - case x: JsObject => x - case x => throw new RuntimeException(s"Can't parse value as JsObject: $x") - } - - key -> value - - case xs => throw new RuntimeException(s"Expected two elements, got: $xs") - } - } -} From 907e71fb5b66a33e355f030c89ac17edf6e551ae Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 11:55:24 +0400 Subject: [PATCH 22/31] Remove more ficus code --- .../generator/TransactionsGeneratorApp.scala | 26 ++++++----- .../settings/BlockchainSettings.scala | 4 -- .../com/wavesplatform/settings/package.scala | 7 --- .../utils/ConfigSettingsValidator.scala | 46 +++++++++++-------- .../runner/input/PureconfigImplicits.scala | 5 +- 5 files changed, 42 insertions(+), 46 deletions(-) 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 50a651b639..6854b83f44 100644 --- a/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala +++ b/node-generator/src/main/scala/com/wavesplatform/generator/TransactionsGeneratorApp.scala @@ -1,17 +1,15 @@ package com.wavesplatform.generator import java.io.File -import java.net.InetSocketAddress +import java.net.{InetSocketAddress, URI} import java.util.concurrent.Executors - -import scala.concurrent._ -import scala.concurrent.duration._ +import scala.concurrent.* +import scala.concurrent.duration.* import scala.util.{Failure, Random, Success} - import cats.implicits.showInterpolator -import com.typesafe.config.ConfigFactory +import com.typesafe.config.{Config, ConfigFactory} import com.wavesplatform.account.AddressScheme -import com.wavesplatform.features.EstimatorProvider._ +import com.wavesplatform.features.EstimatorProvider.* import com.wavesplatform.generator.GeneratorSettings.NodeAddress import com.wavesplatform.generator.Preconditions.{PGenSettings, UniverseHolder} import com.wavesplatform.generator.cli.ScoptImplicits @@ -22,9 +20,9 @@ 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.Ficus.* import net.ceedubs.ficus.readers.{EnumerationReader, NameMapper, ValueReader} -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ +import net.ceedubs.ficus.readers.ArbitraryTypeReader.* import org.asynchttpclient.AsyncHttpClient import org.asynchttpclient.Dsl.asyncHttpClient import org.slf4j.LoggerFactory @@ -32,10 +30,14 @@ 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) + } + // IDEA bugs - implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = com.wavesplatform.settings.inetSocketAddressReader - implicit val readConfigInHyphen: NameMapper = net.ceedubs.ficus.readers.namemappers.implicits.hyphenCase - implicit val httpClient: AsyncHttpClient = asyncHttpClient() + implicit val readConfigInHyphen: NameMapper = net.ceedubs.ficus.readers.namemappers.implicits.hyphenCase + implicit val httpClient: AsyncHttpClient = asyncHttpClient() val log = LoggerFacade(LoggerFactory.getLogger("generator")) diff --git a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala index ddb73fba41..51a84df39a 100644 --- a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala @@ -7,7 +7,6 @@ import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import pureconfig.* import pureconfig.generic.auto.* -import net.ceedubs.ficus.readers.ValueReader import scala.concurrent.duration.* @@ -245,9 +244,6 @@ private[settings] object BlockchainType { } object BlockchainSettings { - implicit val valueReader: ValueReader[BlockchainSettings] = - (cfg: Config, path: String) => fromConfig(cfg.getConfig(path)) - def fromRootConfig(config: Config): BlockchainSettings = fromConfig(config.getConfig("waves.blockchain")) def fromConfig(config: Config): BlockchainSettings = { diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index ce7c7e4a86..b7d96b4f11 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -1,10 +1,8 @@ package com.wavesplatform -import java.net.{InetSocketAddress, URI} import com.typesafe.config.{Config, ConfigFactory} import com.wavesplatform.account.PrivateKey import com.wavesplatform.common.state.ByteStr -import net.ceedubs.ficus.readers.ValueReader import pureconfig.ConfigReader import pureconfig.ConvertHelpers.catchReadError import pureconfig.configurable.genericMapReader @@ -18,11 +16,6 @@ package object settings { ConfigReader.fromString(str => ByteStr.decodeBase58(str).toEither.left.map(e => CannotConvert(str, "ByteStr", e.getMessage))) implicit val preactivatedFeaturesReader: ConfigReader[Map[Short, Int]] = genericMapReader(catchReadError(_.toShort)) - implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = { (config: Config, path: String) => - val uri = new URI(s"my://${config.getString(path)}") - new InetSocketAddress(uri.getHost, uri.getPort) - } - implicit val privateKeyReader: ConfigReader[PrivateKey] = ConfigReader[ByteStr].map(PrivateKey(_)) object SizeInBytes extends TaggedType[Long] diff --git a/node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala b/node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala index 33a3de7608..8db2e7da4a 100644 --- a/node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala +++ b/node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala @@ -1,15 +1,15 @@ package com.wavesplatform.settings.utils import cats.data.{NonEmptyList, Validated, ValidatedNel} -import cats.instances.list._ -import cats.syntax.foldable._ -import cats.syntax.traverse._ -import com.typesafe.config.{Config, ConfigException} +import cats.instances.list.* +import cats.syntax.foldable.* +import cats.syntax.traverse.* +import com.typesafe.config.{Config, ConfigException, ConfigFactory, ConfigValue} import com.wavesplatform.transaction.assets.exchange.AssetPair -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ValueReader +import pureconfig.* -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* +import scala.reflect.ClassTag import scala.util.Try object ConfigSettingsValidator { @@ -46,11 +46,13 @@ class ConfigSettingsValidator(config: Config) { NonEmptyList.one(s"Invalid setting $settingName value: $msg") } - def validate[T: ValueReader](settingName: String, showError: Boolean = false): ErrorsListOr[T] = { - Validated fromTry Try(config.as[T](settingName)) leftMap (ex => createError(settingName, ex.getMessage, showError)) + def validate[T: ConfigReader: ClassTag](settingName: String, showError: Boolean = false): ErrorsListOr[T] = { + Validated fromTry Try(ConfigSource.fromConfig(config).at(settingName).loadOrThrow[T]) leftMap (ex => + createError(settingName, ex.getMessage, showError) + ) } - def validateByPredicate[T: ValueReader](settingName: String)(predicate: T => Boolean, errorMsg: String): ErrorsListOr[T] = { + def validateByPredicate[T: ConfigReader: ClassTag](settingName: String)(predicate: T => Boolean, errorMsg: String): ErrorsListOr[T] = { validate[T](settingName, showError = true).ensure(createError(settingName, errorMsg))(predicate) } @@ -58,21 +60,23 @@ class ConfigSettingsValidator(config: Config) { validateByPredicate[Double](settingName)(p => 0 < p && p <= 100, "required 0 < percent <= 100") } - def validateList[T: ValueReader](settingName: String): ErrorsListOr[List[T]] = { + private def configFromConfigValue(configValue: ConfigValue) = + ConfigFactory.empty().withValue("stubKey", configValue).getConfig("stubKey") + + def validateList[T: ConfigReader: ClassTag](settingName: String): ErrorsListOr[List[T]] = { config .getList(settingName) .asScala .toList .zipWithIndex - .traverse { - case (cfg, index) => - val elemPath = s"$settingName.$index" - Validated fromTry Try(cfg.atPath(elemPath).as[T](elemPath)) leftMap (ex => List(ex.getMessage)) + .traverse { case (cfg, index) => + val elemPath = s"$settingName.$index" + Validated fromTry Try(ConfigSource.fromConfig(configFromConfigValue(cfg)).at(elemPath).loadOrThrow[T]) leftMap (ex => List(ex.getMessage)) } .leftMap(errorsInList => createError(settingName, errorsInList.mkString(", "), showValue = false)) } - def validateMap[K, V: ValueReader](settingName: String)(keyValidator: String => Validated[String, K]): ErrorsListOr[Map[K, V]] = { + def validateMap[K, V: ConfigReader: ClassTag](settingName: String)(keyValidator: String => Validated[String, K]): ErrorsListOr[Map[K, V]] = { config .getConfig(settingName) .root() @@ -82,20 +86,22 @@ class ConfigSettingsValidator(config: Config) { .traverse { entry => val elemPath = s"$settingName.${entry.getKey}" val k = keyValidator(entry.getKey).leftMap(List(_)) - val v = Validated fromTry Try(entry.getValue.atPath(elemPath).as[V](elemPath)) leftMap (ex => List(ex.getMessage)) + val v = Validated fromTry Try(ConfigSource.fromConfig(configFromConfigValue(entry.getValue)).at(elemPath).loadOrThrow[V]) leftMap (ex => + List(ex.getMessage) + ) k.product(v) } .map(_.toMap) .leftMap(errorsInList => createError(settingName, errorsInList.mkString(", "), showValue = false)) } - def validateWithDefault[T: ValueReader](settingName: String, defaultValue: T, showError: Boolean = false): ErrorsListOr[T] = { + def validateWithDefault[T: ConfigReader: ClassTag](settingName: String, defaultValue: T, showError: Boolean = false): ErrorsListOr[T] = { Validated - .fromTry(Try(config.as[T](settingName)).recover { case _: ConfigException.Missing => defaultValue }) + .fromTry(Try(ConfigSource.fromConfig(config).at(settingName).loadOrThrow[T]).recover { case _: ConfigException.Missing => defaultValue }) .leftMap(ex => createError(settingName, ex.getMessage, showError)) } - def validateByPredicateWithDefault[T: ValueReader]( + def validateByPredicateWithDefault[T: ConfigReader: ClassTag]( settingName: String )(predicate: T => Boolean, errorMsg: String, defaultValue: T): ErrorsListOr[T] = { validateWithDefault[T](settingName, defaultValue, showError = true).ensure(createError(settingName, errorMsg))(predicate) diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala index 9ee5cf2420..14bf9ade5c 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/input/PureconfigImplicits.scala @@ -16,9 +16,9 @@ object PureconfigImplicits { private def playJsonConfigReader[T: Reads]: ConfigReader[T] = ConfigReader.fromCursor { cur => for { configValue <- cur.asConfigValue - stubKey = "stubKey" - config = ConfigFactory.empty().withValue(stubKey, configValue) } yield { + val stubKey = "stubKey" + val config = ConfigFactory.empty().withValue(stubKey, configValue) val jsonStr = config.root().render(ConfigRenderOptions.concise()) JsonManipulations .pick(Json.parse(jsonStr), stubKey) @@ -30,7 +30,6 @@ object PureconfigImplicits { } } - implicit val addressConfigReader: ConfigReader[Address] = ConfigReader.fromString(s => Address.fromString(s).left.map(_ => CannotConvert(s, "Address", "invalid address"))) From 01d83c3a81c77f2bff1a1f9e84d1b9913ca95654 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 12:16:33 +0400 Subject: [PATCH 23/31] Remove more ficus code --- .../test/scala/com/wavesplatform/it/BaseTargetChecker.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-it/src/test/scala/com/wavesplatform/it/BaseTargetChecker.scala b/node-it/src/test/scala/com/wavesplatform/it/BaseTargetChecker.scala index 945e1d1439..578ce4b127 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/BaseTargetChecker.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/BaseTargetChecker.scala @@ -12,7 +12,7 @@ import com.wavesplatform.history.StorageFactory import com.wavesplatform.settings.* import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.utils.NTP -import net.ceedubs.ficus.Ficus.* +import pureconfig.ConfigSource object BaseTargetChecker { def main(args: Array[String]): Unit = { @@ -41,7 +41,7 @@ object BaseTargetChecker { blockchainUpdater.processBlock(genesisBlock, genesisBlock.header.generationSignature, None) NodeConfigs.Default.map(_.withFallback(sharedConfig)).collect { - case cfg if cfg.as[Boolean]("waves.miner.enable") => + case cfg if ConfigSource.fromConfig(cfg).at("waves.miner.enable").loadOrThrow[Boolean] => val account = KeyPair.fromSeed(cfg.getString("account-seed")).explicitGet() val address = account.toAddress val balance = blockchainUpdater.balance(address, Waves) From 922af82ec44328b18b3fdf6c96a9077aa0c6d691 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 12:28:13 +0400 Subject: [PATCH 24/31] Move ficus dependency to node-generator --- build.sbt | 12 ++++++++---- project/Dependencies.scala | 1 - 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index f30f343293..3d597af7d6 100644 --- a/build.sbt +++ b/build.sbt @@ -82,10 +82,14 @@ lazy val `node-tests` = project lazy val `grpc-server` = project.dependsOn(node % "compile;runtime->provided", `node-testkit`, `node-tests` % "test->test") -lazy val `ride-runner` = project.dependsOn(node, `grpc-server`, `node-tests` % "test->test") -lazy val `node-it` = project.dependsOn(`repl-jvm`, `grpc-server`, `node-tests` % "test->test") -lazy val `node-generator` = project.dependsOn(node, `node-testkit`, `node-tests` % "compile->test") -lazy val benchmark = project.dependsOn(node, `node-tests` % "test->test") +lazy val `ride-runner` = project.dependsOn(node, `grpc-server`, `node-tests` % "test->test") +lazy val `node-it` = project.dependsOn(`repl-jvm`, `grpc-server`, `node-tests` % "test->test") +lazy val `node-generator` = project + .dependsOn(node, `node-testkit`, `node-tests` % "compile->test") + .settings( + libraryDependencies += "com.iheart" %% "ficus" % "1.5.2" + ) +lazy val benchmark = project.dependsOn(node, `node-tests` % "test->test") lazy val repl = crossProject(JSPlatform, JVMPlatform) .withoutSuffixFor(JVMPlatform) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c73718a67c..96750a0fd8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -108,7 +108,6 @@ object Dependencies { ("org.rudogma" %%% "supertagged" % "2.0-RC2").exclude("org.scala-js", "scalajs-library_2.13"), "commons-net" % "commons-net" % "3.11.1", "commons-io" % "commons-io" % "2.17.0", - "com.iheart" %% "ficus" % "1.5.2", "com.github.pureconfig" %% "pureconfig" % "0.17.7", "net.logstash.logback" % "logstash-logback-encoder" % "8.0" % Runtime, kamonCore, From 389353f36a14e2a68135b3c82e0969ac7df83920 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 13:04:22 +0400 Subject: [PATCH 25/31] Remove redundant Dto --- .../scala/com/wavesplatform/Application.scala | 8 +- .../wavesplatform/network/NetworkServer.scala | 17 +-- .../network/PeerDatabaseImpl.scala | 2 +- .../settings/NetworkSettings.scala | 101 +++++------------- .../BlacklistParallelSpecification.scala | 1 + .../network/BlacklistSpecification.scala | 10 +- .../peer/PeerDatabaseImplSpecification.scala | 1 + .../NetworkSettingsSpecification.scala | 9 +- 8 files changed, 55 insertions(+), 94 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index ab767812d5..b3ac210cfd 100644 --- a/node/src/main/scala/com/wavesplatform/Application.scala +++ b/node/src/main/scala/com/wavesplatform/Application.scala @@ -481,7 +481,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con log.info(s"REST API was bound on ${settings.restAPISettings.bindAddress}:${settings.restAPISettings.port}") } - for (addr <- settings.networkSettings.declaredAddress if settings.networkSettings.uPnPSettings.enable) { + for (addr <- settings.networkSettings.derivedDeclaredAddress if settings.networkSettings.uPnPSettings.enable) { upnp.addPort(addr.getPort) } @@ -501,7 +501,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con log.info("Closing REST API") if (settings.restAPISettings.enable) Try(Await.ready(serverBinding.unbind(), 2.minutes)).failed.map(e => log.error("Failed to unbind REST API port", e)) - for (addr <- settings.networkSettings.declaredAddress if settings.networkSettings.uPnPSettings.enable) upnp.deletePort(addr.getPort) + for (addr <- settings.networkSettings.derivedDeclaredAddress if settings.networkSettings.uPnPSettings.enable) upnp.deletePort(addr.getPort) log.debug("Closing peer database") peerDatabase.close() @@ -604,12 +604,12 @@ object Application extends ScorexLogging { } private[wavesplatform] def loadBlockAt(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl)( - height: Int + height: Int ): Option[(BlockMeta, Seq[(TxMeta, Transaction)])] = loadBlockInfoAt(rdb, blockchainUpdater)(height) private[wavesplatform] def loadBlockInfoAt(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl)( - height: Int + height: Int ): Option[(BlockMeta, Seq[(TxMeta, Transaction)])] = loadBlockMetaAt(rdb.db, blockchainUpdater)(height).map { meta => meta -> blockchainUpdater diff --git a/node/src/main/scala/com/wavesplatform/network/NetworkServer.scala b/node/src/main/scala/com/wavesplatform/network/NetworkServer.scala index fab6019743..f8d9dd26a9 100644 --- a/node/src/main/scala/com/wavesplatform/network/NetworkServer.scala +++ b/node/src/main/scala/com/wavesplatform/network/NetworkServer.scala @@ -39,7 +39,7 @@ object NetworkServer extends ScorexLogging { peerDatabase: PeerDatabase, allChannels: ChannelGroup, peerInfo: ConcurrentHashMap[Channel, PeerInfo], - protocolSpecificPipeline: => Seq[ChannelHandlerAdapter], + protocolSpecificPipeline: => Seq[ChannelHandlerAdapter] ): NetworkServer = { @volatile var shutdownInitiated = false @@ -48,13 +48,13 @@ object NetworkServer extends ScorexLogging { val handshake = Handshake( applicationName, Version.VersionTuple, - networkSettings.nodeName, - networkSettings.nonce, - networkSettings.declaredAddress + networkSettings.derivedNodeName, + networkSettings.derivedNonce, + networkSettings.derivedDeclaredAddress ) val excludedAddresses: Set[InetSocketAddress] = - networkSettings.bindAddress.fold(Set.empty[InetSocketAddress]) { bindAddress => + networkSettings.derivedBindAddress.fold(Set.empty[InetSocketAddress]) { bindAddress => val isLocal = Option(bindAddress.getAddress).exists(_.isAnyLocalAddress) val localAddresses = if (isLocal) { NetworkInterface.getNetworkInterfaces.asScala @@ -62,7 +62,7 @@ object NetworkServer extends ScorexLogging { .toSet } else Set(bindAddress) - localAddresses ++ networkSettings.declaredAddress.toSet + localAddresses ++ networkSettings.derivedDeclaredAddress.toSet } val lengthFieldPrepender = new LengthFieldPrepender(4) @@ -90,7 +90,7 @@ object NetworkServer extends ScorexLogging { ) ++ protocolSpecificPipeline ++ Seq(writeErrorHandler, channelClosedHandler, fatalErrorHandler) - val serverChannel = networkSettings.bindAddress.map { bindAddress => + val serverChannel = networkSettings.derivedBindAddress.map { bindAddress => new ServerBootstrap() .group(bossGroup, workerGroup) .channel(classOf[NioServerSocketChannel]) @@ -203,7 +203,8 @@ object NetworkServer extends ScorexLogging { } def scheduleConnectTask(): Unit = if (!shutdownInitiated) { - val delay = (if (peerConnectionsMap.isEmpty || networkSettings.minConnections.exists(_ > peerConnectionsMap.size())) AverageHandshakePeriod else 5.seconds) + + val delay = (if (peerConnectionsMap.isEmpty || networkSettings.minConnections.exists(_ > peerConnectionsMap.size())) AverageHandshakePeriod + else 5.seconds) + (Random.nextInt(1000) - 500).millis // add some noise so that nodes don't attempt to connect to each other simultaneously workerGroup.schedule(delay) { diff --git a/node/src/main/scala/com/wavesplatform/network/PeerDatabaseImpl.scala b/node/src/main/scala/com/wavesplatform/network/PeerDatabaseImpl.scala index 1330e241fd..5b69b98ae2 100644 --- a/node/src/main/scala/com/wavesplatform/network/PeerDatabaseImpl.scala +++ b/node/src/main/scala/com/wavesplatform/network/PeerDatabaseImpl.scala @@ -49,7 +49,7 @@ class PeerDatabaseImpl(settings: NetworkSettings, ticker: Ticker = Ticker.system override def addCandidate(socketAddress: InetSocketAddress): Boolean = unverifiedPeers.synchronized { val r = !socketAddress.getAddress.isAnyLocalAddress && - !(socketAddress.getAddress.isLoopbackAddress && settings.bindAddress.exists(_.getPort == socketAddress.getPort)) && + !(socketAddress.getAddress.isLoopbackAddress && settings.derivedBindAddress.exists(_.getPort == socketAddress.getPort)) && Option(peersPersistence.getIfPresent(socketAddress)).isEmpty && !unverifiedPeers.contains(socketAddress) if (r) unverifiedPeers.add(socketAddress) diff --git a/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala b/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala index 6c46126cac..db7ffd670c 100644 --- a/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/NetworkSettings.scala @@ -2,9 +2,6 @@ package com.wavesplatform.settings import com.wavesplatform.network.TrafficLogger import com.wavesplatform.utils.* -import pureconfig.* -import pureconfig.generic.auto.* - import java.io.File import java.net.{InetSocketAddress, URI} import scala.concurrent.duration.FiniteDuration @@ -12,7 +9,7 @@ import scala.util.Random case class UPnPSettings(enable: Boolean, gatewayTimeout: FiniteDuration, discoverTimeout: FiniteDuration) -case class NetworkSettingsDto( +case class NetworkSettings( file: Option[File], bindAddress: Option[String], port: Option[Int], @@ -38,79 +35,35 @@ case class NetworkSettingsDto( upnp: UPnPSettings, trafficLogger: TrafficLogger.Settings ) { - def toNetworkSettings: NetworkSettings = { - def randomNonce: Long = { - val base = 1000 - (Random.nextInt(base) + base) * Random.nextInt(base) + Random.nextInt(base) - } - val MaxNodeNameBytesLength = 127 - val declaredAddress1 = declaredAddress.map { address => - val uri = new URI(s"my://$address") - new InetSocketAddress(uri.getHost, uri.getPort) - } - val nonce1 = nonce.getOrElse(randomNonce) - val nodeName1 = nodeName.getOrElse(s"Node-$nonce1") - require(nodeName1.utf8Bytes.length <= MaxNodeNameBytesLength, s"Node name should have length less than $MaxNodeNameBytesLength bytes") - val bindAddress1 = for { - addr <- bindAddress - p <- port - } yield new InetSocketAddress(addr, p) - - NetworkSettings( - file = file, - bindAddress = bindAddress1, - declaredAddress = declaredAddress1, - nodeName = nodeName1, - nonce = nonce1, - knownPeers = knownPeers, - peersDataResidenceTime = peersDataResidenceTime, - blackListResidenceTime = blackListResidenceTime, - breakIdleConnectionsTimeout = breakIdleConnectionsTimeout, - maxInboundConnections = maxInboundConnections, - maxOutboundConnections = maxOutboundConnections, - maxConnectionsPerHost = maxSingleHostConnections, - minConnections = minConnections, - connectionTimeout = connectionTimeout, - maxUnverifiedPeers = maxUnverifiedPeers, - enablePeersExchange = enablePeersExchange, - enableBlacklisting = enableBlacklisting, - peersBroadcastInterval = peersBroadcastInterval, - handshakeTimeout = handshakeTimeout, - suspensionResidenceTime = suspensionResidenceTime, - receivedTxsCacheTimeout = receivedTxsCacheTimeout, - uPnPSettings = upnp, - trafficLogger = trafficLogger - ) + val derivedDeclaredAddress: Option[InetSocketAddress] = declaredAddress.map { address => + val uri = new URI(s"my://$address") + new InetSocketAddress(uri.getHost, uri.getPort) } -} -case class NetworkSettings( - file: Option[File], - bindAddress: Option[InetSocketAddress], - declaredAddress: Option[InetSocketAddress], - nodeName: String, - nonce: Long, - knownPeers: Seq[String], - peersDataResidenceTime: FiniteDuration, - blackListResidenceTime: FiniteDuration, - breakIdleConnectionsTimeout: FiniteDuration, - maxInboundConnections: Int, - maxOutboundConnections: Int, - maxConnectionsPerHost: Int, - minConnections: Option[Int], - connectionTimeout: FiniteDuration, - maxUnverifiedPeers: Int, - enablePeersExchange: Boolean, - enableBlacklisting: Boolean, - peersBroadcastInterval: FiniteDuration, - handshakeTimeout: FiniteDuration, - suspensionResidenceTime: FiniteDuration, - receivedTxsCacheTimeout: FiniteDuration, - uPnPSettings: UPnPSettings, - trafficLogger: TrafficLogger.Settings -) + val derivedNonce: Long = nonce.getOrElse(NetworkSettings.randomNonce) + + val derivedNodeName: String = nodeName.getOrElse(s"Node-$derivedNonce") + require( + derivedNodeName.utf8Bytes.length <= NetworkSettings.MaxNodeNameBytesLength, + s"Node name should have length less than ${NetworkSettings.MaxNodeNameBytesLength} bytes" + ) + + val derivedBindAddress: Option[InetSocketAddress] = for { + addr <- bindAddress + p <- port + } yield new InetSocketAddress(addr, p) + + val maxConnectionsPerHost: Int = maxSingleHostConnections + + val uPnPSettings: UPnPSettings = upnp +} object NetworkSettings { - implicit val configReader: ConfigReader[NetworkSettings] = ConfigReader[NetworkSettingsDto].map(_.toNetworkSettings) + val MaxNodeNameBytesLength = 127 + + def randomNonce: Long = { + val base = 1000 + (Random.nextInt(base) + base) * Random.nextInt(base) + Random.nextInt(base) + } } diff --git a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala index 208c95c4fc..41ab01726b 100644 --- a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistParallelSpecification.scala @@ -5,6 +5,7 @@ import com.wavesplatform.settings.{NetworkSettings, loadConfig} import com.wavesplatform.test.FeatureSpec import org.scalatest.{GivenWhenThen, ParallelTestExecution} import pureconfig.ConfigSource +import pureconfig.generic.auto.* import java.net.{InetAddress, InetSocketAddress} diff --git a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala index eb836d6106..5b61ba6c92 100644 --- a/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/network/BlacklistSpecification.scala @@ -6,6 +6,7 @@ import com.wavesplatform.settings.NetworkSettings import com.wavesplatform.test.FeatureSpec import org.scalatest.GivenWhenThen import pureconfig.ConfigSource +import pureconfig.generic.auto.* import java.net.{InetAddress, InetSocketAddress} @@ -29,9 +30,12 @@ class BlacklistSpecification extends FeatureSpec with GivenWhenThen { Feature("Blacklist") { Scenario("Peer blacklist another peer") { Given("Peer database is empty") - val peerDatabase = new PeerDatabaseImpl(networkSettings, new Ticker { - override def read(): Long = timestamp - }) + val peerDatabase = new PeerDatabaseImpl( + networkSettings, + new Ticker { + override def read(): Long = timestamp + } + ) def isBlacklisted(address: InetSocketAddress) = peerDatabase.isBlacklisted(address.getAddress) diff --git a/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala index f29b5055b6..22665b63d7 100644 --- a/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/network/peer/PeerDatabaseImplSpecification.scala @@ -6,6 +6,7 @@ import com.wavesplatform.network.{PeerDatabase, PeerDatabaseImpl} import com.wavesplatform.settings.NetworkSettings import com.wavesplatform.test.FreeSpec import pureconfig.ConfigSource +import pureconfig.generic.auto.* import java.net.InetSocketAddress import scala.concurrent.duration.* diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala index 8232d3ac3a..220f288265 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala @@ -4,6 +4,7 @@ import java.net.InetSocketAddress import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec import pureconfig.ConfigSource +import pureconfig.generic.auto.* import pureconfig.error.ConfigReaderException import scala.concurrent.duration.* @@ -41,10 +42,10 @@ class NetworkSettingsSpecification extends FlatSpec { |}""".stripMargin)) val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] - networkSettings.bindAddress should be(Some(new InetSocketAddress("127.0.0.1", 6868))) - networkSettings.nodeName should be("default-node-name") - networkSettings.declaredAddress should be(Some(new InetSocketAddress("127.0.0.1", 6868))) - networkSettings.nonce should be(0) + networkSettings.derivedBindAddress should be(Some(new InetSocketAddress("127.0.0.1", 6868))) + networkSettings.derivedNodeName should be("default-node-name") + networkSettings.derivedDeclaredAddress should be(Some(new InetSocketAddress("127.0.0.1", 6868))) + networkSettings.derivedNonce should be(0) networkSettings.knownPeers should be(List("8.8.8.8:6868", "4.4.8.8:6868")) networkSettings.peersDataResidenceTime should be(1.day) networkSettings.blackListResidenceTime should be(10.minutes) From a7240c43a4eb928a810d6e5aceaa29d5ab237c17 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Fri, 15 Nov 2024 13:32:04 +0400 Subject: [PATCH 26/31] Fix tests after renaming fields --- .../test/scala/com/wavesplatform/it/Docker.scala | 5 +++-- .../test/scala/com/wavesplatform/it/Node.scala | 2 +- .../settings/NetworkSettingsSpecification.scala | 15 +++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/node-it/src/test/scala/com/wavesplatform/it/Docker.scala b/node-it/src/test/scala/com/wavesplatform/it/Docker.scala index 8f8ab85a5c..43f8c19521 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/Docker.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/Docker.scala @@ -306,7 +306,7 @@ class Docker( private def getNodeInfo(containerId: String, settings: WavesSettings): NodeInfo = { val restApiPort = settings.restAPISettings.port // assume test nodes always have an open port - val networkPort = settings.networkSettings.bindAddress.get.getPort + val networkPort = settings.networkSettings.derivedBindAddress.get.getPort val containerInfo = inspectContainer(containerId) val wavesIpAddress = containerInfo.networkSettings().networks().get(wavesNetwork.name()).ipAddress() @@ -601,7 +601,8 @@ object Docker { } AddressScheme.current = new AddressScheme { - override val chainId: Byte = ConfigSource.fromConfig(configTemplate).at("waves.blockchain.custom.address-scheme-character").loadOrThrow[String].charAt(0).toByte + override val chainId: Byte = + ConfigSource.fromConfig(configTemplate).at("waves.blockchain.custom.address-scheme-character").loadOrThrow[String].charAt(0).toByte } def apply(owner: Class[?]): Docker = new Docker(tag = owner.getSimpleName) diff --git a/node-it/src/test/scala/com/wavesplatform/it/Node.scala b/node-it/src/test/scala/com/wavesplatform/it/Node.scala index 903f56213e..27488b168e 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/Node.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/Node.scala @@ -55,7 +55,7 @@ abstract class Node(val config: Config) extends AutoCloseable { object Node { implicit class NodeExt(val n: Node) extends AnyVal { - def name: String = n.settings.networkSettings.nodeName + def name: String = n.settings.networkSettings.derivedNodeName def publicKeyStr: String = n.publicKey.toString def fee(txTypeId: Byte): Long = FeeValidation.FeeConstants(TransactionType(txTypeId)) * FeeValidation.FeeUnit def blockDelay: FiniteDuration = n.settings.blockchainSettings.genesisSettings.averageBlockDelay diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala index 220f288265..0df0a8293d 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/NetworkSettingsSpecification.scala @@ -5,7 +5,6 @@ import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec import pureconfig.ConfigSource import pureconfig.generic.auto.* -import pureconfig.error.ConfigReaderException import scala.concurrent.duration.* class NetworkSettingsSpecification extends FlatSpec { @@ -67,32 +66,32 @@ class NetworkSettingsSpecification extends FlatSpec { val config = loadConfig(ConfigFactory.empty()) val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] - networkSettings.nonce should not be 0 + networkSettings.derivedNonce should not be 0 } it should "build node name using nonce" in { val config = loadConfig(ConfigFactory.parseString("waves.network.nonce = 12345")) val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] - networkSettings.nonce should be(12345) - networkSettings.nodeName should be("Node-12345") + networkSettings.derivedNonce should be(12345) + networkSettings.derivedNodeName should be("Node-12345") } it should "build node name using random nonce" in { val config = loadConfig(ConfigFactory.empty()) val networkSettings = ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] - networkSettings.nonce should not be 0 - networkSettings.nodeName should be(s"Node-${networkSettings.nonce}") + networkSettings.derivedNonce should not be 0 + networkSettings.derivedNodeName should be(s"Node-${networkSettings.derivedNonce}") } - it should "fail with ConfigReaderException on too long node name" in { + it should "fail with IllegalArgumentException on too long node name" in { val config = loadConfig( ConfigFactory.parseString( "waves.network.node-name = очень-длинное-название-в-многобайтной-кодировке-отличной-от-однобайтной-кодировки-американского-института-стандартов" ) ) - intercept[ConfigReaderException[NetworkSettings]] { + intercept[IllegalArgumentException] { ConfigSource.fromConfig(config).at("waves.network").loadOrThrow[NetworkSettings] } } From dd38d842dd78eebd1daa7c10c0acde5febfa6b22 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Mon, 18 Nov 2024 12:18:47 +0400 Subject: [PATCH 27/31] Add unit test for DBSettings --- .../settings/DbSettingsSpecification.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala new file mode 100644 index 0000000000..833f2d16be --- /dev/null +++ b/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala @@ -0,0 +1,57 @@ +package com.wavesplatform.settings + +import com.typesafe.config.ConfigFactory +import com.wavesplatform.test.FlatSpec + +class DbSettingsSpecification extends FlatSpec { + "DbSettingsSpecification" should "read values from config" in { + val config = loadConfig(ConfigFactory.parseString("""waves.db { + | directory = "/data" + | store-transactions-by-address = true + | store-lease-states-by-address = true + | store-invoke-script-results = true + | store-state-hashes = false + | max-cache-size = 100000 + | max-rollback-depth = 2000 + | cleanup-interval = 500 + | rocksdb { + | main-cache-size = 512M + | tx-cache-size = 16M + | tx-meta-cache-size = 16M + | tx-snapshot-cache-size = 16M + | api-cache-size=16M + | write-buffer-size = 128M + | enable-statistics = false + | allow-mmap-reads = off + | parallelism = 2 + | max-open-files = 100 + | } + |}""".stripMargin)) + val actualDbSettings = DBSettings.fromConfig(config.getConfig("waves.db")) + + val expectedDbSettings: DBSettings = DBSettings( + directory = "/data", + storeTransactionsByAddress = true, + storeLeaseStatesByAddress = true, + storeInvokeScriptResults = true, + storeStateHashes = false, + maxCacheSize = 100000, + maxRollbackDepth = 2000, + cleanupInterval = Some(500), + rocksdb = RocksDBSettings( + mainCacheSize = SizeInBytes(512L * 1024 * 1024), + txCacheSize = SizeInBytes(16L * 1024 * 1024), + txMetaCacheSize = SizeInBytes(16L * 1024 * 1024), + txSnapshotCacheSize = SizeInBytes(16L * 1024 * 1024), + apiCacheSize = SizeInBytes(16L * 1024 * 1024), + writeBufferSize = SizeInBytes(128L * 1024 * 1024), + enableStatistics = false, + allowMmapReads = false, + parallelism = 2, + maxOpenFiles = 100 + ) + ) + + actualDbSettings should be(expectedDbSettings) + } +} From 0ae2d8b8a97e24e6d177a724ed98a0abb543e982 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Mon, 18 Nov 2024 12:59:52 +0400 Subject: [PATCH 28/31] Remove redundant helpers --- .../wavesplatform/settings/DBSettings.scala | 20 ------------------- .../settings/RocksDBSettings.scala | 18 ----------------- .../settings/WavesSettings.scala | 2 +- .../com/wavesplatform/settings/package.scala | 10 ++++++++++ .../settings/DbSettingsSpecification.scala | 5 ++++- 5 files changed, 15 insertions(+), 40 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala b/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala index 2e2881b3bc..adf14e49b1 100644 --- a/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/DBSettings.scala @@ -1,7 +1,5 @@ package com.wavesplatform.settings -import com.typesafe.config.Config - case class DBSettings( directory: String, storeTransactionsByAddress: Boolean, @@ -13,21 +11,3 @@ case class DBSettings( cleanupInterval: Option[Int] = None, rocksdb: RocksDBSettings ) - -object DBSettings { - def fromConfig(config: Config): DBSettings = DBSettings( - directory = config.getString("directory"), - storeTransactionsByAddress = config.getBoolean("store-transactions-by-address"), - storeLeaseStatesByAddress = config.getBoolean("store-lease-states-by-address"), - storeInvokeScriptResults = config.getBoolean("store-invoke-script-results"), - storeStateHashes = config.getBoolean("store-state-hashes"), - maxCacheSize = config.getInt("max-cache-size"), - maxRollbackDepth = config.getInt("max-rollback-depth"), - cleanupInterval = if (config.hasPath("cleanup-interval")) { - Option(config.getInt("cleanup-interval")) - } else { - None - }, - rocksdb = RocksDBSettings.fromConfig(config.getConfig("rocksdb")) - ) -} diff --git a/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala b/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala index 5b47fce39d..3692e164f2 100644 --- a/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/RocksDBSettings.scala @@ -1,7 +1,5 @@ package com.wavesplatform.settings -import com.typesafe.config.Config - case class RocksDBSettings( mainCacheSize: SizeInBytes, txCacheSize: SizeInBytes, @@ -14,19 +12,3 @@ case class RocksDBSettings( parallelism: Int, maxOpenFiles: Int ) - -object RocksDBSettings { - def fromConfig(config: Config): RocksDBSettings = - RocksDBSettings( - mainCacheSize = SizeInBytes(config.getBytes("main-cache-size").toLong), - txCacheSize = SizeInBytes(config.getBytes("tx-cache-size").toLong), - txMetaCacheSize = SizeInBytes(config.getBytes("tx-meta-cache-size").toLong), - txSnapshotCacheSize = SizeInBytes(config.getBytes("tx-snapshot-cache-size").toLong), - apiCacheSize = SizeInBytes(config.getBytes("api-cache-size").toLong), - writeBufferSize = SizeInBytes(config.getBytes("write-buffer-size").toLong), - enableStatistics = config.getBoolean("enable-statistics"), - allowMmapReads = config.getBoolean("allow-mmap-reads"), - parallelism = config.getInt("parallelism"), - maxOpenFiles = config.getInt("max-open-files") - ) -} diff --git a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala index 3ae166eff9..d029ed74a3 100644 --- a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala @@ -36,7 +36,7 @@ object WavesSettings { val enableLightMode = wavesConfigSource.at("enable-light-mode").loadOrThrow[Boolean] val ntpServer = wavesConfigSource.at("ntp-server").loadOrThrow[String] val maxTxErrorLogSize = wavesConfigSource.at("max-tx-error-log-size").loadOrThrow[Int] - val dbSettings = DBSettings.fromConfig(waves.getConfig("db")) + val dbSettings = wavesConfigSource.at("db").loadOrThrow[DBSettings] val extensions = wavesConfigSource.at("extensions").loadOrThrow[Seq[String]] val extensionsShutdownTimeout = wavesConfigSource.at("extensions-shutdown-timeout").loadOrThrow[FiniteDuration] val networkSettings = wavesConfigSource.at("network").loadOrThrow[NetworkSettings] diff --git a/node/src/main/scala/com/wavesplatform/settings/package.scala b/node/src/main/scala/com/wavesplatform/settings/package.scala index b7d96b4f11..9abd9b2b20 100644 --- a/node/src/main/scala/com/wavesplatform/settings/package.scala +++ b/node/src/main/scala/com/wavesplatform/settings/package.scala @@ -21,6 +21,16 @@ package object settings { object SizeInBytes extends TaggedType[Long] type SizeInBytes = SizeInBytes.Type + implicit val sizeInBytesConfigReader: ConfigReader[SizeInBytes] = ConfigReader.fromCursor(cur => + for { + configValue <- cur.asConfigValue + } yield { + val config = ConfigFactory.empty().withValue("stubKey", configValue) + val bytes: Long = config.getBytes("stubKey") + SizeInBytes(bytes) + } + ) + def loadConfig(userConfig: Config): Config = { loadConfig(Some(userConfig)) } diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala index 833f2d16be..14e918719c 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala @@ -2,6 +2,8 @@ package com.wavesplatform.settings import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec +import pureconfig.ConfigSource +import pureconfig.generic.auto.* class DbSettingsSpecification extends FlatSpec { "DbSettingsSpecification" should "read values from config" in { @@ -27,7 +29,8 @@ class DbSettingsSpecification extends FlatSpec { | max-open-files = 100 | } |}""".stripMargin)) - val actualDbSettings = DBSettings.fromConfig(config.getConfig("waves.db")) + + val actualDbSettings = ConfigSource.fromConfig(config).at("waves.db").loadOrThrow[DBSettings] val expectedDbSettings: DBSettings = DBSettings( directory = "/data", From c547ac987e0aff675bab76dfa223b4ac00e5f123 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Mon, 18 Nov 2024 13:22:47 +0400 Subject: [PATCH 29/31] Add unit test for SizeInBytes ConfigReader --- .../settings/DbSettingsSpecification.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala b/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala index 14e918719c..7e47f9ee36 100644 --- a/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala +++ b/node/tests/src/test/scala/com/wavesplatform/settings/DbSettingsSpecification.scala @@ -4,8 +4,23 @@ import com.typesafe.config.ConfigFactory import com.wavesplatform.test.FlatSpec import pureconfig.ConfigSource import pureconfig.generic.auto.* +import com.typesafe.config.ConfigException.BadValue class DbSettingsSpecification extends FlatSpec { + "SizeInBytes" should "should successfully read bytes values" in { + val config = loadConfig(ConfigFactory.parseString("size-in-bytes-value = 512M")) + val actualValue = ConfigSource.fromConfig(config).at("size-in-bytes-value").loadOrThrow[SizeInBytes] + val expectedValue = SizeInBytes(512L * 1024 * 1024) + actualValue should be(expectedValue) + } + + "SizeInBytes" should "should fail on invalid values" in { + val config = loadConfig(ConfigFactory.parseString("size-in-bytes-value = 512X")) + assertThrows[BadValue] { + ConfigSource.fromConfig(config).at("size-in-bytes-value").loadOrThrow[SizeInBytes] + } + } + "DbSettingsSpecification" should "read values from config" in { val config = loadConfig(ConfigFactory.parseString("""waves.db { | directory = "/data" From f3f7eec25562b92e2bb7687279725f9f73edc529 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Mon, 18 Nov 2024 14:41:00 +0400 Subject: [PATCH 30/31] Refactor WavesSettings --- .../settings/BlockchainSettings.scala | 52 +++++++++---------- .../settings/WavesSettings.scala | 4 +- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala index 51a84df39a..81e5c2dd65 100644 --- a/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/BlockchainSettings.scala @@ -244,33 +244,31 @@ private[settings] object BlockchainType { } object BlockchainSettings { - def fromRootConfig(config: Config): BlockchainSettings = fromConfig(config.getConfig("waves.blockchain")) + def fromRootConfig(config: Config): BlockchainSettings = + ConfigSource.fromConfig(config).at("waves.blockchain").loadOrThrow[BlockchainSettings] - def fromConfig(config: Config): BlockchainSettings = { - val blockchainType = config.getString("type").toUpperCase - val (addressSchemeCharacter, functionalitySettings, genesisSettings, rewardsSettings) = blockchainType match { - case BlockchainType.STAGENET => - ('S', FunctionalitySettings.STAGENET, GenesisSettings.STAGENET, RewardsSettings.STAGENET) - case BlockchainType.TESTNET => - ('T', FunctionalitySettings.TESTNET, GenesisSettings.TESTNET, RewardsSettings.TESTNET) - case BlockchainType.MAINNET => - ('W', FunctionalitySettings.MAINNET, GenesisSettings.MAINNET, RewardsSettings.MAINNET) - case _ => // Custom - // Note: Mind the imperative approach to reading the config here. Be careful when refactoring. - val networkId = config.getString(s"custom.address-scheme-character").charAt(0) - val configSource = ConfigSource.fromConfig(config) - val functionality: FunctionalitySettings = configSource.at("custom.functionality").loadOrThrow[FunctionalitySettings] - val genesis = configSource.at("custom.genesis").loadOrThrow[GenesisSettings] - val rewards = configSource.at("custom.rewards").loadOrThrow[RewardsSettings] - require(functionality.minBlockTime <= genesis.averageBlockDelay, "minBlockTime should be <= averageBlockDelay") - (networkId, functionality, genesis, rewards) - } + implicit val configReader: ConfigReader[BlockchainSettings] = ConfigReader.fromCursor(cur => + for { + objCur <- cur.asObjectCursor + blockchainTypeString <- objCur.atKey("type").flatMap(_.asString).map(_.toUpperCase) + (addressSchemeCharacter, functionalitySettings, genesisSettings, rewardsSettings) <- blockchainTypeString match { + case BlockchainType.STAGENET => Right(('S', FunctionalitySettings.STAGENET, GenesisSettings.STAGENET, RewardsSettings.STAGENET)) + case BlockchainType.TESTNET => Right(('T', FunctionalitySettings.TESTNET, GenesisSettings.TESTNET, RewardsSettings.TESTNET)) + case BlockchainType.MAINNET => Right(('W', FunctionalitySettings.MAINNET, GenesisSettings.MAINNET, RewardsSettings.MAINNET)) + case _ => + // Custom + for { + customObjCur <- objCur.atKey("custom").flatMap(_.asObjectCursor) + networkId <- customObjCur.atKey("address-scheme-character").flatMap(_.asString).map(_.charAt(0)) + functionality <- customObjCur.atKey("functionality").flatMap(ConfigReader[FunctionalitySettings].from) + genesis <- customObjCur.atKey("genesis").flatMap(ConfigReader[GenesisSettings].from) + rewards <- customObjCur.atKey("rewards").flatMap(ConfigReader[RewardsSettings].from) + } yield { + require(functionality.minBlockTime <= genesis.averageBlockDelay, "minBlockTime should be <= averageBlockDelay") + (networkId, functionality, genesis, rewards) + } + } - BlockchainSettings( - addressSchemeCharacter = addressSchemeCharacter, - functionalitySettings = functionalitySettings, - genesisSettings = genesisSettings, - rewardsSettings = rewardsSettings - ) - } + } yield BlockchainSettings(addressSchemeCharacter, functionalitySettings, genesisSettings, rewardsSettings) + ) } diff --git a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala index d029ed74a3..375adb61c6 100644 --- a/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala +++ b/node/src/main/scala/com/wavesplatform/settings/WavesSettings.scala @@ -33,7 +33,6 @@ object WavesSettings { val wavesConfigSource = ConfigSource.fromConfig(waves) val directory = wavesConfigSource.at("directory").loadOrThrow[String] - val enableLightMode = wavesConfigSource.at("enable-light-mode").loadOrThrow[Boolean] val ntpServer = wavesConfigSource.at("ntp-server").loadOrThrow[String] val maxTxErrorLogSize = wavesConfigSource.at("max-tx-error-log-size").loadOrThrow[Int] val dbSettings = wavesConfigSource.at("db").loadOrThrow[DBSettings] @@ -41,7 +40,7 @@ object WavesSettings { val extensionsShutdownTimeout = wavesConfigSource.at("extensions-shutdown-timeout").loadOrThrow[FiniteDuration] val networkSettings = wavesConfigSource.at("network").loadOrThrow[NetworkSettings] val walletSettings = wavesConfigSource.at("wallet").loadOrThrow[WalletSettings] - val blockchainSettings = BlockchainSettings.fromConfig(waves.getConfig("blockchain")) + val blockchainSettings = wavesConfigSource.at("blockchain").loadOrThrow[BlockchainSettings] val minerSettings = wavesConfigSource.at("miner").loadOrThrow[MinerSettings] val restAPISettings = wavesConfigSource.at("rest-api").loadOrThrow[RestAPISettings] val synchronizationSettings = wavesConfigSource.at("synchronization").loadOrThrow[SynchronizationSettings] @@ -49,6 +48,7 @@ object WavesSettings { val featuresSettings = wavesConfigSource.at("features").loadOrThrow[FeaturesSettings] val rewardsSettings = wavesConfigSource.at("rewards").loadOrThrow[RewardsVotingSettings] val metrics = ConfigSource.fromConfig(rootConfig).at("metrics").loadOrThrow[Metrics.Settings] // TODO: Move to waves section + val enableLightMode = wavesConfigSource.at("enable-light-mode").loadOrThrow[Boolean] WavesSettings( directory, From 2d966c163b9a4e05761e3127bc379b02edf846c6 Mon Sep 17 00:00:00 2001 From: Vladimir Logachev Date: Mon, 18 Nov 2024 20:47:54 +0400 Subject: [PATCH 31/31] Remove ConfigSettingsValidator --- .../utils/ConfigSettingsValidator.scala | 109 ------------------ 1 file changed, 109 deletions(-) delete mode 100644 node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala diff --git a/node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala b/node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala deleted file mode 100644 index 8db2e7da4a..0000000000 --- a/node/src/main/scala/com/wavesplatform/settings/utils/ConfigSettingsValidator.scala +++ /dev/null @@ -1,109 +0,0 @@ -package com.wavesplatform.settings.utils - -import cats.data.{NonEmptyList, Validated, ValidatedNel} -import cats.instances.list.* -import cats.syntax.foldable.* -import cats.syntax.traverse.* -import com.typesafe.config.{Config, ConfigException, ConfigFactory, ConfigValue} -import com.wavesplatform.transaction.assets.exchange.AssetPair -import pureconfig.* - -import scala.jdk.CollectionConverters.* -import scala.reflect.ClassTag -import scala.util.Try - -object ConfigSettingsValidator { - - type ErrorsListOr[A] = ValidatedNel[String, A] - - def apply(config: Config): ConfigSettingsValidator = new ConfigSettingsValidator(config) - - implicit class ErrorListOrOps[A](validatedValue: ErrorsListOr[A]) { - def getValueOrThrowErrors: A = validatedValue valueOr (errorsAcc => throw new Exception(errorsAcc.mkString_(", "))) - } - - object AdhocValidation { - def validateAssetPairKey(key: String): Validated[String, AssetPair] = - Validated.fromTry(AssetPair.fromString(key)) leftMap (_ => s"Can't parse asset pair '$key'") - } -} - -class ConfigSettingsValidator(config: Config) { - - import ConfigSettingsValidator.ErrorsListOr - - private def createError[T](settingName: String, errorMsg: String, showError: Boolean = true, showValue: Boolean = true): NonEmptyList[String] = { - - lazy val value = config.getValue(settingName).unwrapped - - lazy val msg = (showValue, showError) match { - case (true, true) => s"$value ($errorMsg)" - case (true, false) => s"$value" - case (false, true) => s"$errorMsg" - case _ => "" - } - - NonEmptyList.one(s"Invalid setting $settingName value: $msg") - } - - def validate[T: ConfigReader: ClassTag](settingName: String, showError: Boolean = false): ErrorsListOr[T] = { - Validated fromTry Try(ConfigSource.fromConfig(config).at(settingName).loadOrThrow[T]) leftMap (ex => - createError(settingName, ex.getMessage, showError) - ) - } - - def validateByPredicate[T: ConfigReader: ClassTag](settingName: String)(predicate: T => Boolean, errorMsg: String): ErrorsListOr[T] = { - validate[T](settingName, showError = true).ensure(createError(settingName, errorMsg))(predicate) - } - - def validatePercent(settingName: String): ErrorsListOr[Double] = { - validateByPredicate[Double](settingName)(p => 0 < p && p <= 100, "required 0 < percent <= 100") - } - - private def configFromConfigValue(configValue: ConfigValue) = - ConfigFactory.empty().withValue("stubKey", configValue).getConfig("stubKey") - - def validateList[T: ConfigReader: ClassTag](settingName: String): ErrorsListOr[List[T]] = { - config - .getList(settingName) - .asScala - .toList - .zipWithIndex - .traverse { case (cfg, index) => - val elemPath = s"$settingName.$index" - Validated fromTry Try(ConfigSource.fromConfig(configFromConfigValue(cfg)).at(elemPath).loadOrThrow[T]) leftMap (ex => List(ex.getMessage)) - } - .leftMap(errorsInList => createError(settingName, errorsInList.mkString(", "), showValue = false)) - } - - def validateMap[K, V: ConfigReader: ClassTag](settingName: String)(keyValidator: String => Validated[String, K]): ErrorsListOr[Map[K, V]] = { - config - .getConfig(settingName) - .root() - .entrySet() - .asScala - .toList - .traverse { entry => - val elemPath = s"$settingName.${entry.getKey}" - val k = keyValidator(entry.getKey).leftMap(List(_)) - val v = Validated fromTry Try(ConfigSource.fromConfig(configFromConfigValue(entry.getValue)).at(elemPath).loadOrThrow[V]) leftMap (ex => - List(ex.getMessage) - ) - k.product(v) - } - .map(_.toMap) - .leftMap(errorsInList => createError(settingName, errorsInList.mkString(", "), showValue = false)) - } - - def validateWithDefault[T: ConfigReader: ClassTag](settingName: String, defaultValue: T, showError: Boolean = false): ErrorsListOr[T] = { - Validated - .fromTry(Try(ConfigSource.fromConfig(config).at(settingName).loadOrThrow[T]).recover { case _: ConfigException.Missing => defaultValue }) - .leftMap(ex => createError(settingName, ex.getMessage, showError)) - } - - def validateByPredicateWithDefault[T: ConfigReader: ClassTag]( - settingName: String - )(predicate: T => Boolean, errorMsg: String, defaultValue: T): ErrorsListOr[T] = { - validateWithDefault[T](settingName, defaultValue, showError = true).ensure(createError(settingName, errorMsg))(predicate) - } -}