problem: allows multiple different endpoints for same upstream
solution: simplify config, allow to setup only one connection type per upstream
This commit is contained in:
@@ -1,448 +0,0 @@
|
||||
package io.emeraldpay.dshackle.config;
|
||||
|
||||
import org.yaml.snakeyaml.TypeDescription;
|
||||
import org.yaml.snakeyaml.introspector.GenericProperty;
|
||||
import org.yaml.snakeyaml.introspector.MethodProperty;
|
||||
import org.yaml.snakeyaml.introspector.Property;
|
||||
import org.yaml.snakeyaml.introspector.PropertySubstitute;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class UpstreamsConfig {
|
||||
|
||||
private String version;
|
||||
private List<DefaultOptions> defaultOptions;
|
||||
private List<Upstream> upstreams;
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public List<DefaultOptions> getDefaultOptions() {
|
||||
return defaultOptions;
|
||||
}
|
||||
|
||||
public void setDefaultOptions(List<DefaultOptions> defaultOptions) {
|
||||
this.defaultOptions = defaultOptions;
|
||||
}
|
||||
|
||||
public List<Upstream> getUpstreams() {
|
||||
return upstreams;
|
||||
}
|
||||
|
||||
public void setUpstreams(List<Upstream> upstreams) {
|
||||
this.upstreams = upstreams;
|
||||
}
|
||||
|
||||
public static class Options {
|
||||
private Boolean disableSyncing;
|
||||
private Integer minPeers;
|
||||
private Integer quorum;
|
||||
|
||||
public Boolean getDisableSyncing() {
|
||||
return disableSyncing;
|
||||
}
|
||||
|
||||
public void setDisableSyncing(Boolean disableSyncing) {
|
||||
this.disableSyncing = disableSyncing;
|
||||
}
|
||||
|
||||
public Integer getMinPeers() {
|
||||
return minPeers;
|
||||
}
|
||||
|
||||
public void setMinPeers(Integer minPeers) {
|
||||
if (minPeers < 0) {
|
||||
throw new IllegalArgumentException("minPeers must be positive number");
|
||||
}
|
||||
this.minPeers = minPeers;
|
||||
}
|
||||
|
||||
public Integer getQuorum() {
|
||||
return quorum;
|
||||
}
|
||||
|
||||
public void setQuorum(Integer quorum) {
|
||||
if (quorum < 0) {
|
||||
throw new IllegalArgumentException("quorum must be positive number");
|
||||
}
|
||||
this.quorum = quorum;
|
||||
}
|
||||
|
||||
public Options merge(Options additional) {
|
||||
if (additional == null) {
|
||||
return this;
|
||||
}
|
||||
Options copy = new Options();
|
||||
copy.setDisableSyncing(this.disableSyncing != null ? this.disableSyncing : additional.disableSyncing);
|
||||
copy.setMinPeers(this.minPeers != null ? this.minPeers : additional.minPeers);
|
||||
copy.setQuorum(this.quorum != null ? this.quorum : additional.quorum);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static Options getDefaults() {
|
||||
Options options = new Options();
|
||||
options.setDisableSyncing(true);
|
||||
options.setMinPeers(1);
|
||||
options.setQuorum(1);
|
||||
return options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class OptionsYaml extends TypeDescription {
|
||||
|
||||
public OptionsYaml() {
|
||||
super(Options.class);
|
||||
super.substituteProperty(new PropertySubstitute("disable-syncing", Boolean.class,
|
||||
"getDisableSyncing", "setDisableSyncing"));
|
||||
super.substituteProperty(new PropertySubstitute("min-peers", Integer.class,
|
||||
"getMinPeers", "setMinPeers"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class DefaultOptions extends Options {
|
||||
private List<String> chains;
|
||||
private Options options;
|
||||
|
||||
public List<String> getChains() {
|
||||
return chains;
|
||||
}
|
||||
|
||||
public void setChains(List<String> chains) {
|
||||
this.chains = chains;
|
||||
}
|
||||
|
||||
public Options getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(Options options) {
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Upstream {
|
||||
private String id;
|
||||
private String chain;
|
||||
|
||||
@Nullable
|
||||
private String provider;
|
||||
private List<Endpoint> endpoints = Collections.emptyList();
|
||||
private Options options;
|
||||
private boolean enabled = true;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getChain() {
|
||||
return chain;
|
||||
}
|
||||
|
||||
public void setChain(String chain) {
|
||||
this.chain = chain;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public void setProvider(@Nullable String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public List<Endpoint> getEndpoints() {
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
public void setEndpoints(List<Endpoint> endpoints) {
|
||||
this.endpoints = endpoints;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Options getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(Options options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Endpoint {
|
||||
private EndpointType type;
|
||||
private URI url;
|
||||
private String host;
|
||||
private int port;
|
||||
@Nullable
|
||||
private Auth auth;
|
||||
private Boolean enabled = true;
|
||||
@Nullable
|
||||
private URI origin;
|
||||
|
||||
public EndpointType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(EndpointType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public URI getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(URI url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Auth getAuth() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
public void setAuth(@Nullable Auth auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public URI getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public void setOrigin(@Nullable URI origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Auth {
|
||||
private String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface WithKey {
|
||||
public String getKey();
|
||||
public void setKey(String key);
|
||||
}
|
||||
|
||||
public static class BasicAuth extends Auth implements WithKey {
|
||||
private String key;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TlsAuth extends Auth implements WithKey {
|
||||
private String ca;
|
||||
private String certificate;
|
||||
private String key;
|
||||
|
||||
public String getCa() {
|
||||
return ca;
|
||||
}
|
||||
|
||||
public void setCa(String ca) {
|
||||
this.ca = ca;
|
||||
}
|
||||
|
||||
public String getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
|
||||
public void setCertificate(String certificate) {
|
||||
this.certificate = certificate;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthYaml extends TypeDescription {
|
||||
|
||||
public AuthYaml() {
|
||||
super(Auth.class);
|
||||
}
|
||||
|
||||
public Class getImpl() {
|
||||
try {
|
||||
Field f = TypeDescription.class.getDeclaredField("impl");
|
||||
f.setAccessible(true);
|
||||
return (Class) f.get(this);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty(String name) {
|
||||
if ("key".equals(name)) {
|
||||
try {
|
||||
return new MethodProperty(new PropertyDescriptor("key", WithKey.class, "getKey", "setKey"));
|
||||
} catch (IntrospectionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if ("ca".equals(name)) {
|
||||
try {
|
||||
return new MethodProperty(new PropertyDescriptor("ca", TlsAuth.class, "getCa", "setCa"));
|
||||
} catch (IntrospectionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if ("certificate".equals(name)) {
|
||||
try {
|
||||
return new MethodProperty(new PropertyDescriptor("certificate", TlsAuth.class, "getCertificate", "setCertificate"));
|
||||
} catch (IntrospectionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return super.getProperty(name);
|
||||
}
|
||||
|
||||
private Optional<ScalarNode> getValue(MappingNode mappingNode, String key) {
|
||||
return mappingNode.getValue()
|
||||
.stream()
|
||||
.filter((n) -> n.getKeyNode() instanceof ScalarNode && n.getValueNode() instanceof ScalarNode)
|
||||
.filter((n) -> {
|
||||
ScalarNode sn = (ScalarNode)n.getKeyNode();
|
||||
return "type".equals(sn.getValue());
|
||||
})
|
||||
.map((n) -> (ScalarNode)n.getValueNode())
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newInstance(Node node) {
|
||||
if (node instanceof MappingNode) {
|
||||
MappingNode mappingNode = (MappingNode)node;
|
||||
Optional<ScalarNode> type = getValue(mappingNode, "type");
|
||||
if (type.isPresent()) {
|
||||
if ("basic".equals(type.get().getValue())) {
|
||||
return new BasicAuth();
|
||||
} else if ("tls".equals(type.get().getValue())) {
|
||||
return new TlsAuth();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported auth type: " + type.get().getValue());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Auth type is not set");
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Auth is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
public static enum EndpointType {
|
||||
JSON_RPC("json-rpc"),
|
||||
WEBSOCKET("ws", "websocket"),
|
||||
DSHACKLE("dshackle"),
|
||||
UNKNOWN("unknown");
|
||||
|
||||
private final String[] code;
|
||||
|
||||
EndpointType(String ... code) {
|
||||
this.code = code;
|
||||
Arrays.sort(this.code);
|
||||
}
|
||||
|
||||
public static EndpointType byName(String code) {
|
||||
code = code.toLowerCase();
|
||||
for (EndpointType t: EndpointType.values()) {
|
||||
if (Arrays.binarySearch(t.code, code) >= 0) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EndpointTypeYaml extends TypeDescription {
|
||||
|
||||
public EndpointTypeYaml() {
|
||||
super(EndpointType.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newInstance(Node node) {
|
||||
if (node instanceof ScalarNode) {
|
||||
return EndpointType.byName(((ScalarNode)node).getValue());
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid type: " + node.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt
Normal file
120
src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt
Normal file
@@ -0,0 +1,120 @@
|
||||
package io.emeraldpay.dshackle.config
|
||||
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class UpstreamsConfig {
|
||||
var version: String? = null
|
||||
var defaultOptions: MutableList<DefaultOptions> = ArrayList<DefaultOptions>()
|
||||
var upstreams: MutableList<Upstream<*>> = ArrayList<Upstream<*>>()
|
||||
|
||||
open class Options {
|
||||
@Deprecated("remove it")
|
||||
var quorum: Int = 1
|
||||
var disableSyncing: Boolean? = null
|
||||
var minPeers: Int? = 1
|
||||
set(minPeers) {
|
||||
if (minPeers != null && minPeers < 0) {
|
||||
throw IllegalArgumentException("minPeers must be positive number")
|
||||
}
|
||||
field = minPeers
|
||||
}
|
||||
|
||||
fun merge(additional: Options?): Options {
|
||||
if (additional == null) {
|
||||
return this
|
||||
}
|
||||
val copy = Options()
|
||||
copy.disableSyncing = if (this.disableSyncing != null) this.disableSyncing else additional.disableSyncing
|
||||
copy.minPeers = if (this.minPeers != null) this.minPeers else additional.minPeers
|
||||
return copy
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getDefaults(): Options {
|
||||
val options = Options()
|
||||
options.disableSyncing = true
|
||||
options.minPeers = 1
|
||||
return options
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DefaultOptions : Options() {
|
||||
var chains: List<String>? = null
|
||||
var options: Options? = null
|
||||
}
|
||||
|
||||
class Upstream<T : UpstreamConnection> {
|
||||
var id: String? = null
|
||||
var chain: String? = null
|
||||
var provider: String? = null
|
||||
var options: Options? = null
|
||||
var isEnabled = true
|
||||
var connection: T? = null
|
||||
}
|
||||
|
||||
open class UpstreamConnection
|
||||
|
||||
class GrpcConnection : UpstreamConnection() {
|
||||
var host: String? = null
|
||||
var port: Int = 0
|
||||
var auth: TlsAuth? = null
|
||||
}
|
||||
|
||||
class EthereumConnection : UpstreamConnection() {
|
||||
var rpc: HttpEndpoint? = null
|
||||
var ws: WsEndpoint? = null
|
||||
}
|
||||
|
||||
class HttpEndpoint(val url: URI) {
|
||||
var auth: Auth? = null
|
||||
}
|
||||
|
||||
class WsEndpoint(val url: URI) {
|
||||
var origin: URI? = null
|
||||
}
|
||||
|
||||
open class Auth {
|
||||
var type: String? = null
|
||||
}
|
||||
|
||||
class BasicAuth : Auth() {
|
||||
var key: String? = null
|
||||
}
|
||||
|
||||
class TlsAuth : Auth() {
|
||||
var ca: String? = null
|
||||
var certificate: String? = null
|
||||
var key: String? = null
|
||||
}
|
||||
|
||||
enum class UpstreamType private constructor(vararg code: String) {
|
||||
ETHEREUM_JSON_RPC("ethereum"),
|
||||
DSHACKLE("dshackle", "grpc"),
|
||||
UNKNOWN("unknown");
|
||||
|
||||
private val code: Array<String>
|
||||
|
||||
init {
|
||||
this.code = code as Array<String>
|
||||
Arrays.sort(this.code)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun byName(code: String): UpstreamType {
|
||||
var code = code
|
||||
code = code.toLowerCase()
|
||||
for (t in UpstreamType.values()) {
|
||||
if (Arrays.binarySearch(t.code, code) >= 0) {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,198 @@
|
||||
package io.emeraldpay.dshackle.config
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.yaml.snakeyaml.Yaml
|
||||
import org.yaml.snakeyaml.nodes.CollectionNode
|
||||
import org.yaml.snakeyaml.nodes.MappingNode
|
||||
import org.yaml.snakeyaml.nodes.Node
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.net.URI
|
||||
|
||||
class UpstreamsConfigReader {
|
||||
|
||||
private val log = LoggerFactory.getLogger(UpstreamsConfigReader::class.java)
|
||||
|
||||
fun read(input: InputStream): UpstreamsConfig {
|
||||
val yaml = Yaml()
|
||||
yaml.addTypeDescription(UpstreamsConfig.EndpointTypeYaml())
|
||||
yaml.addTypeDescription(UpstreamsConfig.OptionsYaml())
|
||||
yaml.addTypeDescription(UpstreamsConfig.AuthYaml())
|
||||
return yaml.loadAs(input, UpstreamsConfig::class.java)
|
||||
val configNode = asMappingNode(yaml.compose(InputStreamReader(input)))
|
||||
|
||||
val config = UpstreamsConfig()
|
||||
config.version = getValueAsString(configNode, "version")
|
||||
|
||||
getList<MappingNode>(configNode, "defaultOptions")?.value?.forEach { opts ->
|
||||
val defaultOptions = UpstreamsConfig.DefaultOptions()
|
||||
config.defaultOptions.add(defaultOptions)
|
||||
defaultOptions.chains = getListOfString(opts, "chains")
|
||||
val options = UpstreamsConfig.Options()
|
||||
defaultOptions.options = options
|
||||
getMapping(opts, "options")?.let { values ->
|
||||
getValueAsBool(values, "disable-syncing")?.let {
|
||||
options.disableSyncing = it
|
||||
}
|
||||
getValueAsInt(values, "min-peers")?.let {
|
||||
options.minPeers = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.upstreams = ArrayList<UpstreamsConfig.Upstream<*>>()
|
||||
getList<MappingNode>(configNode, "upstreams")?.value?.forEach { upNode ->
|
||||
val connNode = getMapping(upNode, "connection")
|
||||
if (hasAny(connNode, "ethereum")) {
|
||||
val connConfigNode = getMapping(connNode, "ethereum")!!
|
||||
val upstream = UpstreamsConfig.Upstream<UpstreamsConfig.EthereumConnection>()
|
||||
upstream.id = getValueAsString(upNode, "id")
|
||||
upstream.provider = getValueAsString(upNode, "provider")
|
||||
upstream.chain = getValueAsString(upNode, "chain")
|
||||
config.upstreams.add(upstream)
|
||||
val connection = UpstreamsConfig.EthereumConnection()
|
||||
upstream.connection = connection
|
||||
getMapping(connConfigNode, "rpc")?.let { node ->
|
||||
getValueAsString(node, "url")?.let { url ->
|
||||
val http = UpstreamsConfig.HttpEndpoint(URI(url))
|
||||
connection.rpc = http
|
||||
http.auth = readAuth(getMapping(node, "auth"))
|
||||
}
|
||||
}
|
||||
getMapping(connConfigNode, "ws")?.let { node ->
|
||||
getValueAsString(node, "url")?.let { url ->
|
||||
val ws = UpstreamsConfig.WsEndpoint(URI(url))
|
||||
connection.ws = ws
|
||||
getValueAsString(node, "origin")?.let { origin ->
|
||||
ws.origin = URI(origin)
|
||||
}
|
||||
// ws.auth = readAuth(getMapping(node, "auth"))
|
||||
}
|
||||
}
|
||||
} else if (hasAny(connNode, "grpc")) {
|
||||
val connConfigNode = getMapping(connNode, "grpc")!!
|
||||
val upstream = UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>()
|
||||
upstream.id = getValueAsString(upNode, "id")
|
||||
upstream.provider = getValueAsString(upNode, "provider")
|
||||
config.upstreams.add(upstream)
|
||||
val connection = UpstreamsConfig.GrpcConnection()
|
||||
upstream.connection = connection
|
||||
getValueAsString(connConfigNode, "host")?.let {
|
||||
connection.host = it
|
||||
}
|
||||
getValueAsInt(connConfigNode, "port")?.let {
|
||||
connection.port = it
|
||||
}
|
||||
connection.auth = readAuth(getMapping(connConfigNode, "auth")) as UpstreamsConfig.TlsAuth?
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
private fun readAuth(authNode: MappingNode?): UpstreamsConfig.Auth? {
|
||||
return getValueAsString(authNode, "type")?.let {
|
||||
return when (it) {
|
||||
"tls" -> {
|
||||
val auth = UpstreamsConfig.TlsAuth()
|
||||
auth.ca = getValueAsString(authNode, "ca")
|
||||
auth.certificate = getValueAsString(authNode, "certificate")
|
||||
auth.key = getValueAsString(authNode, "key")
|
||||
auth
|
||||
}
|
||||
"basic" -> {
|
||||
val auth = UpstreamsConfig.BasicAuth()
|
||||
auth.key = getValueAsString(authNode, "key")
|
||||
auth
|
||||
}
|
||||
else -> {
|
||||
log.warn("Invalid Auth type: $it")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasAny(mappingNode: MappingNode?, key: String): Boolean {
|
||||
if (mappingNode == null) {
|
||||
return false
|
||||
}
|
||||
return mappingNode.value
|
||||
.stream()
|
||||
.filter { n -> n.keyNode is ScalarNode }
|
||||
.filter { n ->
|
||||
val sn = n.keyNode as ScalarNode
|
||||
key == sn.value
|
||||
}.count() > 0
|
||||
}
|
||||
|
||||
private fun <T> getValue(mappingNode: MappingNode?, key: String, type: Class<T>): T? {
|
||||
if (mappingNode == null) {
|
||||
return null
|
||||
}
|
||||
return mappingNode.value
|
||||
.stream()
|
||||
.filter { n -> n.keyNode is ScalarNode && type.isAssignableFrom(n.valueNode.javaClass) }
|
||||
.filter { n ->
|
||||
val sn = n.keyNode as ScalarNode
|
||||
key == sn.value
|
||||
}
|
||||
.map { n -> n.valueNode as T }
|
||||
.findFirst().let {
|
||||
if (it.isPresent) {
|
||||
it.get()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMapping(mappingNode: MappingNode?, key: String): MappingNode? {
|
||||
return getValue(mappingNode, key, MappingNode::class.java)
|
||||
}
|
||||
|
||||
private fun getValue(mappingNode: MappingNode?, key: String): ScalarNode? {
|
||||
return getValue(mappingNode, key, ScalarNode::class.java)
|
||||
}
|
||||
|
||||
private fun <T> getList(mappingNode: MappingNode?, key: String): CollectionNode<T>? {
|
||||
return getValue(mappingNode, key, CollectionNode::class.java) as CollectionNode<T>
|
||||
}
|
||||
|
||||
private fun getListOfString(mappingNode: MappingNode?, key: String): List<String>? {
|
||||
return getList<ScalarNode>(mappingNode, key)?.value
|
||||
?.map { it.value }
|
||||
}
|
||||
|
||||
private fun getValueAsString(mappingNode: MappingNode?, key: String): String? {
|
||||
return getValue(mappingNode, key)?.let {
|
||||
return@let it.value
|
||||
}
|
||||
}
|
||||
|
||||
private fun getValueAsInt(mappingNode: MappingNode?, key: String): Int? {
|
||||
return getValue(mappingNode, key)?.let {
|
||||
return@let if (it.isPlain) {
|
||||
it.value.toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getValueAsBool(mappingNode: MappingNode?, key: String): Boolean? {
|
||||
return getValue(mappingNode, key)?.let {
|
||||
return@let if (it.isPlain) {
|
||||
it.value?.toLowerCase() == "true"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun asMappingNode(node: Node): MappingNode {
|
||||
return if (MappingNode::class.java.isAssignableFrom(node.javaClass)) {
|
||||
node as MappingNode
|
||||
} else {
|
||||
throw IllegalArgumentException("Not a map")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,14 @@ open class ConfiguredUpstreams(
|
||||
val config = readConfig()
|
||||
val defaultOptions = buildDefaultOptions(config)
|
||||
config.upstreams.forEach { up ->
|
||||
val options = (up.options ?: UpstreamsConfig.Options())
|
||||
.merge(UpstreamsConfig.Options.getDefaults())
|
||||
|
||||
if (up.provider == "dshackle") {
|
||||
buildGrpcUpstream(up)
|
||||
buildGrpcUpstream(up.connection as UpstreamsConfig.GrpcConnection, options)
|
||||
} else {
|
||||
buildEthereumUpstream(up, defaultOptions)
|
||||
val chain = chainNames[up.chain] ?: return
|
||||
buildEthereumUpstream(up.connection as UpstreamsConfig.EthereumConnection, chain, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +73,7 @@ open class ConfiguredUpstreams(
|
||||
private fun buildDefaultOptions(config: UpstreamsConfig): HashMap<Chain, UpstreamsConfig.Options> {
|
||||
val defaultOptions = HashMap<Chain, UpstreamsConfig.Options>()
|
||||
config.defaultOptions.forEach { df ->
|
||||
df.chains.forEach { chainName ->
|
||||
df.chains?.forEach { chainName ->
|
||||
chainNames[chainName]?.let { chain ->
|
||||
var current = defaultOptions[chain]
|
||||
if (current == null) {
|
||||
@@ -77,56 +81,45 @@ open class ConfiguredUpstreams(
|
||||
} else {
|
||||
current = current.merge(df.options)
|
||||
}
|
||||
defaultOptions[chain] = current
|
||||
defaultOptions[chain] = current!!
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultOptions
|
||||
}
|
||||
|
||||
private fun buildEthereumUpstream(up: UpstreamsConfig.Upstream,
|
||||
defaultOptions: HashMap<Chain, UpstreamsConfig.Options>) {
|
||||
val chain = chainNames[up.chain] ?: return
|
||||
private fun buildEthereumUpstream(up: UpstreamsConfig.EthereumConnection,
|
||||
chain: Chain,
|
||||
options: UpstreamsConfig.Options) {
|
||||
var rpcApi: EthereumApi? = null
|
||||
var wsApi: EthereumWs? = null
|
||||
val urls = ArrayList<URI>()
|
||||
up.endpoints.forEach { endpoint ->
|
||||
if (endpoint.type == UpstreamsConfig.EndpointType.JSON_RPC) {
|
||||
rpcApi = EthereumApi(
|
||||
DefaultRpcClient(DefaultRpcTransport(endpoint.url)),
|
||||
objectMapper,
|
||||
chain
|
||||
)
|
||||
}
|
||||
if (endpoint.type == UpstreamsConfig.EndpointType.WEBSOCKET) {
|
||||
wsApi = EthereumWs(
|
||||
endpoint.url,
|
||||
endpoint.origin ?: URI("http://localhost")
|
||||
)
|
||||
wsApi!!.connect()
|
||||
}
|
||||
up.rpc?.let { endpoint ->
|
||||
rpcApi = EthereumApi(
|
||||
DefaultRpcClient(DefaultRpcTransport(endpoint.url)),
|
||||
objectMapper,
|
||||
chain
|
||||
)
|
||||
urls.add(endpoint.url)
|
||||
}
|
||||
up.ws?.let { endpoint ->
|
||||
wsApi = EthereumWs(
|
||||
endpoint.url,
|
||||
endpoint.origin ?: URI("http://localhost")
|
||||
)
|
||||
wsApi!!.connect()
|
||||
urls.add(endpoint.url)
|
||||
}
|
||||
val options = (up.options ?: UpstreamsConfig.Options())
|
||||
.merge(defaultOptions[chain])
|
||||
.merge(UpstreamsConfig.Options.getDefaults())
|
||||
if (rpcApi != null) {
|
||||
log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}")
|
||||
getOrCreateUpstream(chain).addUpstream(EthereumUpstream(chain, rpcApi!!, wsApi, options))
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildGrpcUpstream(up: UpstreamsConfig.Upstream) {
|
||||
if (up.endpoints.size == 0) {
|
||||
return
|
||||
}
|
||||
val options = (up.options ?: UpstreamsConfig.Options())
|
||||
.merge(UpstreamsConfig.Options.getDefaults())
|
||||
|
||||
val endpoint = up.endpoints.first()
|
||||
if (endpoint.type == UpstreamsConfig.EndpointType.DSHACKLE) {
|
||||
private fun buildGrpcUpstream(up: UpstreamsConfig.GrpcConnection, options: UpstreamsConfig.Options) {
|
||||
val endpoint = up
|
||||
val ds = GrpcUpstreams(
|
||||
endpoint.host,
|
||||
endpoint.host!!,
|
||||
endpoint.port ?: 443,
|
||||
objectMapper,
|
||||
options
|
||||
@@ -140,7 +133,6 @@ open class ConfiguredUpstreams(
|
||||
log.info("Subscribed to $it through gRPC at ${endpoint.host}:${endpoint.port}")
|
||||
getOrCreateUpstream(it).addUpstream(ds.getOrCreate(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getUpstream(chain: Chain): AggregatedUpstreams? {
|
||||
|
||||
@@ -21,7 +21,7 @@ class UpstreamValidator(
|
||||
if (syncing.get().isSyncing) {
|
||||
return UpstreamAvailability.SYNCING
|
||||
}
|
||||
if (peerCount.get() < options.minPeers) {
|
||||
if (options.minPeers != null && peerCount.get() < options.minPeers!!) {
|
||||
return UpstreamAvailability.IMMATURE
|
||||
}
|
||||
return UpstreamAvailability.OK
|
||||
|
||||
@@ -18,7 +18,6 @@ class UpstreamsConfigReaderSpec extends Specification {
|
||||
size() == 1
|
||||
with(get(0)) {
|
||||
chains == ["ethereum"]
|
||||
options.quorum == 1
|
||||
options.minPeers == 3
|
||||
options.disableSyncing
|
||||
}
|
||||
@@ -28,28 +27,26 @@ class UpstreamsConfigReaderSpec extends Specification {
|
||||
id == "local"
|
||||
chain == "ethereum"
|
||||
provider == "geth"
|
||||
endpoints.size() == 2
|
||||
with(endpoints.get(0)) {
|
||||
type == UpstreamsConfig.EndpointType.JSON_RPC
|
||||
url == new URI("http://localhost:8545")
|
||||
}
|
||||
with(endpoints.get(1)) {
|
||||
type == UpstreamsConfig.EndpointType.WEBSOCKET
|
||||
url == new URI("ws://localhost:8546")
|
||||
connection instanceof UpstreamsConfig.EthereumConnection
|
||||
with((UpstreamsConfig.EthereumConnection)connection) {
|
||||
rpc != null
|
||||
rpc.url == new URI("http://localhost:8545")
|
||||
ws != null
|
||||
ws.url == new URI("ws://localhost:8546")
|
||||
}
|
||||
}
|
||||
with(act.upstreams.get(1)) {
|
||||
id == "infura"
|
||||
chain == "ethereum"
|
||||
provider == "infura"
|
||||
endpoints.size() == 1
|
||||
with(endpoints.get(0)) {
|
||||
type == UpstreamsConfig.EndpointType.JSON_RPC
|
||||
url == new URI("https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2")
|
||||
auth instanceof UpstreamsConfig.BasicAuth
|
||||
with((UpstreamsConfig.BasicAuth)auth) {
|
||||
connection instanceof UpstreamsConfig.EthereumConnection
|
||||
with((UpstreamsConfig.EthereumConnection)connection) {
|
||||
rpc.url == new URI("https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2")
|
||||
rpc.auth instanceof UpstreamsConfig.BasicAuth
|
||||
with((UpstreamsConfig.BasicAuth)rpc.auth) {
|
||||
key == "4fc258fe41a68149c199ad8f281f2015"
|
||||
}
|
||||
ws == null
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,11 +63,9 @@ class UpstreamsConfigReaderSpec extends Specification {
|
||||
act.upstreams.size() == 1
|
||||
with(act.upstreams.get(0)) {
|
||||
id == "remote"
|
||||
chain == "auto"
|
||||
provider == "dshackle"
|
||||
endpoints.size() == 1
|
||||
with(endpoints.get(0)) {
|
||||
type == UpstreamsConfig.EndpointType.DSHACKLE
|
||||
connection instanceof UpstreamsConfig.GrpcConnection
|
||||
with((UpstreamsConfig.GrpcConnection)connection) {
|
||||
host == "10.2.0.15"
|
||||
auth instanceof UpstreamsConfig.TlsAuth
|
||||
with((UpstreamsConfig.TlsAuth)auth) {
|
||||
|
||||
@@ -6,24 +6,25 @@ defaultOptions:
|
||||
options:
|
||||
disable-syncing: true
|
||||
min-peers: 3
|
||||
quorum: 1
|
||||
|
||||
upstreams:
|
||||
- id: local
|
||||
chain: ethereum
|
||||
provider: geth
|
||||
endpoints:
|
||||
- type: json-rpc
|
||||
url: "http://localhost:8545"
|
||||
- type: ws
|
||||
url: "ws://localhost:8546"
|
||||
origin: "http://localhost"
|
||||
connection:
|
||||
ethereum:
|
||||
rpc:
|
||||
url: "http://localhost:8545"
|
||||
ws:
|
||||
url: "ws://localhost:8546"
|
||||
origin: "http://localhost"
|
||||
- id: infura
|
||||
chain: ethereum
|
||||
provider: infura
|
||||
endpoints:
|
||||
- type: json-rpc
|
||||
url: "https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2"
|
||||
auth:
|
||||
type: basic
|
||||
key: 4fc258fe41a68149c199ad8f281f2015
|
||||
connection:
|
||||
ethereum:
|
||||
rpc:
|
||||
url: "https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2"
|
||||
auth:
|
||||
type: basic
|
||||
key: 4fc258fe41a68149c199ad8f281f2015
|
||||
@@ -6,14 +6,12 @@ defaultOptions:
|
||||
options:
|
||||
disable-syncing: true
|
||||
min-peers: 3
|
||||
quorum: 1
|
||||
|
||||
upstreams:
|
||||
- id: remote
|
||||
chain: auto
|
||||
provider: dshackle
|
||||
endpoints:
|
||||
- type: dshackle
|
||||
connection:
|
||||
grpc:
|
||||
host: "10.2.0.15"
|
||||
auth:
|
||||
type: tls
|
||||
|
||||
Reference in New Issue
Block a user