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:
@@ -23,7 +23,7 @@ plugins {
|
|||||||
|
|
||||||
|
|
||||||
group = 'io.emeraldpay.dshackle'
|
group = 'io.emeraldpay.dshackle'
|
||||||
version = '0.1-SNAPSHOT'
|
version = '0.2-SNAPSHOT'
|
||||||
|
|
||||||
targetCompatibility = '1.8'
|
targetCompatibility = '1.8'
|
||||||
sourceCompatibility = '1.8'
|
sourceCompatibility = '1.8'
|
||||||
|
|||||||
@@ -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
|
package io.emeraldpay.dshackle.config
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.yaml.snakeyaml.Yaml
|
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.InputStream
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
class UpstreamsConfigReader {
|
class UpstreamsConfigReader {
|
||||||
|
|
||||||
|
private val log = LoggerFactory.getLogger(UpstreamsConfigReader::class.java)
|
||||||
|
|
||||||
fun read(input: InputStream): UpstreamsConfig {
|
fun read(input: InputStream): UpstreamsConfig {
|
||||||
val yaml = Yaml()
|
val yaml = Yaml()
|
||||||
yaml.addTypeDescription(UpstreamsConfig.EndpointTypeYaml())
|
val configNode = asMappingNode(yaml.compose(InputStreamReader(input)))
|
||||||
yaml.addTypeDescription(UpstreamsConfig.OptionsYaml())
|
|
||||||
yaml.addTypeDescription(UpstreamsConfig.AuthYaml())
|
val config = UpstreamsConfig()
|
||||||
return yaml.loadAs(input, UpstreamsConfig::class.java)
|
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 config = readConfig()
|
||||||
val defaultOptions = buildDefaultOptions(config)
|
val defaultOptions = buildDefaultOptions(config)
|
||||||
config.upstreams.forEach { up ->
|
config.upstreams.forEach { up ->
|
||||||
|
val options = (up.options ?: UpstreamsConfig.Options())
|
||||||
|
.merge(UpstreamsConfig.Options.getDefaults())
|
||||||
|
|
||||||
if (up.provider == "dshackle") {
|
if (up.provider == "dshackle") {
|
||||||
buildGrpcUpstream(up)
|
buildGrpcUpstream(up.connection as UpstreamsConfig.GrpcConnection, options)
|
||||||
} else {
|
} 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> {
|
private fun buildDefaultOptions(config: UpstreamsConfig): HashMap<Chain, UpstreamsConfig.Options> {
|
||||||
val defaultOptions = HashMap<Chain, UpstreamsConfig.Options>()
|
val defaultOptions = HashMap<Chain, UpstreamsConfig.Options>()
|
||||||
config.defaultOptions.forEach { df ->
|
config.defaultOptions.forEach { df ->
|
||||||
df.chains.forEach { chainName ->
|
df.chains?.forEach { chainName ->
|
||||||
chainNames[chainName]?.let { chain ->
|
chainNames[chainName]?.let { chain ->
|
||||||
var current = defaultOptions[chain]
|
var current = defaultOptions[chain]
|
||||||
if (current == null) {
|
if (current == null) {
|
||||||
@@ -77,56 +81,45 @@ open class ConfiguredUpstreams(
|
|||||||
} else {
|
} else {
|
||||||
current = current.merge(df.options)
|
current = current.merge(df.options)
|
||||||
}
|
}
|
||||||
defaultOptions[chain] = current
|
defaultOptions[chain] = current!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return defaultOptions
|
return defaultOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildEthereumUpstream(up: UpstreamsConfig.Upstream,
|
private fun buildEthereumUpstream(up: UpstreamsConfig.EthereumConnection,
|
||||||
defaultOptions: HashMap<Chain, UpstreamsConfig.Options>) {
|
chain: Chain,
|
||||||
val chain = chainNames[up.chain] ?: return
|
options: UpstreamsConfig.Options) {
|
||||||
var rpcApi: EthereumApi? = null
|
var rpcApi: EthereumApi? = null
|
||||||
var wsApi: EthereumWs? = null
|
var wsApi: EthereumWs? = null
|
||||||
val urls = ArrayList<URI>()
|
val urls = ArrayList<URI>()
|
||||||
up.endpoints.forEach { endpoint ->
|
up.rpc?.let { endpoint ->
|
||||||
if (endpoint.type == UpstreamsConfig.EndpointType.JSON_RPC) {
|
rpcApi = EthereumApi(
|
||||||
rpcApi = EthereumApi(
|
DefaultRpcClient(DefaultRpcTransport(endpoint.url)),
|
||||||
DefaultRpcClient(DefaultRpcTransport(endpoint.url)),
|
objectMapper,
|
||||||
objectMapper,
|
chain
|
||||||
chain
|
)
|
||||||
)
|
urls.add(endpoint.url)
|
||||||
}
|
}
|
||||||
if (endpoint.type == UpstreamsConfig.EndpointType.WEBSOCKET) {
|
up.ws?.let { endpoint ->
|
||||||
wsApi = EthereumWs(
|
wsApi = EthereumWs(
|
||||||
endpoint.url,
|
endpoint.url,
|
||||||
endpoint.origin ?: URI("http://localhost")
|
endpoint.origin ?: URI("http://localhost")
|
||||||
)
|
)
|
||||||
wsApi!!.connect()
|
wsApi!!.connect()
|
||||||
}
|
|
||||||
urls.add(endpoint.url)
|
urls.add(endpoint.url)
|
||||||
}
|
}
|
||||||
val options = (up.options ?: UpstreamsConfig.Options())
|
|
||||||
.merge(defaultOptions[chain])
|
|
||||||
.merge(UpstreamsConfig.Options.getDefaults())
|
|
||||||
if (rpcApi != null) {
|
if (rpcApi != null) {
|
||||||
log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}")
|
log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}")
|
||||||
getOrCreateUpstream(chain).addUpstream(EthereumUpstream(chain, rpcApi!!, wsApi, options))
|
getOrCreateUpstream(chain).addUpstream(EthereumUpstream(chain, rpcApi!!, wsApi, options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildGrpcUpstream(up: UpstreamsConfig.Upstream) {
|
private fun buildGrpcUpstream(up: UpstreamsConfig.GrpcConnection, options: UpstreamsConfig.Options) {
|
||||||
if (up.endpoints.size == 0) {
|
val endpoint = up
|
||||||
return
|
|
||||||
}
|
|
||||||
val options = (up.options ?: UpstreamsConfig.Options())
|
|
||||||
.merge(UpstreamsConfig.Options.getDefaults())
|
|
||||||
|
|
||||||
val endpoint = up.endpoints.first()
|
|
||||||
if (endpoint.type == UpstreamsConfig.EndpointType.DSHACKLE) {
|
|
||||||
val ds = GrpcUpstreams(
|
val ds = GrpcUpstreams(
|
||||||
endpoint.host,
|
endpoint.host!!,
|
||||||
endpoint.port ?: 443,
|
endpoint.port ?: 443,
|
||||||
objectMapper,
|
objectMapper,
|
||||||
options
|
options
|
||||||
@@ -140,7 +133,6 @@ open class ConfiguredUpstreams(
|
|||||||
log.info("Subscribed to $it through gRPC at ${endpoint.host}:${endpoint.port}")
|
log.info("Subscribed to $it through gRPC at ${endpoint.host}:${endpoint.port}")
|
||||||
getOrCreateUpstream(it).addUpstream(ds.getOrCreate(it))
|
getOrCreateUpstream(it).addUpstream(ds.getOrCreate(it))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUpstream(chain: Chain): AggregatedUpstreams? {
|
override fun getUpstream(chain: Chain): AggregatedUpstreams? {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class UpstreamValidator(
|
|||||||
if (syncing.get().isSyncing) {
|
if (syncing.get().isSyncing) {
|
||||||
return UpstreamAvailability.SYNCING
|
return UpstreamAvailability.SYNCING
|
||||||
}
|
}
|
||||||
if (peerCount.get() < options.minPeers) {
|
if (options.minPeers != null && peerCount.get() < options.minPeers!!) {
|
||||||
return UpstreamAvailability.IMMATURE
|
return UpstreamAvailability.IMMATURE
|
||||||
}
|
}
|
||||||
return UpstreamAvailability.OK
|
return UpstreamAvailability.OK
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ class UpstreamsConfigReaderSpec extends Specification {
|
|||||||
size() == 1
|
size() == 1
|
||||||
with(get(0)) {
|
with(get(0)) {
|
||||||
chains == ["ethereum"]
|
chains == ["ethereum"]
|
||||||
options.quorum == 1
|
|
||||||
options.minPeers == 3
|
options.minPeers == 3
|
||||||
options.disableSyncing
|
options.disableSyncing
|
||||||
}
|
}
|
||||||
@@ -28,28 +27,26 @@ class UpstreamsConfigReaderSpec extends Specification {
|
|||||||
id == "local"
|
id == "local"
|
||||||
chain == "ethereum"
|
chain == "ethereum"
|
||||||
provider == "geth"
|
provider == "geth"
|
||||||
endpoints.size() == 2
|
connection instanceof UpstreamsConfig.EthereumConnection
|
||||||
with(endpoints.get(0)) {
|
with((UpstreamsConfig.EthereumConnection)connection) {
|
||||||
type == UpstreamsConfig.EndpointType.JSON_RPC
|
rpc != null
|
||||||
url == new URI("http://localhost:8545")
|
rpc.url == new URI("http://localhost:8545")
|
||||||
}
|
ws != null
|
||||||
with(endpoints.get(1)) {
|
ws.url == new URI("ws://localhost:8546")
|
||||||
type == UpstreamsConfig.EndpointType.WEBSOCKET
|
|
||||||
url == new URI("ws://localhost:8546")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
with(act.upstreams.get(1)) {
|
with(act.upstreams.get(1)) {
|
||||||
id == "infura"
|
id == "infura"
|
||||||
chain == "ethereum"
|
chain == "ethereum"
|
||||||
provider == "infura"
|
provider == "infura"
|
||||||
endpoints.size() == 1
|
connection instanceof UpstreamsConfig.EthereumConnection
|
||||||
with(endpoints.get(0)) {
|
with((UpstreamsConfig.EthereumConnection)connection) {
|
||||||
type == UpstreamsConfig.EndpointType.JSON_RPC
|
rpc.url == new URI("https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2")
|
||||||
url == new URI("https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2")
|
rpc.auth instanceof UpstreamsConfig.BasicAuth
|
||||||
auth instanceof UpstreamsConfig.BasicAuth
|
with((UpstreamsConfig.BasicAuth)rpc.auth) {
|
||||||
with((UpstreamsConfig.BasicAuth)auth) {
|
|
||||||
key == "4fc258fe41a68149c199ad8f281f2015"
|
key == "4fc258fe41a68149c199ad8f281f2015"
|
||||||
}
|
}
|
||||||
|
ws == null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -66,11 +63,9 @@ class UpstreamsConfigReaderSpec extends Specification {
|
|||||||
act.upstreams.size() == 1
|
act.upstreams.size() == 1
|
||||||
with(act.upstreams.get(0)) {
|
with(act.upstreams.get(0)) {
|
||||||
id == "remote"
|
id == "remote"
|
||||||
chain == "auto"
|
|
||||||
provider == "dshackle"
|
provider == "dshackle"
|
||||||
endpoints.size() == 1
|
connection instanceof UpstreamsConfig.GrpcConnection
|
||||||
with(endpoints.get(0)) {
|
with((UpstreamsConfig.GrpcConnection)connection) {
|
||||||
type == UpstreamsConfig.EndpointType.DSHACKLE
|
|
||||||
host == "10.2.0.15"
|
host == "10.2.0.15"
|
||||||
auth instanceof UpstreamsConfig.TlsAuth
|
auth instanceof UpstreamsConfig.TlsAuth
|
||||||
with((UpstreamsConfig.TlsAuth)auth) {
|
with((UpstreamsConfig.TlsAuth)auth) {
|
||||||
|
|||||||
@@ -6,24 +6,25 @@ defaultOptions:
|
|||||||
options:
|
options:
|
||||||
disable-syncing: true
|
disable-syncing: true
|
||||||
min-peers: 3
|
min-peers: 3
|
||||||
quorum: 1
|
|
||||||
|
|
||||||
upstreams:
|
upstreams:
|
||||||
- id: local
|
- id: local
|
||||||
chain: ethereum
|
chain: ethereum
|
||||||
provider: geth
|
provider: geth
|
||||||
endpoints:
|
connection:
|
||||||
- type: json-rpc
|
ethereum:
|
||||||
url: "http://localhost:8545"
|
rpc:
|
||||||
- type: ws
|
url: "http://localhost:8545"
|
||||||
url: "ws://localhost:8546"
|
ws:
|
||||||
origin: "http://localhost"
|
url: "ws://localhost:8546"
|
||||||
|
origin: "http://localhost"
|
||||||
- id: infura
|
- id: infura
|
||||||
chain: ethereum
|
chain: ethereum
|
||||||
provider: infura
|
provider: infura
|
||||||
endpoints:
|
connection:
|
||||||
- type: json-rpc
|
ethereum:
|
||||||
url: "https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2"
|
rpc:
|
||||||
auth:
|
url: "https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2"
|
||||||
type: basic
|
auth:
|
||||||
key: 4fc258fe41a68149c199ad8f281f2015
|
type: basic
|
||||||
|
key: 4fc258fe41a68149c199ad8f281f2015
|
||||||
@@ -6,14 +6,12 @@ defaultOptions:
|
|||||||
options:
|
options:
|
||||||
disable-syncing: true
|
disable-syncing: true
|
||||||
min-peers: 3
|
min-peers: 3
|
||||||
quorum: 1
|
|
||||||
|
|
||||||
upstreams:
|
upstreams:
|
||||||
- id: remote
|
- id: remote
|
||||||
chain: auto
|
|
||||||
provider: dshackle
|
provider: dshackle
|
||||||
endpoints:
|
connection:
|
||||||
- type: dshackle
|
grpc:
|
||||||
host: "10.2.0.15"
|
host: "10.2.0.15"
|
||||||
auth:
|
auth:
|
||||||
type: tls
|
type: tls
|
||||||
|
|||||||
Reference in New Issue
Block a user