problem: reading yaml to both plain properties and structured objects is a mess
solution: use just objects for configs
This commit is contained in:
44
README.adoc
44
README.adoc
@@ -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
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
|||||||
@@ -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}"
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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`
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
13
src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfig.kt
Normal file
13
src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfig.kt
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package io.emeraldpay.dshackle.config
|
||||||
|
|
||||||
|
import org.yaml.snakeyaml.nodes.MappingNode
|
||||||
|
|
||||||
|
interface ConfigReader<T> {
|
||||||
|
|
||||||
|
fun read(input: MappingNode?): T?
|
||||||
|
|
||||||
|
}
|
||||||
11
src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt
Normal file
11
src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<*>>()
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ->
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
7
src/test/resources/cache-redis-full.yaml
Normal file
7
src/test/resources/cache-redis-full.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cache:
|
||||||
|
redis:
|
||||||
|
enabled: true
|
||||||
|
host: redis-master
|
||||||
|
port: 1234
|
||||||
|
db: 5
|
||||||
|
password: HelloWorld!1
|
||||||
66
src/test/resources/dshackle-full.yaml
Normal file
66
src/test/resources/dshackle-full.yaml
Normal 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
version: v1
|
version: v1
|
||||||
|
|
||||||
defaultOptions:
|
defaults:
|
||||||
- chains:
|
- chains:
|
||||||
- ethereum
|
- ethereum
|
||||||
options:
|
options:
|
||||||
|
|||||||
9
src/test/resources/upstreams-extra.yaml
Normal file
9
src/test/resources/upstreams-extra.yaml
Normal 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
|
||||||
Reference in New Issue
Block a user