Chains config refactoring (#311)

Generate code from chains config
This commit is contained in:
Vyacheslav
2023-09-29 13:36:34 +03:00
committed by GitHub
parent a2c5743fb2
commit ef6af898e0
80 changed files with 1527 additions and 1321 deletions

32
foundation/build.gradle Normal file
View File

@@ -0,0 +1,32 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.9.10'
id 'maven-publish'
}
repositories {
mavenLocal()
mavenCentral()
}
group = 'dshackle'
dependencies {
implementation 'org.yaml:snakeyaml:1.24'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'
}
test {
useJUnitPlatform()
}
version '1.0.0'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
mavenLocal()
}
}

View File

View File

@@ -0,0 +1,90 @@
package io.emeraldpay.dshackle.foundation
import java.time.Duration
class ChainOptions {
data class Options(
val disableUpstreamValidation: Boolean,
val disableValidation: Boolean,
val validationInterval: Int,
val timeout: Duration,
val providesBalance: Boolean?,
val validatePeers: Boolean,
val minPeers: Int,
val validateSyncing: Boolean,
val validateCallLimit: Boolean,
val validateChain: Boolean,
)
open class DefaultOptions : PartialOptions() {
var chains: List<String>? = null
var options: PartialOptions? = null
}
open class PartialOptions {
companion object {
@JvmStatic
fun getDefaults(): PartialOptions {
val options = PartialOptions()
options.minPeers = 1
return options
}
}
var disableValidation: Boolean? = null
var disableUpstreamValidation: Boolean? = null
var validationInterval: Int? = null
set(value) {
require(value == null || value > 0) {
"validation-interval must be a positive number: $value"
}
field = value
}
var timeout: Duration? = null
var providesBalance: Boolean? = null
var validatePeers: Boolean? = null
var validateCalllimit: Boolean? = null
var minPeers: Int? = null
set(value) {
require(value == null || value >= 0) {
"min-peers must be a positive number: $value"
}
field = value
}
var validateSyncing: Boolean? = null
var validateChain: Boolean? = null
fun merge(overwrites: PartialOptions?): PartialOptions {
if (overwrites == null) {
return this
}
val copy = PartialOptions()
copy.validatePeers = overwrites.validatePeers ?: this.validatePeers
copy.minPeers = overwrites.minPeers ?: this.minPeers
copy.disableValidation = overwrites.disableValidation ?: this.disableValidation
copy.validationInterval = overwrites.validationInterval ?: this.validationInterval
copy.providesBalance = overwrites.providesBalance ?: this.providesBalance
copy.validateSyncing = overwrites.validateSyncing ?: this.validateSyncing
copy.validateCalllimit = overwrites.validateCalllimit ?: this.validateCalllimit
copy.timeout = overwrites.timeout ?: this.timeout
copy.validateChain = overwrites.validateChain ?: this.validateChain
copy.disableUpstreamValidation =
overwrites.disableUpstreamValidation ?: this.disableUpstreamValidation
return copy
}
fun buildOptions(): Options =
Options(
this.disableUpstreamValidation ?: false,
this.disableValidation ?: false,
this.validationInterval ?: 30,
this.timeout ?: Duration.ofSeconds(60),
this.providesBalance,
this.validatePeers ?: true,
this.minPeers ?: 1,
this.validateSyncing ?: true,
this.validateCalllimit ?: true,
this.validateChain ?: true,
)
}
}

View File

@@ -0,0 +1,48 @@
package io.emeraldpay.dshackle.foundation
import org.yaml.snakeyaml.nodes.MappingNode
import java.time.Duration
class ChainOptionsReader : YamlConfigReader<ChainOptions.PartialOptions>() {
override fun read(upNode: MappingNode?): ChainOptions.PartialOptions? {
return if (hasAny(upNode, "options")) {
return getMapping(upNode, "options")?.let { values ->
readOptions(values)
}
} else {
null
}
}
fun readOptions(values: MappingNode): ChainOptions.PartialOptions {
val options = ChainOptions.PartialOptions()
getValueAsBool(values, "validate-peers")?.let {
options.validatePeers = it
}
getValueAsBool(values, "validate-syncing")?.let {
options.validateSyncing = it
}
getValueAsBool(values, "validate-call-limit")?.let {
options.validateCalllimit = it
}
getValueAsBool(values, "validate-chain")?.let {
options.validateChain = it
}
getValueAsInt(values, "min-peers")?.let {
options.minPeers = it
}
getValueAsInt(values, "timeout")?.let {
options.timeout = Duration.ofSeconds(it.toLong())
}
getValueAsBool(values, "disable-validation")?.let {
options.disableValidation = it
}
getValueAsInt(values, "validation-interval")?.let {
options.validationInterval = it
}
getValueAsBool(values, "balance")?.let {
options.providesBalance = it
}
return options
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2020 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.foundation
import org.yaml.snakeyaml.nodes.MappingNode
interface ConfigReader<T> {
fun read(input: MappingNode?): T?
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2020 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.foundation
/**
* Update configuration value from environment variables. Format: ${ENV_VAR_NAME}
*/
class EnvVariables {
companion object {
private val envRegex = Regex("\\$\\{(\\w+?)}")
}
fun postProcess(value: String): String {
return envRegex.replace(value) { m ->
m.groups[1]?.let { g ->
System.getProperty(g.value) ?: System.getenv(g.value) ?: ""
} ?: ""
}
}
}

View File

@@ -0,0 +1,187 @@
/**
* Copyright (c) 2020 EmeraldPay, Inc
* Copyright (c) 2020 ETCDEV GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.foundation
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.CollectionNode
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.NodeTuple
import org.yaml.snakeyaml.nodes.ScalarNode
import java.io.InputStream
import java.io.InputStreamReader
import java.util.Locale
import kotlin.time.Duration
import kotlin.time.toJavaDuration
abstract class YamlConfigReader<T> : ConfigReader<T> {
private val envVariables = EnvVariables()
val filename = "dshackle.yaml"
fun read(input: InputStream): T? {
val configNode = readNode(input)
return read(configNode)
}
fun readNode(input: String): MappingNode {
return readNode(input.byteInputStream())
}
fun readNode(input: InputStream): MappingNode {
val yaml = Yaml()
return asMappingNode(yaml.compose(InputStreamReader(input)))
}
fun mergeMappingNode(a: MappingNode?, b: MappingNode?): MappingNode? = when {
a == null -> b
b == null -> a
else -> {
val mergedTuples = a.value.toMutableList()
mergedTuples.addAll(
b.value.filter { tupleB ->
mergedTuples.none { it.keyNode.valueAsString() == tupleB.keyNode.valueAsString() } && (tupleB.valueNode is MappingNode || tupleB.valueNode is ScalarNode)
},
)
a.value.forEach { tupleA ->
b.value.find { it.keyNode.valueAsString() == tupleA.keyNode.valueAsString() }?.let { tupleB ->
if (tupleA.valueNode is MappingNode && tupleB.valueNode is MappingNode) {
mergedTuples[mergedTuples.indexOf(tupleA)] = NodeTuple(tupleA.keyNode, mergeMappingNode(tupleA.valueNode as MappingNode, tupleB.valueNode as MappingNode))
} else if (tupleA.valueNode is ScalarNode && tupleB.valueNode is ScalarNode) {
mergedTuples[mergedTuples.indexOf(tupleA)] = NodeTuple(tupleA.keyNode, tupleB.valueNode)
}
}
}
MappingNode(a.tag, mergedTuples, a.flowStyle)
}
}
protected fun hasAny(mappingNode: MappingNode?, key: String): Boolean =
mappingNode?.let { node ->
node.value
.stream()
.anyMatch { it.keyNode.valueAsString() == key }
} ?: false
@Suppress("UNCHECKED_CAST")
private fun <T> getValue(mappingNode: MappingNode?, key: String, type: Class<T>): T? {
if (mappingNode == null) {
return null
}
return mappingNode.value
.stream()
.filter { type.isAssignableFrom(it.valueNode.javaClass) }
.filter { it.keyNode.valueAsString() == key }
.map { n -> n.valueNode as T }
.findFirst()
.orElse(null)
}
fun Node.valueAsString(): String? = if (this is ScalarNode) this.value else null
protected fun getMapping(mappingNode: MappingNode?, key: String): MappingNode? {
return getValue(mappingNode, key, MappingNode::class.java)
}
private fun getValue(mappingNode: MappingNode?, key: String): ScalarNode? {
return getValue(mappingNode, key, ScalarNode::class.java)
}
@Suppress("UNCHECKED_CAST")
protected fun <T> getList(mappingNode: MappingNode?, key: String): CollectionNode<T>? {
val value = getValue(mappingNode, key, CollectionNode::class.java) ?: return null
return value as CollectionNode<T>
}
protected fun getListOfString(mappingNode: MappingNode?, key: String): List<String>? {
return getList<ScalarNode>(mappingNode, key)?.value
?.map { it.value }
?.map(envVariables::postProcess)
}
protected fun getValueAsString(mappingNode: MappingNode?, key: String): String? {
return getValue(mappingNode, key)?.let {
return@let it.value
}?.let(envVariables::postProcess)
}
protected fun getValueAsDuration(mappingNode: MappingNode?, key: String): java.time.Duration? {
return getValue(mappingNode, key)?.let {
return@let if (it.isPlain) {
Duration.parse(it.value).toJavaDuration()
} else {
null
}
}
}
protected fun getValueAsInt(mappingNode: MappingNode?, key: String): Int? {
return getValue(mappingNode, key)?.let {
return@let if (it.isPlain) {
it.value.toIntOrNull()
} else {
null
}
}
}
protected fun getValueAsLong(mappingNode: MappingNode?, key: String): Long? {
return getValue(mappingNode, key)?.let {
return@let if (it.isPlain) {
it.value.toLongOrNull()
} else {
null
}
}
}
protected fun getValueAsBool(mappingNode: MappingNode?, key: String): Boolean? {
return getValue(mappingNode, key)?.let {
return@let if (it.isPlain) {
it.value.lowercase(Locale.getDefault()) == "true"
} else {
null
}
}
}
protected fun asMappingNode(node: Node): MappingNode {
return if (MappingNode::class.java.isAssignableFrom(node.javaClass)) {
node as MappingNode
} else {
throw IllegalArgumentException("Not a map")
}
}
fun getValueAsBytes(mappingNode: MappingNode?, key: String): Int? {
return getValueAsString(mappingNode, key)?.let(envVariables::postProcess)?.let {
val m = Regex("^(\\d+)(m|mb|k|kb|b)?$").find(it.lowercase().trim())
?: throw IllegalArgumentException("Not a data size: $it. Example of correct values: '1024', '1kb', '5mb'")
val multiplier = m.groups[2]?.let {
when (it.value) {
"k", "kb" -> 1024
"m", "mb" -> 1024 * 1024
else -> 1
}
} ?: 1
val base = m.groups[1]!!.value.toInt()
base * multiplier
}
}
}

View File

@@ -0,0 +1,55 @@
package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.foundation.ChainOptions
import java.time.Duration
data class ChainsConfig(private val chains: List<ChainConfig>) : Iterable<ChainsConfig.ChainConfig> {
private val chainMap: Map<String, ChainConfig> = chains.fold(emptyMap()) { acc, item ->
acc.plus(item.shortNames.map { Pair(it, item) })
}
override fun iterator(): Iterator<ChainConfig> {
return chains.iterator()
}
companion object {
@JvmStatic
fun default(): ChainsConfig = ChainsConfig(emptyList())
}
data class ChainConfig(
val expectedBlockTime: Duration,
val syncingLagSize: Int,
val laggingLagSize: Int,
val options: ChainOptions.PartialOptions,
val chainId: String,
val netVersion: Long,
val grpcId: Int,
val code: String,
val shortNames: List<String>,
val callLimitContract: String?,
val id: String,
val blockchain: String,
) {
companion object {
@JvmStatic
fun default() = ChainConfig(
Duration.ofSeconds(12),
6,
1,
ChainOptions.PartialOptions(),
"0x0",
0,
0,
"UNKNOWN",
emptyList(),
null,
"undefined",
"undefined",
)
}
}
fun resolve(chain: String): ChainConfig {
return chainMap[chain] ?: ChainConfig.default()
}
}

View File

@@ -0,0 +1,114 @@
package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.foundation.ChainOptions
import io.emeraldpay.dshackle.foundation.ChainOptionsReader
import io.emeraldpay.dshackle.foundation.YamlConfigReader
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.NodeTuple
import org.yaml.snakeyaml.nodes.ScalarNode
import org.yaml.snakeyaml.nodes.Tag
class ChainsConfigReader(
private val chainsOptionsReader: ChainOptionsReader,
) : YamlConfigReader<ChainsConfig>() {
private val defaultConfig = this::class.java.getResourceAsStream("/chains.yaml")!!
private fun readChains(input: MappingNode?): Map<Int, Pair<String, MappingNode>> {
return getMapping(input, "chain-settings")?.let { config ->
val default = getMapping(config, "default")
getList<MappingNode>(config, "protocols")?.let { protocols ->
protocols.value.fold(emptyMap()) { acc, protocol ->
val blockchain = getValueAsString(protocol, "id")
?: throw IllegalArgumentException("Blockchain id is not defined")
val settings = mergeMappingNode(default, getMapping(protocol, "settings"))
acc.plus(
getList<MappingNode>(protocol, "chains")?.let { chains ->
chains.value.map { chain ->
val chainSettings = mergeMappingNode(settings, getMapping(chain, "settings"))
val updatedChain = mergeMappingNode(
chain,
MappingNode(
chain.tag,
listOf(
NodeTuple(ScalarNode(Tag.STR, "settings", null, null, DumperOptions.ScalarStyle.LITERAL), chainSettings),
NodeTuple(
ScalarNode(Tag.STR, "blockchain", null, null, DumperOptions.ScalarStyle.LITERAL),
ScalarNode(Tag.STR, blockchain, null, null, DumperOptions.ScalarStyle.LITERAL),
),
),
chain.flowStyle,
),
)
val grpcId = getValueAsInt(updatedChain, "grpcId")
?: throw IllegalArgumentException("grpcId for chain is not defined")
Pair(grpcId, Pair(blockchain, updatedChain!!))
}
} ?: listOf(),
)
}
}
} ?: emptyMap()
}
private fun parseChain(blockchain: String, node: MappingNode): ChainsConfig.ChainConfig {
val id = getValueAsString(node, "id")
?: throw IllegalArgumentException("undefined id for $blockchain")
val settings = getMapping(node, "settings") ?: throw IllegalArgumentException("undefined settings for $blockchain")
val lags = getMapping(settings, "lags")?.let { lagConfig ->
Pair(
getValueAsInt(lagConfig, "syncing")
?: throw IllegalArgumentException("undefined syncing for $blockchain"),
getValueAsInt(lagConfig, "lagging")
?: throw IllegalArgumentException("undefined lagging for $blockchain"),
)
} ?: throw IllegalArgumentException("undefined lags for $blockchain")
val expectedBlockTime = getValueAsDuration(settings, "expected-block-time")
?: throw IllegalArgumentException("undefined expected block time")
val validateContract = getValueAsString(node, "call-validate-contract")
val options = chainsOptionsReader.read(settings) ?: ChainOptions.PartialOptions()
val chainId = getValueAsString(node, "chain-id")
?: throw IllegalArgumentException("undefined chain id for $blockchain")
val code = getValueAsString(node, "code")
?: throw IllegalArgumentException("undefined code for $blockchain")
val grpcId = getValueAsInt(node, "grpcId")
?: throw IllegalArgumentException("undefined code for $blockchain")
val netVersion = getValueAsLong(node, "net-version") ?: chainId.drop(2).toLong(radix = 16)
val shortNames = getListOfString(node, "short-names")
?: throw IllegalArgumentException("undefined shortnames for $blockchain")
return ChainsConfig.ChainConfig(
expectedBlockTime = expectedBlockTime,
syncingLagSize = lags.first,
laggingLagSize = lags.second,
options = options,
callLimitContract = validateContract,
chainId = chainId,
code = code,
grpcId = grpcId,
netVersion = netVersion,
shortNames = shortNames,
id = id,
blockchain = blockchain,
)
}
override fun read(input: MappingNode?): ChainsConfig {
val default = readChains(readNode(defaultConfig))
val current = readChains(input)
val chains = default.keys.toSet().plus(current.keys).map {
val defChain = default[it]
val curChain = current[it]
when {
curChain == null && defChain != null -> parseChain(defChain.first, defChain.second)
curChain != null && defChain == null -> parseChain(curChain.first, curChain.second)
curChain != null && defChain != null -> {
val merged = mergeMappingNode(defChain.second, curChain.second)
parseChain(defChain.first, merged!!)
}
else -> ChainsConfig.ChainConfig.default()
}
}
return ChainsConfig(chains)
}
}

View File

@@ -0,0 +1,407 @@
version: v1
chain-settings:
default:
expected-block-time: 12s
lags:
syncing: 6
lagging: 1
protocols:
- id: bitcoin
settings:
expected-block-time: 10m
lags:
syncing: 3
lagging: 1
chains:
- id: mainnet
chain-id: 0x0
short-names: [bitcoin, btc]
code: BTC
grpcId: 1
- id: testnet
chain-id: 0x0
short-names: [bitcoin-testnet]
code: TESTNET_BITCOIN
grpcId: 10003
- id: ethereum
settings:
expected-block-time: 12s
lags:
syncing: 6
lagging: 1
chains:
- id: mainnet
chain-id: 0x1
short-names: [eth, ethereum, homestead]
code: ETH
grpcId: 100
call-validate-contract: 0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96
- id: goerli
chain-id: 0x5
code: GOERLI
grpcId: 10005
short-names: [goerli, goerli-testnet]
call-validate-contract: 0xCD9303A1F6da2a68f465A579a24cc2Ee5AE2192f
- id: ropsten
code: ROPSTEN
grpcId: 10006
chain-id: 0x3
short-names: [ropsten, ropsten-testnet]
- id: sepolia
code: SEPOLIA
grpcId: 10008
chain-id: 0xaa36a7
short-names: [sepolia, sepolia-testnet]
- id: holesky
code: ETHEREUM_HOLESKY
grpcId: 10027
chain-id: 0x4268
short-names: [holesky, ethereum-holesky]
- id: ethereum-classic
chains:
- id: mainnet
short-names: [ethereum-classic, etc]
chain-id: 0x3d
net-version: 1
code: ETC
grpcId: 101
- id: fantom
settings:
expected-block-time: 3s
options:
validate-peers: false
lags:
syncing: 10
lagging: 5
chains:
- id: mainnet
short-names: [fantom]
code: FTM
grpcId: 102
chain-id: 0xfa
- id: testnet
code: FANTOM_TESTNET
grpcId: 10016
short-names: [fantom-testnet]
chain-id: 0xfa2
- id: polygon
settings:
expected-block-time: 2.7s
lags:
syncing: 20
lagging: 10
chains:
- id: mainnet
call-validate-contract: 0x53Daa71B04d589429f6d3DF52db123913B818F23
code: POLYGON
grpcId: 1002
chain-id: 0x89
short-names: [polygon, matic]
- id: mumbai
call-validate-contract: 0x53Daa71B04d589429f6d3DF52db123913B818F23
code: POLYGON_POS_MUMBAI
grpcId: 10013
chain-id: 0x13881
short-names: [polygon-mumbai]
- id: arbitrum
settings:
expected-block-time: 260ms
options:
validate-peers: false
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: ARBITRUM
grpcId: 1004
short-names: [arbitrum, arb]
chain-id: 0xa4b1
- id: goerli
code: ARBITRUM_TESTNET
grpcId: 10009
short-names: [arbitrum-testnet, arbitrum-goerli]
chain-id: 0x66eed
settings:
expected-block-time: 1s
- id: optimism
settings:
expected-block-time: 2s
options:
validate-peers: false
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: OPTIMISM
grpcId: 1005
short-names: [optimism]
chain-id: 0xa
- id: goerli
code: OPTIMISM_TESTNET
grpcId: 10010
short-names: [optimism-testnet, optimism-goerli]
chain-id: 0x1A4
- id: bsc
settings:
expected-block-time: 3s
lags:
syncing: 20
lagging: 10
chains:
- id: mainnet
code: BSC
grpcId: 1006
chain-id: 0x38
short-names: [bsc, binance, bnb-smart-chain]
- id: testnet
code: BSC_TESTNET
grpcId: 10026
short-names: [bsc-testnet]
chain-id: 0x61
- id: polygon-zkevm
settings:
expected-block-time: 2.7s
options:
disable-validation: true
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: POLYGON_ZKEVM
grpcId: 1007
short-names: [polygon-zkevm]
chain-id: 0x44d
- id: testnet
code: POLYGON_ZKEVM_TESTNET
grpcId: 10011
short-names: [polygon-zkevm-testnet]
chain-id: 0x5a2
settings:
expected-block-time: 1m
- id: arbitrum-nova
settings:
expected-block-time: 1s
options:
disable-validation: true
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: ARBITRUM_NOVA
grpcId: 1008
short-names: [arbitrum-nova]
chain-id: 0xa4ba
- id: zksync
settings:
expected-block-time: 5s
options:
disable-validation: true
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: ZKSYNC
grpcId: 1009
chain-id: 0x144
short-names: [zksync]
- id: testnet
code: ZKS_TESTNET
grpcId: 10012
chain-id: 0x118
short-names: [zksync-testnet]
- id: base
settings:
expected-block-time: 2s
options:
validate-peers: false
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: BASE
grpcId: 1010
short-names: [base]
chain-id: 0x2105
- id: goerli
code: BASE_GOERLI
grpcId: 10014
short-names: [base-goerli]
chain-id: 0x14a33
- id: linea
settings:
expected-block-time: 12s
lags:
syncing: 6
lagging: 1
chains:
- id: mainnet
code: LINEA
grpcId: 1011
short-names: [linea]
chain-id: 0xe708
- id: goerli
code: LINEA_GOERLI
grpcId: 10015
short-names: [linea-goerli]
chain-id: 0xe704
- id: gnosis
settings:
expected-block-time: 6s
options:
validate-peers: false
lags:
syncing: 10
lagging: 5
chains:
- id: mainnet
code: GNOSIS
grpcId: 1012
short-names: [gnosis]
chain-id: 0x64
- id: chiado
code: GNOSIS_CHIADO
grpcId: 10017
short-names: [gnosis-chiado]
chain-id: 0x27d8
- id: avalanche
settings:
expected-block-time: 2s
options:
validate-peers: false
validate-syncing: false
lags:
syncing: 10
lagging: 5
chains:
- id: mainnet
code: AVALANCHE
grpcId: 1013
short-names: [avalanche]
chain-id: 0xa86a
- id: fuji
code: AVALANCHE_FUJI
grpcId: 10018
short-names: [avalanche-fuji]
chain-id: 0xa869
- id: aurora
settings:
expected-block-time: 1s
options:
validate-peers: false
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: AURORA
grpcId: 1015
short-names: [aurora]
chain-id: 0x4e454152
- id: testnet
code: AURORA_TESTNET
grpcId: 10021
short-names: [aurora-testnet]
chain-id: 0x4e454153
- id: mantle
settings:
expected-block-time: 500ms
options:
validate-peers: false
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: MANTLE
grpcId: 1017
short-names: [mantle]
chain-id: 0x1388
- id: testnet
code: MANTLE_TESTNET
grpcId: 10023
short-names: [mantle-testnet]
chain-id: 0x1389
- id: klaytn
settings:
expected-block-time: 1s
options:
validate-peers: false
lags:
syncing: 40
lagging: 20
chains:
- id: mainnet
code: KLAYTN
grpcId: 1018
short-names: [klaytn]
chain-id: 0x2019
- id: baobab
code: KLAYTN_BAOBAB
grpcId: 10024
short-names: [klaytn-baobab]
chain-id: 0x3e9
- id: celo
settings:
expected-block-time: 5s
lags:
syncing: 10
lagging: 5
chains:
- id: mainnet
code: CELO
grpcId: 1019
short-names: [celo]
chain-id: 0xa4ec
- id: alfajores
code: CELO_ALFAJORES
grpcId: 10028
short-names: [celo-alfajores]
chain-id: 0xaef3
- id: moonbeam
settings:
expected-block-time: 12s
lags:
syncing: 6
lagging: 1
chains:
- id: mainnet
code: MOONBEAM
grpcId: 1020
short-names: [moonbeam]
chain-id: 0x504
- id: moonriver
code: MOONRIVER
grpcId: 1021
short-names: [moonriver]
chain-id: 0x505
- id: moonbase-alpha
code: MOONBEAM_ALPHA
grpcId: 10029
short-names: [moonbase-alpha]
chain-id: 0x507
- id: scroll
settings:
expected-block-time: 3s
options:
validate-peers: false
lags:
syncing: 10
lagging: 5
chains:
- id: alphanet
code: SCROLL_ALPHANET
grpcId: 10022
short-names: [scroll-alphanet]
chain-id: 0x82751
- id: sepolia
code: SCROLL_SEPOLIA
grpcId: 10025
short-names: [scroll-sepolia]
chain-id: 0x8274f

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2020 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.foundation.EnvVariables
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.ValueSource
class EnvVariablesTest {
lateinit var reader: EnvVariables
@BeforeEach
fun setup() {
reader = EnvVariables()
}
@ParameterizedTest
@ValueSource(strings = ["", "a", "13143", "/etc/client1.myservice.com.key", "true", "1a68f20154fc258fe4149c199ad8f281"])
fun `Post process for usual strings`(s: String) {
assertEquals(s, reader.postProcess(s))
}
@ParameterizedTest
@CsvSource(
"p_\${id}, p_1",
"home: \${HOME}, home: /home/user",
"\${PASSWORD}, 1a68f20154fc258fe4149c199ad8f281",
)
fun `Post process replaces from env`(orig: String, replaced: String) {
System.setProperty("id", "1")
System.setProperty("HOME", "/home/user")
System.setProperty("PASSWORD", "1a68f20154fc258fe4149c199ad8f281")
assertEquals(replaced, reader.postProcess(orig))
}
}

View File

@@ -0,0 +1,80 @@
package io.emeraldpay.dshackle.foundation
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.MappingNode
import java.io.StringReader
class YamlConfigReaderTest {
@ParameterizedTest
@CsvSource(
"1024, 1024",
"1k, 1024",
"1kb, 1024",
"1K, 1024",
"16kb, 16384",
"1M, 1048576",
"4mb, 4194304",
)
fun `reads bytes values`(input: String, expected: Int) {
val rdr = Impl()
assertEquals(expected, rdr.getValueAsBytes(rdr.asNode("test", input), "test"))
}
@Test
fun `mergeMappingNode should merge nodes correctly`() {
val yaml = Yaml()
val a = yaml.compose(
StringReader(
"""
key1: value1
key2:
subkey1: subvalue1
""",
),
) as MappingNode
val b = yaml.compose(
StringReader(
"""
key1: value3
key2:
subkey1: subvalue1Modified
subkey2: subvalue2
key3: value3
""",
),
) as MappingNode
val result = Impl().mergeMappingNode(a, b)!!
val yml = Impl()
assertEquals(3, result.value.size)
assertEquals("value3", yml.getNodeValue(result, "key1"))
assertEquals("subvalue1Modified", yml.getNodeValue(yml.getNode(result, "key2") as MappingNode, "subkey1"))
assertEquals("subvalue2", yml.getNodeValue(yml.getNode(result, "key2") as MappingNode, "subkey2"))
assertEquals("value3", yml.getNodeValue(result, "key3"))
}
class Impl : YamlConfigReader<Any>() {
override fun read(input: MappingNode?): Any? {
return null
}
fun getNode(node: MappingNode, key: String): Any? {
return Impl().getMapping(node, key)
}
fun getNodeValue(node: MappingNode, key: String): String? {
return Impl().getValueAsString(node, key)
}
fun asNode(key: String, value: String): MappingNode {
return Yaml().compose(StringReader("$key: $value")) as MappingNode
}
}
}

View File

@@ -0,0 +1,44 @@
package org.drpc.chainsconfig
import io.emeraldpay.dshackle.config.ChainsConfigReader
import io.emeraldpay.dshackle.foundation.ChainOptionsReader
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
internal class ChainsConfigReaderTest {
@Test
fun `read standard config without custom`() {
val reader = ChainsConfigReader(ChainOptionsReader())
val config = reader.read(null)
val arb = config.resolve("arbitrum-goerli")
assertEquals(arb.chainId, "0x66eed")
assertEquals(arb.expectedBlockTime.seconds, 1L)
assertEquals(arb.options.validatePeers, false)
assertEquals(arb.id, "goerli")
val ethc = config.resolve("ethereum-classic")
assertEquals(ethc.chainId, "0x3d")
assertEquals(ethc.netVersion, 1)
assertEquals(ethc.code, "ETC")
assertEquals(ethc.grpcId, 101)
assertEquals(ethc.expectedBlockTime.seconds, 12)
assertEquals(ethc.laggingLagSize, 1)
assertEquals(ethc.syncingLagSize, 6)
assertEquals(ethc.id, "mainnet")
}
@Test
fun `read standard config with custom one`() {
val reader = ChainsConfigReader(ChainOptionsReader())
val chains = reader.read(this.javaClass.classLoader.getResourceAsStream("configs/chains-basic.yaml")!!)!!
val config = chains.resolve("fantom")
assertEquals(config.expectedBlockTime.seconds, 10)
assertEquals(config.chainId, "0xfb")
assertEquals(config.syncingLagSize, 11)
assertEquals(config.laggingLagSize, 4)
assertEquals(config.code, "FTA")
assertEquals(config.grpcId, 102)
assertEquals(config.netVersion, 251)
}
}

View File

@@ -0,0 +1,18 @@
version: v1
chain-settings:
protocols:
- id: fantom
settings:
expected-block-time: 10s
options:
validate-peers: true
lags:
syncing: 11
lagging: 4
chains:
- id: mainnet
short-names: [fantom]
code: FTA
grpcId: 102
chain-id: 0xfb