problem: reading yaml to both plain properties and structured objects is a mess

solution: use just objects for configs
This commit is contained in:
Igor Artamonov
2020-03-23 22:54:52 -04:00
parent d4bd180e23
commit 014461520e
37 changed files with 528 additions and 339 deletions

View File

@@ -77,7 +77,21 @@ proxy:
blockchain: kovan blockchain: kovan
upstreams: upstreams:
config: "upstreams.yaml" upstreams:
- id: infura-eth
chain: ethereum
connection:
ethereum:
rpc:
url: "https://mainnet.infura.io/v3/${INFURA_USER}"
ws:
url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}"
- id: infura-kovan
chain: kovan
connection:
ethereum:
rpc:
url: "https://kovan.infura.io/v3/${INFURA_USER}"
---- ----
Which sets the following: Which sets the following:
@@ -88,33 +102,7 @@ Which sets the following:
** proxying requests to Ethereum and Kovan upstreams ** proxying requests to Ethereum and Kovan upstreams
** request path for Ethereum Mainnet is `/eth`, for Kovan is `/kovan` ** request path for Ethereum Mainnet is `/eth`, for Kovan is `/kovan`
** i.e. call Mainnet by `POST http://127.0.0.0:8545/eth` with JSON RPC payload ** i.e. call Mainnet by `POST http://127.0.0.0:8545/eth` with JSON RPC payload
- read upstreams configuration from file `upstreams.yaml` in the current directory - sets up 2 upstreams, one for Ethereum Mainnet and another for Kovan Testnet (both upstreams are configured to use Infura endpoint)
Now create file `upstreams.yaml`:
[source,yaml]
----
version: v1
upstreams:
- id: infura-eth
chain: ethereum
connection:
ethereum:
rpc:
url: "https://mainnet.infura.io/v3/${INFURA_USER}"
ws:
url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}"
- id: infura-kovan
chain: kovan
connection:
ethereum:
rpc:
url: "https://kovan.infura.io/v3/${INFURA_USER}"
----
This configures:
- setups 2 upstreams, one for Ethereum Mainnet and another for Kovan Testnet (both upstreams are configured to use Infura endpoint)
- for Ethereum Mainnet it connects using JSON RPC and Websockets connections, for Kovan just JSON RPC is used - for Ethereum Mainnet it connects using JSON RPC and Websockets connections, for Kovan just JSON RPC is used
- Infura authentication config is omitted for this demo - Infura authentication config is omitted for this demo
- `${INFURA_USER}` will be provided through environment variable - `${INFURA_USER}` will be provided through environment variable

View File

@@ -2,5 +2,20 @@ version: v1
port: 2449 port: 2449
tls: tls:
enabled: false enabled: false
upstreams: upstreams:
config: upstreams.yaml upstreams:
- id: infura-eth
chain: ethereum
connection:
ethereum:
rpc:
url: "https://mainnet.infura.io/v3/${INFURA_USER}"
ws:
url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}"
- id: infura-kovan
chain: kovan
connection:
ethereum:
rpc:
url: "https://kovan.infura.io/v3/${INFURA_USER}"

View File

@@ -1,16 +0,0 @@
version: v1
upstreams:
- id: infura-eth
chain: ethereum
connection:
ethereum:
rpc:
url: "https://mainnet.infura.io/v3/${INFURA_USER}"
ws:
url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}"
- id: infura-kovan
chain: kovan
connection:
ethereum:
rpc:
url: "https://kovan.infura.io/v3/${INFURA_USER}"

View File

@@ -17,6 +17,7 @@ Create file `dshackle.yaml` with following content:
[source,yaml] [source,yaml]
---- ----
version: v1 version: v1
host: 0.0.0.0
port: 2449 port: 2449
tls: tls:
enabled: false enabled: false
@@ -29,39 +30,28 @@ proxy:
- id: kovan - id: kovan
blockchain: kovan blockchain: kovan
upstreams: upstreams:
config: "upstreams.yaml" upstreams:
- id: infura-eth
chain: ethereum
connection:
ethereum:
rpc:
url: "https://mainnet.infura.io/v3/${INFURA_USER}"
ws:
url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}"
- id: infura-kovan
chain: kovan
connection:
ethereum:
rpc:
url: "https://kovan.infura.io/v3/${INFURA_USER}"
---- ----
This very basic config says that: This very basic config says that:
- application will listen for connections on 0.0.0.0:2449 - application listen for gRPC connections on 0.0.0.0:2449, with TLS security disabled (_never use in production!_)
- TLS security should be disabled (_never use in production!_) - listen for HTTP JSON RPC connections on 0.0.0.0:8545, without TLS security too (again, _don't use in production, it's insecure_)
- read upstreams configuration from file `upstreams.yaml` in current directory - sets up 2 upstreams, one for Ethereum Mainnet and another for Kovan Testnet (both upstreams are configured for Infura for demo purposes, but you can use other compatible endpoints)
Now create file `upstreams.yaml`:
[source,yaml]
----
version: v1
upstreams:
- id: infura-eth
chain: ethereum
connection:
ethereum:
rpc:
url: "https://mainnet.infura.io/v3/${INFURA_USER}"
ws:
url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}"
- id: infura-kovan
chain: kovan
connection:
ethereum:
rpc:
url: "https://kovan.infura.io/v3/${INFURA_USER}"
----
This config:
- setups 2 upstreams, one for Ethereum Mainnet and another for Kovan Testnet (both upstreams are configured for Infura for demo purposes, but you can use other compatible endpoints)
- for Ethereum Mainnet it connects using JSON RPC and Websockets connections, for Kovan just JSON RPC is used - for Ethereum Mainnet it connects using JSON RPC and Websockets connections, for Kovan just JSON RPC is used
- Infura authentication config is omitted for this demo - Infura authentication config is omitted for this demo
- `${INFURA_USER}` value can be provided through environment variables - `${INFURA_USER}` value can be provided through environment variables

View File

@@ -17,7 +17,7 @@ tls:
require: true require: true
ca: "ca.crt" ca: "ca.crt"
upstreams: upstreams:
config: "upstreams.yaml" include: "upstreams.yaml"
---- ----
It configures following: It configures following:

View File

@@ -17,7 +17,7 @@ Those protocols can be configures with additional security, TLS and authenticati
---- ----
version: v1 version: v1
defaultOptions: defaults:
- chains: - chains:
- ethereum - ethereum
options: options:

View File

@@ -12,7 +12,7 @@ docker run -p 2449:2449 -v $(pwd):/config -w /config emeraldpay/dshackle
=== Install & Run manually === Install & Run manually
1. Download latest release from https://github.com/emeraldpay/dshackle/releases 1. Download latest release from https://github.com/emeraldpay/dshackle/releases
2. Unpack `unzip dshackle-0.3.zip` 2. Unpack `unzip dshackle-0.6.zip`
3. Copy to `/opt/dshackle` 3. Copy to `/opt/dshackle`
4. Setup configuration in `/etc/dshackle` 4. Setup configuration in `/etc/dshackle`
5. Run as `cp /etc/dshackle && /opt/dshackle/bin/dshackle` 5. Run as `cp /etc/dshackle && /opt/dshackle/bin/dshackle`

View File

@@ -19,13 +19,14 @@ import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.module.SimpleModule
import io.lettuce.core.AbstractRedisClient import io.emeraldpay.dshackle.config.CacheConfig
import io.lettuce.core.cluster.RedisClusterClient import io.emeraldpay.dshackle.config.MainConfig
import io.emeraldpay.dshackle.config.MainConfigReader
import io.emeraldpay.dshackle.config.UpstreamsConfig
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.*
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment import org.springframework.core.env.Environment
import org.springframework.scheduling.annotation.EnableAsync import org.springframework.scheduling.annotation.EnableAsync
import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.scheduling.annotation.EnableScheduling
@@ -43,11 +44,37 @@ open class Config(
@Autowired private val env: Environment @Autowired private val env: Environment
) { ) {
private val log = LoggerFactory.getLogger(Config::class.java) companion object {
private val log = LoggerFactory.getLogger(Config::class.java)
private const val DEFAULT_CONFIG = "/etc/dshackle/dshackle.yaml"
private const val LOCAL_CONFIG = "./dshackle.yaml"
}
private var configFilePath: File? = null
init {
configFilePath = getConfigPath()
}
fun getConfigPath(): File {
env.getProperty("configPath")?.let {
return File(it).normalize()
}
var target = File(DEFAULT_CONFIG)
if (!FileResolver.isAccessible(target)) {
target = File(LOCAL_CONFIG)
if (!FileResolver.isAccessible(target)) {
throw IllegalStateException("Configuration is not found neither at ${DEFAULT_CONFIG} nor ${LOCAL_CONFIG}")
}
}
target = target.normalize()
return target
}
@Bean @Bean
open fun objectMapper(): ObjectMapper { open fun objectMapper(): ObjectMapper {
val module = SimpleModule("EmeraldDShackle", Version(1, 0, 0, null, null, null)) val module = SimpleModule("EmeraldDshackle", Version(1, 0, 0, null, null, null))
val objectMapper = ObjectMapper() val objectMapper = ObjectMapper()
objectMapper.registerModule(module) objectMapper.registerModule(module)
@@ -65,23 +92,28 @@ open class Config(
} }
@Bean @Bean
@Qualifier("configDir") open fun mainConfig(@Autowired fileResolver: FileResolver): MainConfig {
open fun configDir(): File { val f = configFilePath ?: throw IllegalStateException("Config path is not set")
val config = env.getProperty("configPath") ?: throw IllegalStateException("Config path is not set") log.info("Using config: ${f.absolutePath}")
if (config.trim().isEmpty()) { val reader = MainConfigReader(fileResolver)
throw IllegalStateException("Config path is empty") return reader.read(f.inputStream())
} ?: throw IllegalStateException("Config is not available at ${f.absolutePath}")
log.info("Use configuration from: $config")
return File(config).parentFile
} }
@Bean @Bean
open fun fileResolver(): FileResolver { open fun fileResolver(): FileResolver {
return FileResolver(configDir()) val f = configFilePath ?: throw IllegalStateException("Config path is not set")
return FileResolver(f.absoluteFile.parentFile)
} }
@Bean @Bean
open fun redisClient(): AbstractRedisClient { open fun upstreamsConfig(@Autowired mainConfig: MainConfig): UpstreamsConfig? {
return RedisClusterClient.create("redis://password@localhost:6379/0"); return mainConfig.upstreams
} }
@Bean
open fun cacheConfig(@Autowired mainConfig: MainConfig): CacheConfig {
return mainConfig.cache ?: CacheConfig()
}
} }

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2019 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
import io.emeraldpay.dshackle.config.ProxyConfig
import io.emeraldpay.dshackle.config.ProxyConfigReader
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean
import org.springframework.core.env.*
import org.springframework.core.io.FileSystemResource
import org.springframework.core.io.Resource
import org.springframework.core.io.support.ResourcePropertySource
import java.io.File
import java.util.*
const val DEFAULT_CONFIG = "/etc/dshackle/dshackle.yaml"
const val LOCAL_CONFIG = "./dshackle.yaml"
open class DshackleEnvironment: StandardEnvironment() {
companion object {
private val log = LoggerFactory.getLogger(DshackleEnvironment::class.java)
}
override fun customizePropertySources(propertySources: MutablePropertySources) {
super.customizePropertySources(propertySources)
propertySources.addLast(mainConfig())
propertySources.addLast(ResourcePropertySource("version.properties"))
}
open fun getResource(): File? {
var target = File(DEFAULT_CONFIG)
if (!isAcceptedConfig(target)) {
target = File(LOCAL_CONFIG)
if (!isAcceptedConfig(target)) {
log.error("Configuration is not found neither at $DEFAULT_CONFIG nor $LOCAL_CONFIG")
return null
}
}
target = target.normalize()
return target
}
open fun mainConfig(): PropertySource<*> {
val target = getResource() ?: return PropertySource.named("mainConfig")
val resource = FileSystemResource(target)
val loadedProperties = this.loadYaml(resource)
loadedProperties["configPath"] = target.absolutePath
loadedProperties[ProxyConfig.CONFIG_ID] = extractYamlProxy(resource)
return PropertiesPropertySource("mainConfig", loadedProperties)
}
protected fun loadYaml(resource: Resource): Properties {
val factory = YamlPropertiesFactoryBean()
factory.setResources(resource)
factory.afterPropertiesSet()
return factory.getObject()!!
}
protected fun extractYamlProxy(resource: Resource): ProxyConfig? {
val reader = ProxyConfigReader()
return reader.read(resource.inputStream)
}
protected fun isAcceptedConfig(target: File): Boolean {
return target.exists() && target.isFile
}
}

View File

@@ -21,6 +21,12 @@ class FileResolver(
private val baseDir: File private val baseDir: File
) { ) {
companion object {
fun isAccessible(file: File): Boolean {
return file.exists() && file.isFile && file.canRead()
}
}
fun resolve(path: String): File { fun resolve(path: String): File {
val direct = File(path) val direct = File(path)
if (direct.isAbsolute) { if (direct.isAbsolute) {

View File

@@ -15,25 +15,27 @@
*/ */
package io.emeraldpay.dshackle package io.emeraldpay.dshackle
import io.emeraldpay.dshackle.config.MainConfig
import io.grpc.* import io.grpc.*
import io.grpc.netty.GrpcSslContexts import io.grpc.netty.GrpcSslContexts
import io.grpc.netty.NettyServerBuilder import io.grpc.netty.NettyServerBuilder
import io.netty.handler.ssl.ClientAuth import io.netty.handler.ssl.ClientAuth
import io.netty.handler.ssl.SslContext
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.env.Environment import org.springframework.core.env.Environment
import org.springframework.core.io.ResourceLoader import org.springframework.core.io.ResourceLoader
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.net.InetSocketAddress
import javax.annotation.PostConstruct import javax.annotation.PostConstruct
import javax.annotation.PreDestroy import javax.annotation.PreDestroy
@Service @Service
open class GrpcServer( open class GrpcServer(
@Autowired val rpcs: List<io.grpc.BindableService>, @Autowired val rpcs: List<io.grpc.BindableService>,
@Autowired val resourceLoader: ResourceLoader, @Autowired val mainConfig: MainConfig,
@Autowired val env: Environment, @Autowired val tlsSetup: TlsSetup
@Autowired val fileResolver: FileResolver
) { ) {
private val log = LoggerFactory.getLogger(GrpcServer::class.java) private val log = LoggerFactory.getLogger(GrpcServer::class.java)
@@ -42,54 +44,17 @@ open class GrpcServer(
@PostConstruct @PostConstruct
fun start() { fun start() {
log.info("Starting GRPC Server...") log.info("Starting gRPC Server...")
log.debug("Running with DEBUG LOGGING") log.debug("Running with DEBUG LOGGING")
val port = env.getProperty("port", "2449").toInt() log.info("Listening Native gRPC on ${mainConfig.host}:${mainConfig.port}")
log.info("Listening Native on 0.0.0.0:$port") val serverBuilder = NettyServerBuilder
val serverBuilder = NettyServerBuilder.forPort(port) .forAddress(InetSocketAddress(mainConfig.host, mainConfig.port))
rpcs.forEach { tlsSetup.setupServer("Native gRPC", mainConfig.tls, true)?.let {
serverBuilder.addService(it) serverBuilder.sslContext(it)
} }
val mustBeSecure = env.getProperty("tls.enabled", "") == "true" rpcs.forEach {
val tlsDisabled = env.getProperty("tls.enabled", "") == "false" serverBuilder.addService(it)
var hasServerCertificate = true
if (!tlsDisabled) {
if (StringUtils.isEmpty(env.getProperty("tls.server.certificate"))) {
if (mustBeSecure) {
log.warn("tls.server.certificate property is not set (path to server TLS certificate)")
System.exit(1)
}
hasServerCertificate = false
}
if (StringUtils.isEmpty(env.getProperty("tls.server.key"))) {
if (mustBeSecure) {
log.warn("tls.server.key property is not set (path to server TLS certificate key)")
System.exit(1)
}
hasServerCertificate = false
}
}
if (mustBeSecure || (!tlsDisabled && hasServerCertificate)) {
log.info("Using TLS")
val sslContextBuilder = GrpcSslContexts.forServer(
fileResolver.resolve(env.getProperty("tls.server.certificate")!!),
fileResolver.resolve(env.getProperty("tls.server.key")!!)
)
if (StringUtils.isNotEmpty(env.getProperty("tls.client.ca"))) {
log.info("Using TLS for client authentication")
sslContextBuilder.trustManager(
fileResolver.resolve(env.getProperty("tls.client.ca")!!)
)
if (env.getProperty("tls.client.require", "true") == "true") {
sslContextBuilder.clientAuth(ClientAuth.REQUIRE)
}
} else {
log.warn("Trust all clients")
}
serverBuilder.sslContext(sslContextBuilder.build())
} else {
log.warn("Using insecure transport")
} }
val server = serverBuilder.build() val server = serverBuilder.build()

View File

@@ -15,6 +15,7 @@
*/ */
package io.emeraldpay.dshackle package io.emeraldpay.dshackle
import io.emeraldpay.dshackle.config.MainConfig
import io.emeraldpay.dshackle.config.ProxyConfig import io.emeraldpay.dshackle.config.ProxyConfig
import io.emeraldpay.dshackle.proxy.ProxyServer import io.emeraldpay.dshackle.proxy.ProxyServer
import io.emeraldpay.dshackle.proxy.ReadRpcJson import io.emeraldpay.dshackle.proxy.ReadRpcJson
@@ -31,7 +32,7 @@ import javax.annotation.PostConstruct
*/ */
@Service @Service
class ProxyStarter( class ProxyStarter(
@Autowired private val env: Environment, @Autowired private val mainConfig: MainConfig,
@Autowired private val readRpcJson: ReadRpcJson, @Autowired private val readRpcJson: ReadRpcJson,
@Autowired private val writeRpcJson: WriteRpcJson, @Autowired private val writeRpcJson: WriteRpcJson,
@Autowired private val nativeCall: NativeCall, @Autowired private val nativeCall: NativeCall,
@@ -44,7 +45,7 @@ class ProxyStarter(
@PostConstruct @PostConstruct
fun start() { fun start() {
val config = env.getProperty(ProxyConfig.CONFIG_ID, ProxyConfig::class.java) val config = mainConfig.proxy
if (config == null) { if (config == null) {
log.debug("Proxy server is not configured") log.debug("Proxy server is not configured")
return return

View File

@@ -21,6 +21,7 @@ import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Import import org.springframework.context.annotation.Import
import org.springframework.core.io.ClassPathResource import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.support.ResourcePropertySource
@SpringBootApplication(scanBasePackages = [ "io.emeraldpay.dshackle" ]) @SpringBootApplication(scanBasePackages = [ "io.emeraldpay.dshackle" ])
@Import(Config::class) @Import(Config::class)
@@ -30,7 +31,7 @@ private val log = LoggerFactory.getLogger(Starter::class.java)
fun main(args: Array<String>) { fun main(args: Array<String>) {
val app = SpringApplication(Starter::class.java) val app = SpringApplication(Starter::class.java)
app.setEnvironment(DshackleEnvironment()) app.setDefaultProperties(ResourcePropertySource("version.properties").source)
app.setBanner(ResourceBanner(ClassPathResource("banner.txt"))) app.setBanner(ResourceBanner(ClassPathResource("banner.txt")))
app.run(*args) app.run(*args)
} }

View File

@@ -34,7 +34,11 @@ class TlsSetup(
private val log = LoggerFactory.getLogger(TlsSetup::class.java) private val log = LoggerFactory.getLogger(TlsSetup::class.java)
} }
fun setupServer(category: String, config: AuthConfig.ServerTlsAuth): SslContext? { fun setupServer(category: String, config: AuthConfig.ServerTlsAuth?, grpc: Boolean): SslContext? {
if (config == null) {
log.warn("Using insecure transport for $category")
return null
}
val mustBeSecure = config.enabled != null && config.enabled!! val mustBeSecure = config.enabled != null && config.enabled!!
val tlsDisabled = config.enabled != null && !config.enabled!! val tlsDisabled = config.enabled != null && !config.enabled!!
var hasServerCertificate = true var hasServerCertificate = true
@@ -56,10 +60,17 @@ class TlsSetup(
} }
if (mustBeSecure || (!tlsDisabled && hasServerCertificate)) { if (mustBeSecure || (!tlsDisabled && hasServerCertificate)) {
log.info("Using TLS for $category") log.info("Using TLS for $category")
val sslContextBuilder = SslContextBuilder.forServer( val sslContextBuilder = if (grpc) {
fileResolver.resolve(config.certificate!!), GrpcSslContexts.forServer(
fileResolver.resolve(config.key!!) fileResolver.resolve(config.certificate!!),
) fileResolver.resolve(config.key!!)
)
} else {
SslContextBuilder.forServer(
fileResolver.resolve(config.certificate!!),
fileResolver.resolve(config.key!!)
)
}
if (StringUtils.isNotEmpty(config.clientCa)) { if (StringUtils.isNotEmpty(config.clientCa)) {
log.info("Using TLS for client authentication for $category") log.info("Using TLS for client authentication for $category")
sslContextBuilder.trustManager( sslContextBuilder.trustManager(

View File

@@ -1,43 +0,0 @@
/**
* Copyright (c) 2019 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
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean
import org.springframework.core.env.PropertiesPropertySource
import org.springframework.core.env.PropertySource
import org.springframework.core.io.Resource
import org.springframework.core.io.support.EncodedResource
import org.springframework.core.io.support.PropertySourceFactory
import java.util.*
class YamlPropertySourceFactory: PropertySourceFactory {
override fun createPropertySource(name: String?, resource: EncodedResource): PropertySource<*> {
val loadedProperties = this.loadYamlIntoProperties(resource.resource)
return PropertiesPropertySource(if (StringUtils.isNotBlank(name)) name else resource.resource.filename, loadedProperties)
}
private fun loadYamlIntoProperties(resource: Resource): Properties {
val factory = YamlPropertiesFactoryBean()
factory.setResources(resource)
factory.afterPropertiesSet()
return factory.getObject()
}
}

View File

@@ -1,6 +1,7 @@
package io.emeraldpay.dshackle.cache package io.emeraldpay.dshackle.cache
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.config.CacheConfig
import io.emeraldpay.dshackle.config.EnvVariables import io.emeraldpay.dshackle.config.EnvVariables
import io.emeraldpay.grpc.Chain import io.emeraldpay.grpc.Chain
import io.lettuce.core.RedisClient import io.lettuce.core.RedisClient
@@ -20,7 +21,7 @@ import kotlin.collections.HashMap
@Repository @Repository
class CachesFactory( class CachesFactory(
@Autowired private val objectMapper: ObjectMapper, @Autowired private val objectMapper: ObjectMapper,
@Autowired private val env: Environment @Autowired private val cacheConfig: CacheConfig
) { ) {
companion object { companion object {
@@ -33,24 +34,20 @@ class CachesFactory(
@PostConstruct @PostConstruct
fun init() { fun init() {
if (!env.getProperty("${CONFIG_PREFIX}.enabled", Boolean::class.java, false)) { val redisConfig = cacheConfig.redis ?: return
return
}
val address = env.getProperty("${CONFIG_PREFIX}.host", "127.0.0.1")
val port = env.getProperty("${CONFIG_PREFIX}.port", Int::class.java, 6379)
var uri = RedisURI.builder() var uri = RedisURI.builder()
.withHost(address) .withHost(redisConfig.host)
.withPort(port) .withPort(redisConfig.port)
env.getProperty("${CONFIG_PREFIX}.db", Int::class.java)?.let { value -> redisConfig.db?.let { value ->
uri = uri.withDatabase(value) uri = uri.withDatabase(value)
} }
//log URI _before_ adding a password, to avoid leaking it to the log //log URI _before_ adding a password, to avoid leaking it to the log
log.info("Use Redis cache at: ${uri.build().toURI()}") log.info("Use Redis cache at: ${uri.build().toURI()}")
env.getProperty("${CONFIG_PREFIX}.password")?.let { value -> redisConfig.password?.let { value ->
uri = uri.withPassword(value) uri = uri.withPassword(value)
} }

View File

@@ -65,6 +65,9 @@ class AuthConfigReader : YamlConfigReader() {
getValueAsBool(node, "enabled")?.let { getValueAsBool(node, "enabled")?.let {
auth.enabled = it auth.enabled = it
} }
if (auth.enabled != null && !auth.enabled!!) {
return null
}
getMapping(node, "server")?.let { node -> getMapping(node, "server")?.let { node ->
auth.certificate = getValueAsString(node, "certificate") auth.certificate = getValueAsString(node, "certificate")
auth.key = getValueAsString(node, "key") auth.key = getValueAsString(node, "key")

View File

@@ -0,0 +1,13 @@
package io.emeraldpay.dshackle.config
class CacheConfig {
var redis: Redis? = null;
class Redis(
var host: String = "127.0.0.1",
var port: Int = 6379,
var db: Int? = 0,
var password: String? = null
)
}

View File

@@ -0,0 +1,43 @@
package io.emeraldpay.dshackle.config
import org.slf4j.LoggerFactory
import org.yaml.snakeyaml.nodes.MappingNode
import java.io.InputStream
class CacheConfigReader : YamlConfigReader(), ConfigReader<CacheConfig> {
companion object {
private val log = LoggerFactory.getLogger(CacheConfigReader::class.java)
}
fun read(input: InputStream): CacheConfig? {
val configNode = readNode(input)
return read(configNode)
}
override fun read(input: MappingNode?): CacheConfig? {
return getMapping(input, "cache")?.let { node ->
val config = CacheConfig()
getMapping(node, "redis")?.let { node ->
val redis = CacheConfig.Redis()
getValueAsString(node, "host")?.let {
redis.host = it
}
getValueAsInt(node, "port")?.let {
redis.port = it
}
getValueAsInt(node, "db")?.let {
redis.db = it
}
getValueAsString(node, "password")?.let {
redis.password = it
}
config.redis = redis
}
if (config.redis == null) {
return null
}
config
}
}
}

View File

@@ -0,0 +1,9 @@
package io.emeraldpay.dshackle.config
import org.yaml.snakeyaml.nodes.MappingNode
interface ConfigReader<T> {
fun read(input: MappingNode?): T?
}

View File

@@ -0,0 +1,11 @@
package io.emeraldpay.dshackle.config
class MainConfig {
var host = "127.0.0.1"
var port = 2449
var tls: AuthConfig.ServerTlsAuth? = null
var cache: CacheConfig? = null
var proxy: ProxyConfig? = null
var upstreams: UpstreamsConfig? = null
}

View File

@@ -0,0 +1,50 @@
package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.FileResolver
import org.slf4j.LoggerFactory
import org.yaml.snakeyaml.nodes.MappingNode
import java.io.InputStream
class MainConfigReader(
fileResolver: FileResolver
) : YamlConfigReader(), ConfigReader<MainConfig> {
companion object {
private val log = LoggerFactory.getLogger(MainConfigReader::class.java)
}
private val authConfigReader = AuthConfigReader()
private val proxyConfigReader = ProxyConfigReader()
private val upstreamsConfigReader = UpstreamsConfigReader(fileResolver)
private val cacheConfigReader = CacheConfigReader()
fun read(input: InputStream): MainConfig? {
val configNode = readNode(input)
return read(configNode)
}
override fun read(input: MappingNode?): MainConfig? {
val config = MainConfig()
getValueAsString(input, "host")?.let {
config.host = it
}
getValueAsInt(input, "port")?.let {
config.port = it
}
authConfigReader.readServerTls(input)?.let {
config.tls = it
}
proxyConfigReader.read(input)?.let {
config.proxy = it
}
upstreamsConfigReader.read(input)?.let {
config.upstreams = it
}
cacheConfigReader.read(input)?.let {
config.cache = it
}
return config
}
}

View File

@@ -26,7 +26,7 @@ import java.io.InputStreamReader
/** /**
* Read YAML config, part related to Proxy configuration * Read YAML config, part related to Proxy configuration
*/ */
class ProxyConfigReader : YamlConfigReader() { class ProxyConfigReader : YamlConfigReader(), ConfigReader<ProxyConfig> {
companion object { companion object {
private val log = LoggerFactory.getLogger(ProxyConfigReader::class.java) private val log = LoggerFactory.getLogger(ProxyConfigReader::class.java)
@@ -37,10 +37,14 @@ class ProxyConfigReader : YamlConfigReader() {
fun read(input: InputStream): ProxyConfig? { fun read(input: InputStream): ProxyConfig? {
val configNode = readNode(input) val configNode = readNode(input)
return read(getMapping(configNode, "proxy")) return read(configNode)
} }
fun read(input: MappingNode?): ProxyConfig? { override fun read(input: MappingNode?): ProxyConfig? {
return readInternal(getMapping(input, "proxy"))
}
fun readInternal(input: MappingNode?): ProxyConfig? {
if (input == null) { if (input == null) {
return null return null
} }

View File

@@ -22,7 +22,6 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
class UpstreamsConfig { class UpstreamsConfig {
var version: String? = null
var defaultOptions: MutableList<DefaultOptions> = ArrayList<DefaultOptions>() var defaultOptions: MutableList<DefaultOptions> = ArrayList<DefaultOptions>()
var upstreams: MutableList<Upstream<*>> = ArrayList<Upstream<*>>() var upstreams: MutableList<Upstream<*>> = ArrayList<Upstream<*>>()

View File

@@ -15,29 +15,38 @@
*/ */
package io.emeraldpay.dshackle.config package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.FileResolver
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.MappingNode import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.ScalarNode import org.yaml.snakeyaml.nodes.ScalarNode
import reactor.util.function.Tuples import reactor.util.function.Tuples
import java.io.InputStream import java.io.InputStream
import java.io.InputStreamReader
import java.net.URI import java.net.URI
import java.time.Duration import java.time.Duration
class UpstreamsConfigReader : YamlConfigReader() { class UpstreamsConfigReader(
private val fileResolver: FileResolver
) : YamlConfigReader(), ConfigReader<UpstreamsConfig> {
private val log = LoggerFactory.getLogger(UpstreamsConfigReader::class.java) private val log = LoggerFactory.getLogger(UpstreamsConfigReader::class.java)
private val authConfigReader = AuthConfigReader() private val authConfigReader = AuthConfigReader()
fun read(input: InputStream): UpstreamsConfig { fun read(input: InputStream): UpstreamsConfig? {
val configNode = readNode(input) val configNode = readNode(input)
return readInternal(configNode)
}
override fun read(input: MappingNode?): UpstreamsConfig? {
return getMapping(input, "upstreams")?.let {
readInternal(it)
}
}
fun readInternal(input: MappingNode?): UpstreamsConfig? {
val config = UpstreamsConfig() val config = UpstreamsConfig()
config.version = getValueAsString(configNode, "version")
getList<MappingNode>(configNode, "defaultOptions")?.value?.forEach { opts -> getList<MappingNode>(input, "defaults")?.value?.forEach { opts ->
val defaultOptions = UpstreamsConfig.DefaultOptions() val defaultOptions = UpstreamsConfig.DefaultOptions()
config.defaultOptions.add(defaultOptions) config.defaultOptions.add(defaultOptions)
defaultOptions.chains = getListOfString(opts, "chains") defaultOptions.chains = getListOfString(opts, "chains")
@@ -47,7 +56,32 @@ class UpstreamsConfigReader : YamlConfigReader() {
} }
config.upstreams = ArrayList<UpstreamsConfig.Upstream<*>>() config.upstreams = ArrayList<UpstreamsConfig.Upstream<*>>()
getList<MappingNode>(configNode, "upstreams")?.value?.forEachIndexed { pos, upNode ->
getValueAsString(input, "include")?.let { path ->
fileResolver.resolve(path).let { file ->
if (file.exists() && file.isFile && file.canRead()) {
read(file.inputStream())?.let {
it.upstreams.forEach { upstream -> config.upstreams.add(upstream) }
}
} else {
log.warn("Failed to read config from $path")
}
}
}
getListOfString(input, "include")?.forEach { path ->
fileResolver.resolve(path).let { file ->
if (file.exists() && file.isFile && file.canRead()) {
read(file.inputStream())?.let {
it.upstreams.forEach { upstream -> config.upstreams.add(upstream) }
}
} else {
log.warn("Failed to read config from $path")
}
}
}
getList<MappingNode>(input, "upstreams")?.value?.forEachIndexed { pos, upNode ->
val connNode = getMapping(upNode, "connection") val connNode = getMapping(upNode, "connection")
if (hasAny(connNode, "ethereum")) { if (hasAny(connNode, "ethereum")) {
val connConfigNode = getMapping(connNode, "ethereum")!! val connConfigNode = getMapping(connNode, "ethereum")!!
@@ -121,10 +155,10 @@ class UpstreamsConfigReader : YamlConfigReader() {
internal fun readUpstreamGrpc(upNode: MappingNode, upstream: UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>) { internal fun readUpstreamGrpc(upNode: MappingNode, upstream: UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>) {
if (hasAny(upNode, "labels")) { if (hasAny(upNode, "labels")) {
log.warn("Labels are not applied to gRPC upstream") log.warn("Labels should be not applied to gRPC upstream")
} }
if (hasAny(upNode, "chain")) { if (hasAny(upNode, "chain")) {
log.warn("Chain is not applied to gRPC upstream") log.warn("Chain should be not applied to gRPC upstream")
} }
} }

View File

@@ -24,7 +24,7 @@ import org.yaml.snakeyaml.nodes.ScalarNode
import java.io.InputStream import java.io.InputStream
import java.io.InputStreamReader import java.io.InputStreamReader
open class YamlConfigReader { abstract class YamlConfigReader {
private val envVariables = EnvVariables() private val envVariables = EnvVariables()
fun readNode(input: String): MappingNode { fun readNode(input: String): MappingNode {

View File

@@ -59,10 +59,8 @@ class ProxyServer(
.host(config.host) .host(config.host)
.port(config.port) .port(config.port)
config.tls?.let { tls -> tlsSetup.setupServer("proxy", config.tls, false)?.let { sslContext ->
tlsSetup.setupServer("proxy", tls)?.let { sslContext -> serverBuilder = serverBuilder.secure { secure -> secure.sslContext(sslContext) }
serverBuilder = serverBuilder.secure { secure -> secure.sslContext(sslContext) }
}
} }
val server: DisposableServer = serverBuilder val server: DisposableServer = serverBuilder

View File

@@ -40,10 +40,10 @@ import kotlin.system.exitProcess
@Repository @Repository
open class ConfiguredUpstreams( open class ConfiguredUpstreams(
@Autowired val env: Environment,
@Autowired private val objectMapper: ObjectMapper, @Autowired private val objectMapper: ObjectMapper,
@Autowired private val currentUpstreams: CurrentUpstreams, @Autowired private val currentUpstreams: CurrentUpstreams,
@Autowired private val fileResolver: FileResolver @Autowired private val fileResolver: FileResolver,
@Autowired private val config: UpstreamsConfig
) { ) {
private val log = LoggerFactory.getLogger(ConfiguredUpstreams::class.java) private val log = LoggerFactory.getLogger(ConfiguredUpstreams::class.java)
@@ -59,7 +59,6 @@ open class ConfiguredUpstreams(
@PostConstruct @PostConstruct
fun start() { fun start() {
val config = readConfig()
val defaultOptions = buildDefaultOptions(config) val defaultOptions = buildDefaultOptions(config)
config.upstreams.forEach { up -> config.upstreams.forEach { up ->
@@ -79,23 +78,6 @@ open class ConfiguredUpstreams(
} }
} }
private fun readConfig(): UpstreamsConfig {
val path = env.getProperty("upstreams.config")
if (StringUtils.isEmpty(path)) {
log.error("Path to upstreams is not set (upstreams.config)")
exitProcess(1)
}
val upstreamConfig = fileResolver.resolve(path!!).normalize()
val ok = upstreamConfig.exists() && upstreamConfig.isFile
if (!ok) {
log.error("Unable to setup upstreams from ${upstreamConfig.path}")
exitProcess(1)
}
log.info("Read upstream configuration from ${upstreamConfig.path}")
val reader = UpstreamsConfigReader()
return reader.read(upstreamConfig.inputStream())
}
private fun buildDefaultOptions(config: UpstreamsConfig): HashMap<Chain, UpstreamsConfig.Options> { private fun buildDefaultOptions(config: UpstreamsConfig): HashMap<Chain, UpstreamsConfig.Options> {
val defaultOptions = HashMap<Chain, UpstreamsConfig.Options>() val defaultOptions = HashMap<Chain, UpstreamsConfig.Options>()
config.defaultOptions.forEach { defaultsConfig -> config.defaultOptions.forEach { defaultsConfig ->

View File

@@ -31,7 +31,7 @@ class TlsSetupSpec extends Specification {
enabled: false enabled: false
) )
when: when:
def act = tlsSetup.setupServer("test", config) def act = tlsSetup.setupServer("test", config, false)
then: then:
act == null act == null
} }
@@ -44,7 +44,7 @@ class TlsSetupSpec extends Specification {
key: "127.0.0.1.p8.key" key: "127.0.0.1.p8.key"
) )
when: when:
def act = tlsSetup.setupServer("test", config) def act = tlsSetup.setupServer("test", config, false)
then: then:
act != null act != null
act.server act.server
@@ -67,7 +67,7 @@ class TlsSetupSpec extends Specification {
clientCa: "ca.myhost.dev.crt" clientCa: "ca.myhost.dev.crt"
) )
when: when:
def act = tlsSetup.setupServer("test", config) def act = tlsSetup.setupServer("test", config, false)
then: then:
act != null act != null
act.server act.server
@@ -87,7 +87,7 @@ class TlsSetupSpec extends Specification {
key: "127.0.0.1.p8.key", key: "127.0.0.1.p8.key",
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
t.message == "Certificate not set" t.message == "Certificate not set"
@@ -100,7 +100,7 @@ class TlsSetupSpec extends Specification {
certificate: "127.0.0.1.crt" certificate: "127.0.0.1.crt"
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
t.message == "Certificate Key not set" t.message == "Certificate Key not set"
@@ -114,7 +114,7 @@ class TlsSetupSpec extends Specification {
key: "127.0.0.1.p8.key", key: "127.0.0.1.p8.key",
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
} }
@@ -127,7 +127,7 @@ class TlsSetupSpec extends Specification {
key: "none.p8.key", key: "none.p8.key",
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
} }
@@ -140,7 +140,7 @@ class TlsSetupSpec extends Specification {
key: "127.0.0.1.key", key: "127.0.0.1.key",
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
} }
@@ -154,7 +154,7 @@ class TlsSetupSpec extends Specification {
clientRequire: true clientRequire: true
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
} }
@@ -169,7 +169,7 @@ class TlsSetupSpec extends Specification {
clientCa: "none.crt" clientCa: "none.crt"
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
} }
@@ -184,7 +184,7 @@ class TlsSetupSpec extends Specification {
clientCa: "ca.myhost.dev.key" clientCa: "ca.myhost.dev.key"
) )
when: when:
tlsSetup.setupServer("test", config) tlsSetup.setupServer("test", config, false)
then: then:
def t = thrown(IllegalArgumentException) def t = thrown(IllegalArgumentException)
} }

View File

@@ -0,0 +1,24 @@
package io.emeraldpay.dshackle.config
import spock.lang.Specification
class CacheConfigReaderSpec extends Specification {
CacheConfigReader reader = new CacheConfigReader()
def "Read full"() {
setup:
def config = this.class.getClassLoader().getResourceAsStream("cache-redis-full.yaml")
when:
def act = reader.read(config)
then:
act.redis != null
with(act.redis) {
host == "redis-master"
port == 1234
db == 5
password == "HelloWorld!1"
}
}
}

View File

@@ -0,0 +1,73 @@
package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.FileResolver
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.grpc.Chain
import spock.lang.Specification
class MainConfigReaderSpec extends Specification {
MainConfigReader reader = new MainConfigReader(TestingCommons.fileResolver())
def "Read full config"() {
setup:
def config = this.class.getClassLoader().getResourceAsStream("dshackle-full.yaml")
when:
def act = reader.read(config)
then:
act != null
act.host == "192.168.1.101"
act.port == 2448
act.tls != null
with(act.tls) {
certificate == "/path/127.0.0.1.crt"
key == "/path/127.0.0.1.p8.key"
!clientRequire
clientCa == "/path/ca.dshackle.test.crt"
}
act.cache != null
with(act.cache) {
redis != null
redis.host == "redis-master"
}
act.proxy != null
with(act.proxy) {
port == 8082
tls != null
routes != null
routes.size() == 3
with(routes[0]) {
id == "eth"
blockchain == Chain.ETHEREUM
}
with(routes[1]) {
id == "etc"
blockchain == Chain.ETHEREUM_CLASSIC
}
with(routes[2]) {
id == "kovan"
blockchain == Chain.TESTNET_KOVAN
}
}
act.upstreams != null
with(act.upstreams) {
defaultOptions != null
defaultOptions.size() == 1
with(defaultOptions[0]) {
chains == ["ethereum"]
options.minPeers == 3
}
upstreams.size() == 3
with(upstreams[0]) {
id == "remote"
}
with(upstreams[1]) {
id == "local"
}
with(upstreams[2]) {
id == "infura"
}
}
}
}

View File

@@ -23,7 +23,7 @@ import spock.lang.Specification
class UpstreamsConfigReaderSpec extends Specification { class UpstreamsConfigReaderSpec extends Specification {
UpstreamsConfigReader reader = new UpstreamsConfigReader() UpstreamsConfigReader reader = new UpstreamsConfigReader(TestingCommons.fileResolver())
def "Parse standard config"() { def "Parse standard config"() {
setup: setup:
@@ -32,7 +32,6 @@ class UpstreamsConfigReaderSpec extends Specification {
def act = reader.read(config) def act = reader.read(config)
then: then:
act != null act != null
act.version == "v1"
with(act.defaultOptions) { with(act.defaultOptions) {
size() == 1 size() == 1
with(get(0)) { with(get(0)) {
@@ -81,7 +80,6 @@ class UpstreamsConfigReaderSpec extends Specification {
def act = reader.read(config) def act = reader.read(config)
then: then:
act != null act != null
act.version == "v1"
act.upstreams.size() == 1 act.upstreams.size() == 1
with(act.upstreams.get(0)) { with(act.upstreams.get(0)) {
id == "remote" id == "remote"
@@ -141,7 +139,6 @@ class UpstreamsConfigReaderSpec extends Specification {
def act = reader.read(config) def act = reader.read(config)
then: then:
act != null act != null
act.version == "v1"
with(act.defaultOptions) { with(act.defaultOptions) {
size() == 0 size() == 0
} }

View File

@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule
import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.FileResolver
import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesFactory import io.emeraldpay.dshackle.cache.CachesFactory
import io.emeraldpay.dshackle.config.CacheConfig
import io.emeraldpay.dshackle.upstream.AggregatedUpstream import io.emeraldpay.dshackle.upstream.AggregatedUpstream
import io.emeraldpay.dshackle.upstream.ChainUpstreams import io.emeraldpay.dshackle.upstream.ChainUpstreams
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
@@ -78,7 +79,7 @@ class TestingCommons {
} }
static CachesFactory emptyCaches() { static CachesFactory emptyCaches() {
return new CachesFactory(objectMapper(), new StandardEnvironment()) return new CachesFactory(objectMapper(), new CacheConfig())
} }
static FileResolver fileResolver() { static FileResolver fileResolver() {

View File

@@ -0,0 +1,7 @@
cache:
redis:
enabled: true
host: redis-master
port: 1234
db: 5
password: HelloWorld!1

View File

@@ -0,0 +1,66 @@
version: v1
host: 192.168.1.101
port: 2448
tls:
enabled: true
server:
certificate: "/path/127.0.0.1.crt"
key: "/path/127.0.0.1.p8.key"
client:
require: false
ca: "/path/ca.dshackle.test.crt"
cache:
redis:
enabled: true
host: redis-master
proxy:
port: 8082
tls:
enabled: true
server:
certificate: "/path/second/127.0.0.1.crt"
key: "/path/second/127.0.0.1.p8.key"
client:
require: false
ca: "/path/second/ca.dshackle.test.crt"
routes:
- id: eth
blockchain: ethereum
- id: etc
blockchain: ethereum_classic
- id: kovan
blockchain: kovan
upstreams:
defaults:
- chains:
- ethereum
options:
min-peers: 3
include:
- "upstreams-extra.yaml"
upstreams:
- id: local
chain: ethereum
connection:
ethereum:
rpc:
url: "http://localhost:8545"
ws:
url: "ws://localhost:8546"
origin: "http://localhost"
basic-auth:
username: 9c199ad8f281f20154fc258fe41a6814
password: 258fe4149c199ad8f2811a68f20154fc
- id: infura
chain: ethereum
connection:
ethereum:
rpc:
url: "https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2"
basic-auth:
username: 4fc258fe41a68149c199ad8f281f2015
password: 1a68f20154fc258fe4149c199ad8f281

View File

@@ -1,6 +1,6 @@
version: v1 version: v1
defaultOptions: defaults:
- chains: - chains:
- ethereum - ethereum
options: options:

View File

@@ -0,0 +1,9 @@
upstreams:
- id: remote
connection:
grpc:
host: "10.2.0.15"
tls:
ca: /etc/ca.myservice.com.crt
certificate: /etc/client1.myservice.com.crt
key: /etc/client1.myservice.com.key