From 92218623f20180fea3807910c5afb50d0df152dc Mon Sep 17 00:00:00 2001 From: terminal Date: Fri, 15 Jul 2022 18:31:19 +0400 Subject: [PATCH 01/11] extract ethereum connectors from ethereum upstreams and add ethereum pos config --- .../dshackle/config/UpstreamsConfig.kt | 5 + .../dshackle/config/UpstreamsConfigReader.kt | 192 ++++++++++-------- .../dshackle/startup/ConfiguredUpstreams.kt | 164 +++++++-------- .../dshackle/upstream/HttpFactory.kt | 10 + .../dshackle/upstream/HttpRpcFactory.kt | 45 ++++ .../upstream/ethereum/EthereumRpcUpstream.kt | 122 ----------- .../upstream/ethereum/EthereumUpstream.kt | 78 ++++++- .../upstream/ethereum/EthereumWsUpstream.kt | 130 ------------ .../ethereum/connectors/ConnectorFactory.kt | 10 + .../ethereum/connectors/EthereumConnector.kt | 13 ++ .../connectors/EthereumConnectorFactory.kt | 34 ++++ .../connectors/EthereumRpcConnector.kt | 76 +++++++ .../connectors/EthereumWsConnector.kt | 74 +++++++ .../ethereum_pos/EthereumPosUpstream.kt | 50 +++++ .../upstream/grpc/EthereumGrpcUpstream.kt | 8 +- .../config/UpstreamsConfigReaderSpec.groovy | 20 ++ .../dshackle/rpc/StreamHeadSpec.groovy | 4 +- .../dshackle/test/ConnectorFactoryMock.groovy | 29 +++ .../test/EthereumConnectorMock.groovy | 37 ++++ .../dshackle/test/EthereumUpstreamMock.groovy | 27 +-- .../dshackle/upstream/FilteredApisSpec.groovy | 15 +- .../resources/upstreams-ethereum-pos.yaml | 9 + 22 files changed, 688 insertions(+), 464 deletions(-) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsUpstream.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt create mode 100644 src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy create mode 100644 src/test/groovy/io/emeraldpay/dshackle/test/EthereumConnectorMock.groovy create mode 100644 src/test/resources/upstreams-ethereum-pos.yaml diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index d681a5bb..662105b0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -114,6 +114,11 @@ open class UpstreamsConfig { var zeroMq: BitcoinZeroMq? = null } + class EthereumPosConnection : UpstreamConnection() { + var execution : EthereumConnection? = null + var blockPriority : Int = 0 + } + data class BitcoinZeroMq( val host: String = "127.0.0.1", val port: Int diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index a118d956..f280ec64 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -86,94 +86,16 @@ class UpstreamsConfigReader( getList(input, "upstreams")?.value?.forEachIndexed { _, upNode -> val connNode = getMapping(upNode, "connection") if (hasAny(connNode, "ethereum")) { - val connConfigNode = getMapping(connNode, "ethereum")!! - val upstream = UpstreamsConfig.Upstream() - readUpstreamCommon(upNode, upstream) - readUpstreamStandard(upNode, upstream) - if (isValid(upstream)) { - 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.basicAuth = authConfigReader.readClientBasicAuth(node) - http.tls = authConfigReader.readClientTls(node) - } - } - 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.basicAuth = authConfigReader.readClientBasicAuth(node) - - getValueAsBytes(node, "frameSize")?.let { - if (it < 65_535) { - throw IllegalStateException("frameSize cannot be less than 64Kb") - } - ws.frameSize = it - } - getValueAsBytes(node, "msgSize")?.let { - if (it < 65_535) { - throw IllegalStateException("msgSize cannot be less than 64Kb") - } - ws.msgSize = it - } - } - } - } else { - log.error("Upstream at #0 has invalid configuration") + readUpstream(config, upNode) { + readEthereumConnection(getMapping(connNode, "ethereum")!!) } } else if (hasAny(connNode, "bitcoin")) { - val connConfigNode = getMapping(connNode, "bitcoin")!! - val upstream = UpstreamsConfig.Upstream() - readUpstreamCommon(upNode, upstream) - readUpstreamStandard(upNode, upstream) - if (isValid(upstream)) { - config.upstreams.add(upstream) - val connection = UpstreamsConfig.BitcoinConnection() - upstream.connection = connection - getMapping(connConfigNode, "rpc")?.let { node -> - getValueAsString(node, "url")?.let { url -> - val http = UpstreamsConfig.HttpEndpoint(URI(url)) - connection.rpc = http - http.basicAuth = authConfigReader.readClientBasicAuth(node) - http.tls = authConfigReader.readClientTls(node) - } - } - getMapping(connConfigNode, "esplora")?.let { node -> - getValueAsString(node, "url")?.let { url -> - val http = UpstreamsConfig.HttpEndpoint(URI(url)) - http.basicAuth = authConfigReader.readClientBasicAuth(node) - http.tls = authConfigReader.readClientTls(node) - connection.esplora = http - } - } - getMapping(connConfigNode, "zeromq")?.let { node -> - getValueAsString(node, "address")?.let { address -> - val zmqConfig: Pair? = try { - if (address.contains(":")) { - address.split(":").let { - Pair(it[0], it[1].toInt()) - } - } else { - Pair("127.0.0.1", address.toInt()) - } - } catch (t: Throwable) { - log.warn("Invalid config for ZeroMQ: $address. Expected to be in format HOST:PORT") - null - } - zmqConfig?.let { - connection.zeroMq = UpstreamsConfig.BitcoinZeroMq(it.first, it.second) - } - } - } - } else { - log.error("Upstream at #0 has invalid configuration") + readUpstream(config, upNode) { + readBitcoinConnection(getMapping(connNode, "bitcoin")!!) + } + } else if (hasAny(connNode, "ethereum-pos")) { + readUpstream(config, upNode) { + readEthereumPosConnection(getMapping(connNode, "ethereum-pos")!!) } } else if (hasAny(connNode, "grpc")) { val connConfigNode = getMapping(connNode, "grpc")!! @@ -200,6 +122,104 @@ class UpstreamsConfigReader( return config } + private fun readBitcoinConnection(connConfigNode: MappingNode): UpstreamsConfig.BitcoinConnection { + val connection = UpstreamsConfig.BitcoinConnection() + getMapping(connConfigNode, "rpc")?.let { node -> + getValueAsString(node, "url")?.let { url -> + val http = UpstreamsConfig.HttpEndpoint(URI(url)) + connection.rpc = http + http.basicAuth = authConfigReader.readClientBasicAuth(node) + http.tls = authConfigReader.readClientTls(node) + } + } + getMapping(connConfigNode, "esplora")?.let { node -> + getValueAsString(node, "url")?.let { url -> + val http = UpstreamsConfig.HttpEndpoint(URI(url)) + http.basicAuth = authConfigReader.readClientBasicAuth(node) + http.tls = authConfigReader.readClientTls(node) + connection.esplora = http + } + } + getMapping(connConfigNode, "zeromq")?.let { node -> + getValueAsString(node, "address")?.let { address -> + val zmqConfig: Pair? = try { + if (address.contains(":")) { + address.split(":").let { + Pair(it[0], it[1].toInt()) + } + } else { + Pair("127.0.0.1", address.toInt()) + } + } catch (t: Throwable) { + log.warn("Invalid config for ZeroMQ: $address. Expected to be in format HOST:PORT") + null + } + zmqConfig?.let { + connection.zeroMq = UpstreamsConfig.BitcoinZeroMq(it.first, it.second) + } + } + } + return connection + } + + private fun readEthereumPosConnection(connConfigNode: MappingNode) : UpstreamsConfig.EthereumPosConnection { + val connection = UpstreamsConfig.EthereumPosConnection() + getMapping(connConfigNode, "execution")?.let { + connection.execution = readEthereumConnection(it) + } + getValueAsInt(connConfigNode, "block-priority")?.let { + connection.blockPriority = it + } + return connection + } + private fun readEthereumConnection(connConfigNode : MappingNode) : UpstreamsConfig.EthereumConnection { + val connection = UpstreamsConfig.EthereumConnection() + getMapping(connConfigNode, "rpc")?.let { node -> + getValueAsString(node, "url")?.let { url -> + val http = UpstreamsConfig.HttpEndpoint(URI(url)) + connection.rpc = http + http.basicAuth = authConfigReader.readClientBasicAuth(node) + http.tls = authConfigReader.readClientTls(node) + } + } + 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.basicAuth = authConfigReader.readClientBasicAuth(node) + + getValueAsBytes(node, "frameSize")?.let { + if (it < 65_535) { + throw IllegalStateException("frameSize cannot be less than 64Kb") + } + ws.frameSize = it + } + getValueAsBytes(node, "msgSize")?.let { + if (it < 65_535) { + throw IllegalStateException("msgSize cannot be less than 64Kb") + } + ws.msgSize = it + } + } + } + return connection + } + + private fun readUpstream(config: UpstreamsConfig, upNode: MappingNode, connFactory: () -> T) { + val upstream = UpstreamsConfig.Upstream() + readUpstreamCommon(upNode, upstream) + readUpstreamStandard(upNode, upstream) + if (isValid(upstream)) { + config.upstreams.add(upstream) + upstream.connection = connFactory() + } else { + log.error("Upstream at #0 has invalid configuration") + } + } + fun isValid(upstream: UpstreamsConfig.Upstream<*>): Boolean { val id = upstream.id // In general, we just check that id is suitable for urls and references, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index 48e4f189..bcf9e216 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -20,9 +20,7 @@ import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.upstream.CurrentMultistreamHolder -import io.emeraldpay.dshackle.upstream.Head -import io.emeraldpay.dshackle.upstream.MergedHead +import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcHead import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcUpstream import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinZMQHead @@ -31,20 +29,14 @@ import io.emeraldpay.dshackle.upstream.bitcoin.ExtractBlock import io.emeraldpay.dshackle.upstream.bitcoin.ZMQServer import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory -import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsUpstream +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory import io.emeraldpay.dshackle.upstream.grpc.GrpcUpstreams -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcHttpClient import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics import io.emeraldpay.grpc.BlockchainType import io.emeraldpay.grpc.Chain -import io.micrometer.core.instrument.Counter -import io.micrometer.core.instrument.Metrics -import io.micrometer.core.instrument.Tag -import io.micrometer.core.instrument.Timer import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository @@ -83,18 +75,24 @@ open class ConfiguredUpstreams( } val options = (up.options ?: UpstreamsConfig.Options()) .merge(defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults()) - when (BlockchainType.from(chain)) { + val upstream = when (BlockchainType.from(chain)) { BlockchainType.ETHEREUM -> { buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options) } BlockchainType.BITCOIN -> { buildBitcoinUpstream(up.cast(UpstreamsConfig.BitcoinConnection::class.java), chain, options) } +// BlockchainType.ETHEREUM_POS -> { +// buildEthereumPosUpstream(up.cast(UpstreamsConfig.EthereumPosConnection::class.java), chain, options) +// } else -> { log.error("Chain is unsupported: ${up.chain}") return@forEach } } + upstream?.let { + currentUpstreams.update(UpstreamChange(chain, upstream, UpstreamChange.ChangeType.ADDED)) + } } } } @@ -141,19 +139,33 @@ open class ConfiguredUpstreams( } } +// private fun buildEthereumPosUpstream( +// config: UpstreamsConfig.Upstream, +// chain: Chain, +// options: UpstreamsConfig.Options +// ) : Upstream? { +// val conn = config.connection!! +// val execution = conn.execution +// if (execution == null) { +// log.warn("Upstream doesn't have execution layer configuration") +// return null +// } +// +// val connectorFactory = buildEthereumConnectorFactory(execution, chain) +// } + private fun buildBitcoinUpstream( config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options - ) { - + ) : Upstream? { val conn = config.connection!! - val directApi: Reader? = buildHttpClient(config) - if (directApi == null) { + val httpFactory = buildHttpFactory(conn) + if (httpFactory == null) { log.warn("Upstream doesn't have API configuration") - return + return null } - + val directApi: Reader = httpFactory.create(config.id, chain) val esplora = conn.esplora?.let { endpoint -> val tls = endpoint.tls?.let { tls -> tls.ca?.let { ca -> @@ -180,63 +192,34 @@ open class ConfiguredUpstreams( QuorumForLabels.QuorumItem(1, config.labels), methods, esplora ) - upstream.start() - currentUpstreams.update(UpstreamChange(chain, upstream, UpstreamChange.ChangeType.ADDED)) + return upstream } private fun buildEthereumUpstream( config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options - ) { + ) : EthereumUpstream? { val conn = config.connection!! val urls = ArrayList() val methods = buildMethods(config, chain) - conn.rpc?.let { endpoint -> - urls.add(endpoint.url) - } - val wsFactoryApi: EthereumWsFactory? = conn.ws?.let { endpoint -> - val wsApi = EthereumWsFactory( - endpoint.url, - endpoint.origin ?: URI("http://localhost"), - ) - wsApi.config = endpoint - endpoint.basicAuth?.let { auth -> - wsApi.basicAuth = auth - } - urls.add(endpoint.url) - wsApi + val connectorFactory = buildEthereumConnectorFactory(conn, chain, urls) + if (connectorFactory == null) { + return null } - - log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}") - val ethereumUpstream = if (wsFactoryApi != null && !conn.preferHttp) { - EthereumWsUpstream( - config.id!!, - chain, wsFactoryApi, - options, config.role, - QuorumForLabels.QuorumItem(1, config.labels), - methods - ) - } else { - val directApi: Reader? = buildHttpClient(config) - if (directApi == null) { - log.warn("Upstream doesn't have API configuration") - return - } - EthereumRpcUpstream( - config.id!!, - chain, directApi, wsFactoryApi, - options, config.role, - QuorumForLabels.QuorumItem(1, config.labels), - methods - ) - } - - ethereumUpstream.start() - currentUpstreams.update(UpstreamChange(chain, ethereumUpstream, UpstreamChange.ChangeType.ADDED)) + val upstream = EthereumUpstream( + config.id!!, + chain, + options, config.role, + methods, + QuorumForLabels.QuorumItem(1, config.labels), + connectorFactory + ) + upstream.start() + return upstream } private fun buildGrpcUpstream( @@ -262,39 +245,42 @@ open class ConfiguredUpstreams( .subscribe(currentUpstreams::update) } - private fun buildHttpClient(config: UpstreamsConfig.Upstream): JsonRpcHttpClient? { - val conn = config.connection!! - val urls = ArrayList() + private fun buildHttpFactory(conn: UpstreamsConfig.RpcConnection, urls: ArrayList? = null): HttpRpcFactory? { return conn.rpc?.let { endpoint -> val tls = conn.rpc?.tls?.let { tls -> tls.ca?.let { ca -> fileResolver.resolve(ca).readBytes() } } - val metricsTags = listOf( - // "unknown" is not supposed to happen - Tag.of("upstream", config.id ?: "unknown"), - // UNSPECIFIED shouldn't happen too - Tag.of("chain", (Global.chainById(config.chain).chainCode)) - ) - val metrics = RpcMetrics( - Timer.builder("upstream.rpc.conn") - .description("Request time through a HTTP JSON RPC connection") - .tags(metricsTags) - .publishPercentileHistogram() - .register(Metrics.globalRegistry), - Counter.builder("upstream.rpc.fail") - .description("Number of failures of HTTP JSON RPC requests") - .tags(metricsTags) - .register(Metrics.globalRegistry) - ) - urls.add(endpoint.url) - JsonRpcHttpClient( - endpoint.url.toString(), - metrics, - conn.rpc?.basicAuth, - tls - ) + urls?.add(endpoint.url) + HttpRpcFactory(endpoint.url.toString(), conn.rpc?.basicAuth, tls) } } + + private fun buildWsFactory(conn: UpstreamsConfig.EthereumConnection, urls: ArrayList? = null): EthereumWsFactory? { + return conn.ws?.let { endpoint -> + val wsApi = EthereumWsFactory( + endpoint.url, + endpoint.origin ?: URI("http://localhost"), + ) + wsApi.config = endpoint + endpoint.basicAuth?.let { auth -> + wsApi.basicAuth = auth + } + urls?.add(endpoint.url) + wsApi + } + } + + private fun buildEthereumConnectorFactory(conn: UpstreamsConfig.EthereumConnection, chain: Chain, urls: ArrayList): EthereumConnectorFactory? { + val wsFactoryApi = buildWsFactory(conn, urls) + val httpFactory = buildHttpFactory(conn, urls) + log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}") + val connectorFactory = EthereumConnectorFactory(conn.preferHttp, wsFactoryApi, httpFactory) + if (!connectorFactory.isValid()) { + log.warn("Upstream configuration is invalid (probably no http endpoint)") + return null + } + return connectorFactory + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt new file mode 100644 index 00000000..10d99560 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt @@ -0,0 +1,10 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.grpc.Chain + +interface HttpFactory { + fun create(id: String?, chain: Chain): Reader +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt new file mode 100644 index 00000000..9261cc6c --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt @@ -0,0 +1,45 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.config.AuthConfig +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcHttpClient +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics +import io.emeraldpay.grpc.Chain +import io.micrometer.core.instrument.Counter +import io.micrometer.core.instrument.Metrics +import io.micrometer.core.instrument.Tag +import io.micrometer.core.instrument.Timer + +open class HttpRpcFactory( + private val url: String, + private val basicAuth: AuthConfig.ClientBasicAuth?, + private val tls: ByteArray? +) : HttpFactory { + override fun create(id: String?, chain: Chain): Reader { + val metricsTags = listOf( + // "unknown" is not supposed to happen + Tag.of("upstream", id ?: "unknown"), + // UNSPECIFIED shouldn't happen too + Tag.of("chain", chain.chainCode) + ) + val metrics = RpcMetrics( + Timer.builder("upstream.rpc.conn") + .description("Request time through a HTTP JSON RPC connection") + .tags(metricsTags) + .publishPercentileHistogram() + .register(Metrics.globalRegistry), + Counter.builder("upstream.rpc.fail") + .description("Number of failures of HTTP JSON RPC requests") + .tags(metricsTags) + .register(Metrics.globalRegistry) + ) + return JsonRpcHttpClient( + url, + metrics, + basicAuth, + tls + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt deleted file mode 100644 index 51bed8d7..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt +++ /dev/null @@ -1,122 +0,0 @@ -package io.emeraldpay.dshackle.upstream.ethereum - -import io.emeraldpay.dshackle.cache.Caches -import io.emeraldpay.dshackle.cache.CachesEnabled -import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.Head -import io.emeraldpay.dshackle.upstream.MergedHead -import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.UpstreamAvailability -import io.emeraldpay.dshackle.upstream.calls.CallMethods -import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import io.emeraldpay.grpc.Chain -import org.slf4j.LoggerFactory -import org.springframework.context.Lifecycle -import reactor.core.Disposable -import java.time.Duration - -open class EthereumRpcUpstream( - id: String, - val chain: Chain, - private val directReader: Reader, - private val ethereumWsFactory: EthereumWsFactory? = null, - options: UpstreamsConfig.Options, - role: UpstreamsConfig.UpstreamRole, - private val node: QuorumForLabels.QuorumItem, - targets: CallMethods -) : EthereumUpstream(id, options, role, targets, node), Upstream, CachesEnabled, Lifecycle { - - constructor(id: String, chain: Chain, api: Reader) : - this( - id, chain, api, null, - UpstreamsConfig.Options.getDefaults(), UpstreamsConfig.UpstreamRole.PRIMARY, - QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels()), - DirectCallMethods() - ) - - private val log = LoggerFactory.getLogger(EthereumRpcUpstream::class.java) - - private val head: Head = this.createHead() - private var validatorSubscription: Disposable? = null - - override fun setCaches(caches: Caches) { - if (head is CachesEnabled) { - head.setCaches(caches) - } - } - - override fun start() { - log.info("Configured for ${chain.chainName}") - - if (getOptions().disableValidation != null && getOptions().disableValidation!!) { - log.warn("Disable validation for upstream ${this.getId()}") - this.setLag(0) - this.setStatus(UpstreamAvailability.OK) - } else { - log.debug("Start validation for upstream ${this.getId()}") - val validator = EthereumUpstreamValidator(this, getOptions()) - validatorSubscription = validator.start() - .subscribe(this::setStatus) - } - } - - override fun isRunning(): Boolean { - return true - } - - override fun stop() { - validatorSubscription?.dispose() - validatorSubscription = null - if (head is Lifecycle) { - head.stop() - } - } - - open fun createHead(): Head { - return if (ethereumWsFactory != null) { - // do not set upstream to the WS, since it doesn't control the RPC upstream - val ws = ethereumWsFactory.create(null, null, null).apply { - connect() - } - val wsHead = EthereumWsHead(ws).apply { - start() - } - // receive bew blocks through WebSockets, but also periodically verify with RPC in case if WS failed - val rpcHead = EthereumRpcHead(getApi(), Duration.ofSeconds(60)).apply { - start() - } - MergedHead(listOf(rpcHead, wsHead)).apply { - start() - } - } else { - log.warn("Setting up upstream ${this.getId()} with RPC-only access, less effective than WS+RPC") - EthereumRpcHead(getApi()).apply { - start() - } - } - } - - override fun getHead(): Head { - return head - } - - override fun getApi(): Reader { - return directReader - } - - override fun isGrpc(): Boolean { - return false - } - - @Suppress("UNCHECKED_CAST") - override fun cast(selfType: Class): T { - if (!selfType.isAssignableFrom(this.javaClass)) { - throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") - } - return this as T - } -} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt index c14bd120..4248f78f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt @@ -16,19 +16,79 @@ */ package io.emeraldpay.dshackle.upstream.ethereum +import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.Capability -import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.Disposable -abstract class EthereumUpstream( +open class EthereumUpstream( id: String, + val chain: Chain, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, - private val node: QuorumForLabels.QuorumItem? -) : DefaultUpstream(id, options, role, targets, node) { + private val node: QuorumForLabels.QuorumItem?, + connectorFactory: ConnectorFactory +) : DefaultUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { + private val log = LoggerFactory.getLogger(EthereumUpstream::class.java) + private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) + private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) + + private var validatorSubscription: Disposable? = null + + override fun setCaches(caches: Caches) { + if (connector is CachesEnabled) { + connector.setCaches(caches) + } + } + + override fun start() { + log.info("Configured for ${chain.chainName}") + connector.start() + if (getOptions().disableValidation != null && getOptions().disableValidation!!) { + log.warn("Disable validation for upstream ${this.getId()}") + this.setLag(0) + this.setStatus(UpstreamAvailability.OK) + } else { + log.debug("Start validation for upstream ${this.getId()}") + val validator = EthereumUpstreamValidator(this, getOptions()) + validatorSubscription = validator.start() + .subscribe(this::setStatus) + } + } + override fun getHead(): Head { + return connector.getHead() + } + + override fun stop() { + validatorSubscription?.dispose() + validatorSubscription = null + connector.stop() + } + + override fun isRunning(): Boolean { + return connector.isRunning + } + + override fun getApi(): Reader { + return connector.getApi() + } + + override fun isGrpc(): Boolean { + return false + } private val capabilities = if (options.providesBalance != false) { setOf(Capability.RPC, Capability.BALANCE) @@ -43,4 +103,12 @@ abstract class EthereumUpstream( override fun getLabels(): Collection { return node?.let { listOf(it.labels) } ?: emptyList() } + + @Suppress("UNCHECKED_CAST") + override fun cast(selfType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return this as T + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsUpstream.kt deleted file mode 100644 index c9ddb32b..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsUpstream.kt +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright (c) 2021 EmeraldPay, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.emeraldpay.dshackle.upstream.ethereum - -import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.Head -import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.UpstreamAvailability -import io.emeraldpay.dshackle.upstream.calls.CallMethods -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsClient -import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics -import io.emeraldpay.grpc.Chain -import io.micrometer.core.instrument.Counter -import io.micrometer.core.instrument.Metrics -import io.micrometer.core.instrument.Tag -import io.micrometer.core.instrument.Timer -import org.slf4j.LoggerFactory -import org.springframework.context.Lifecycle -import reactor.core.Disposable - -class EthereumWsUpstream( - id: String, - val chain: Chain, - ethereumWsFactory: EthereumWsFactory, - options: UpstreamsConfig.Options, - role: UpstreamsConfig.UpstreamRole, - node: QuorumForLabels.QuorumItem, - targets: CallMethods -) : EthereumUpstream(id, options, role, targets, node), Upstream, Lifecycle { - - companion object { - private val log = LoggerFactory.getLogger(EthereumWsUpstream::class.java) - } - - private val head: EthereumWsHead - private val connection: WsConnection - private val api: JsonRpcWsClient - - private var validatorSubscription: Disposable? = null - private val validator: EthereumUpstreamValidator - - init { - val metricsTags = listOf( - Tag.of("upstream", id), - // UNSPECIFIED shouldn't happen too - Tag.of("chain", chain.chainCode) - ) - val metrics = RpcMetrics( - Timer.builder("upstream.ws.conn") - .description("Request time through a WebSocket JSON RPC connection") - .tags(metricsTags) - .publishPercentileHistogram() - .register(Metrics.globalRegistry), - Counter.builder("upstream.ws.fail") - .description("Number of failures of WebSocket JSON RPC requests") - .tags(metricsTags) - .register(Metrics.globalRegistry) - ) - - validator = EthereumUpstreamValidator(this, getOptions()) - - connection = ethereumWsFactory.create(this, validator, metrics) - head = EthereumWsHead(connection) - api = JsonRpcWsClient(connection) - } - - override fun getHead(): Head { - return head - } - - override fun getApi(): Reader { - return api - } - - override fun isGrpc(): Boolean { - return false - } - - @Suppress("UNCHECKED_CAST") - override fun cast(selfType: Class): T { - if (!selfType.isAssignableFrom(this.javaClass)) { - throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") - } - return this as T - } - - override fun start() { - connection.connect() - head.start() - - if (getOptions().disableValidation != null && getOptions().disableValidation!!) { - log.warn("Disable validation for upstream ${this.getId()}") - this.setLag(0) - this.setStatus(UpstreamAvailability.OK) - } else { - log.debug("Start validation for upstream ${this.getId()}") - val validator = EthereumUpstreamValidator(this, getOptions()) - validatorSubscription = validator.start() - .subscribe(this::setStatus) - } - } - - override fun stop() { - validatorSubscription?.dispose() - validatorSubscription = null - head.stop() - connection.close() - } - - override fun isRunning(): Boolean { - return head.isRunning - } -} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt new file mode 100644 index 00000000..09c5f26d --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt @@ -0,0 +1,10 @@ +package io.emeraldpay.dshackle.upstream.ethereum.connectors + +import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator +import io.emeraldpay.grpc.Chain + +interface ConnectorFactory { + fun create(upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain): EthereumConnector + fun isValid(): Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt new file mode 100644 index 00000000..dcd8f5b0 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt @@ -0,0 +1,13 @@ +package io.emeraldpay.dshackle.upstream.ethereum.connectors + +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import org.springframework.context.Lifecycle + +interface EthereumConnector : Lifecycle { + fun getHead(): Head + + fun getApi(): Reader +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt new file mode 100644 index 00000000..68c11b43 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt @@ -0,0 +1,34 @@ +package io.emeraldpay.dshackle.upstream.ethereum.connectors + +import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.HttpFactory +import io.emeraldpay.dshackle.upstream.HttpRpcFactory +import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator +import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory + +open class EthereumConnectorFactory( + private val preferHttp: Boolean, + private val wsFactory: EthereumWsFactory?, + private val httpFactory: HttpFactory? +): ConnectorFactory { + private val log = LoggerFactory.getLogger(EthereumConnectorFactory::class.java) + + override fun isValid(): Boolean { + if (preferHttp && httpFactory == null) { + return false; + } + return true + } + + override fun create(upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain): EthereumConnector { + if (wsFactory!= null && !preferHttp) { + return EthereumWsConnector(wsFactory, upstream, validator, chain) + } + if (httpFactory == null) { + throw java.lang.IllegalArgumentException("Can't create rpc connector if no http factory set") + } + return EthereumRpcConnector(httpFactory.create(upstream.getId(), chain), wsFactory, upstream.getId()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt new file mode 100644 index 00000000..1dca02ba --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt @@ -0,0 +1,76 @@ +package io.emeraldpay.dshackle.upstream.ethereum.connectors + +import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.cache.CachesEnabled +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.MergedHead +import io.emeraldpay.dshackle.upstream.ethereum.* +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import java.time.Duration + +class EthereumRpcConnector( + private val directReader : Reader, + wsFactory: EthereumWsFactory?, + id : String, +) : EthereumConnector, CachesEnabled { + private val conn : WsConnection? + private val head : Head + + companion object { + private val log = LoggerFactory.getLogger(EthereumRpcConnector::class.java) + } + + init { + if (wsFactory != null) { + // do not set upstream to the WS, since it doesn't control the RPC upstream + conn = wsFactory.create(null, null, null) + val wsHead = EthereumWsHead(conn) + // receive bew blocks through WebSockets, but also periodically verify with RPC in case if WS failed + val rpcHead = EthereumRpcHead(directReader, Duration.ofSeconds(60)) + head = MergedHead(listOf(rpcHead, wsHead)) + } else { + conn = null + log.warn("Setting up connector for $id upstream with RPC-only access, less effective than WS+RPC") + head = EthereumRpcHead(directReader) + } + } + + override fun setCaches(caches: Caches) { + if (head is CachesEnabled) { + head.setCaches(caches) + } + } + + override fun start() { + if (head is Lifecycle) { + head.start() + } + conn?.connect() + } + + override fun isRunning(): Boolean { + if (head is Lifecycle) { + return head.isRunning + } + return true + } + + override fun stop() { + if (head is Lifecycle) { + head.stop() + } + conn?.close() + } + + override fun getApi(): Reader { + return directReader + } + + override fun getHead(): Head { + return head + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt new file mode 100644 index 00000000..fdb2e834 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt @@ -0,0 +1,74 @@ +package io.emeraldpay.dshackle.upstream.ethereum.connectors + +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator +import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory +import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsHead +import io.emeraldpay.dshackle.upstream.ethereum.WsConnection +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsClient +import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics +import io.emeraldpay.grpc.Chain +import io.micrometer.core.instrument.Counter +import io.micrometer.core.instrument.Metrics +import io.micrometer.core.instrument.Tag +import io.micrometer.core.instrument.Timer + +class EthereumWsConnector( + wsFactory: EthereumWsFactory, + upstream: DefaultUpstream, + validator: EthereumUpstreamValidator, + chain: Chain, +) : EthereumConnector { + private val conn: WsConnection + private val api: Reader + private val head: EthereumWsHead + + init { + val metricsTags = listOf( + Tag.of("upstream", upstream.getId()), + // UNSPECIFIED shouldn't happen too + Tag.of("chain", chain.chainCode) + ) + val metrics = RpcMetrics( + Timer.builder("upstream.ws.conn") + .description("Request time through a WebSocket JSON RPC connection") + .tags(metricsTags) + .publishPercentileHistogram() + .register(Metrics.globalRegistry), + Counter.builder("upstream.ws.fail") + .description("Number of failures of WebSocket JSON RPC requests") + .tags(metricsTags) + .register(Metrics.globalRegistry) + ) + + conn = wsFactory.create(upstream, validator, metrics) + head = EthereumWsHead(conn) + api = JsonRpcWsClient(conn) + } + + override fun start() { + conn.connect() + head.start() + } + + override fun isRunning(): Boolean { + return head.isRunning + } + + override fun stop() { + conn.close() + head.stop() + } + + override fun getApi(): Reader { + return api + } + + override fun getHead(): Head { + return head + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt new file mode 100644 index 00000000..298b784a --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt @@ -0,0 +1,50 @@ +package io.emeraldpay.dshackle.upstream.ethereum_pos + +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.startup.QuorumForLabels +import io.emeraldpay.dshackle.upstream.Capability +import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse + +class EthereumPosUpstream( + id: String, + options: UpstreamsConfig.Options, + role: UpstreamsConfig.UpstreamRole, + targets: CallMethods?, + node: QuorumForLabels.QuorumItem?, + private val ethereumUpstream: EthereumUpstream +) : DefaultUpstream(id, options, role, targets, node) { + override fun getCapabilities(): Set { + return ethereumUpstream.getCapabilities() + } + + override fun getLabels(): Collection { + return ethereumUpstream.getLabels() + } + + override fun getHead() : Head { + return ethereumUpstream.getHead() + } + + override fun isGrpc(): Boolean { + return false + } + + override fun getApi(): Reader { + return ethereumUpstream.getApi() + } + + @Suppress("UNCHECKED_CAST") + override fun cast(selfType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return this as T + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt index 3951d981..7a3c5006 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt @@ -24,11 +24,7 @@ import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.Capability -import io.emeraldpay.dshackle.upstream.Head -import io.emeraldpay.dshackle.upstream.Selector -import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.UpstreamAvailability +import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient @@ -53,7 +49,7 @@ open class EthereumGrpcUpstream( private val chain: Chain, private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, private val client: JsonRpcGrpcClient -) : EthereumUpstream( +) : DefaultUpstream( "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", UpstreamsConfig.Options.getDefaults(), role, diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy index 73f8db20..d69c22b6 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy @@ -154,6 +154,26 @@ class UpstreamsConfigReaderSpec extends Specification { } } + def "Parse ethereum pos upstreams"() { + setup: + def config = this.class.getClassLoader().getResourceAsStream("upstreams-ethereum-pos.yaml") + when: + def act = reader.read(config) + then: + act != null + act.upstreams.size() == 1 + with(act.upstreams.get(0)) { + id == "eth2-1" + chain == "ropsten" + connection instanceof UpstreamsConfig.EthereumPosConnection + with((UpstreamsConfig.EthereumPosConnection) connection) { + execution.rpc != null + execution.rpc.url == new URI("http://34.106.60.110:8545") + blockPriority == 100 + } + } + } + def "Parse bitcoin upstreams with esplora"() { setup: def config = this.class.getClassLoader().getResourceAsStream("upstreams-bitcoin-esplora.yaml") diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy index ae121f21..068d4240 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy @@ -87,7 +87,9 @@ class StreamHeadSpec extends Specification { ) then: StepVerifier.create(flux.take(2)) - .then { upstream.nextBlock(BlockContainer.from(blocks[0])) } + .then { + upstream.nextBlock(BlockContainer.from(blocks[0])) + } .expectNext(heads[0]) .then { upstream.nextBlock(BlockContainer.from(blocks[1])) } .expectNext(heads[1]) diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy new file mode 100644 index 00000000..34d7e3d6 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy @@ -0,0 +1,29 @@ +package io.emeraldpay.dshackle.test + +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator +import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.grpc.Chain + +class ConnectorFactoryMock implements ConnectorFactory { + Reader api + Head head + + ConnectorFactoryMock(Reader api, Head head) { + this.api = api + this.head = head + } + + boolean isValid() { + return true + } + + EthereumConnector create(DefaultUpstream upstream, EthereumUpstreamValidator validator, Chain chain) { + return new EthereumConnectorMock(api, head) + } +} \ No newline at end of file diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumConnectorMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumConnectorMock.groovy new file mode 100644 index 00000000..5e25d636 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumConnectorMock.groovy @@ -0,0 +1,37 @@ +package io.emeraldpay.dshackle.test + +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse + +class EthereumConnectorMock implements EthereumConnector { + Reader api + Head head + EthereumConnectorMock(Reader api, Head head) { + this.api = api + this.head = head + } + + @Override + Reader getApi() { + return this.api + } + + @Override + Head getHead() { + return this.head + } + + @Override + void start() {} + + @Override + void stop() {} + + @Override + boolean isRunning() { + return true + } +} \ No newline at end of file diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy index 4b292dd7..43caf2ee 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy @@ -19,14 +19,12 @@ package io.emeraldpay.dshackle.test import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.calls.AggregatedCallMethods import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest @@ -36,9 +34,10 @@ import io.emeraldpay.grpc.Chain import org.jetbrains.annotations.NotNull import org.reactivestreams.Publisher -class EthereumUpstreamMock extends EthereumRpcUpstream { - EthereumHeadMock ethereumHeadMock = new EthereumHeadMock() +class EthereumUpstreamMock extends EthereumUpstream { + EthereumHeadMock ethereumHeadMock + static CallMethods allMethods() { new AggregatedCallMethods([ @@ -61,32 +60,24 @@ class EthereumUpstreamMock extends EthereumRpcUpstream { } EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods) { - super(id, chain, api, null, + super(id, chain, UpstreamsConfig.Options.getDefaults(), UpstreamsConfig.UpstreamRole.PRIMARY, + methods, new QuorumForLabels.QuorumItem(1, new UpstreamsConfig.Labels()), - methods) + new ConnectorFactoryMock(api, new EthereumHeadMock())) + this.ethereumHeadMock = this.getHead() as EthereumHeadMock setLag(0) setStatus(UpstreamAvailability.OK) start() } void nextBlock(BlockContainer block) { - ethereumHeadMock.nextBlock(block) + this.ethereumHeadMock.nextBlock(block) } void setBlocks(Publisher blocks) { - ethereumHeadMock.predefined = blocks - } - - @Override - Head createHead() { - return ethereumHeadMock - } - - @Override - Head getHead() { - return ethereumHeadMock + this.ethereumHeadMock.predefined = blocks } @Override diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy index 960e0e40..eefdf46a 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy @@ -21,11 +21,9 @@ import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.test.EthereumApiStub import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory import io.emeraldpay.grpc.Chain -import reactor.core.publisher.Flux import reactor.test.StepVerifier import spock.lang.Retry import spock.lang.Specification @@ -46,15 +44,18 @@ class FilteredApisSpec extends Specification { [test: "foo"], [test: "baz"] ].collect { - new EthereumRpcUpstream( + def httpFactory = Mock(HttpFactory) { + create(_, _) >> TestingCommons.api().tap { it.id = "${i++}" } + } + def connectorFactory = new EthereumConnectorFactory(false, null, httpFactory) + new EthereumUpstream( "test", Chain.ETHEREUM, - TestingCommons.api().tap { it.id = "${i++}" }, - (EthereumWsFactory) null, new UpstreamsConfig.Options(), UpstreamsConfig.UpstreamRole.PRIMARY, + ethereumTargets, new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(it)), - ethereumTargets + connectorFactory ) } def matcher = new Selector.LabelMatcher("test", ["foo"]) diff --git a/src/test/resources/upstreams-ethereum-pos.yaml b/src/test/resources/upstreams-ethereum-pos.yaml new file mode 100644 index 00000000..b2a1a180 --- /dev/null +++ b/src/test/resources/upstreams-ethereum-pos.yaml @@ -0,0 +1,9 @@ + upstreams: + - id: eth2-1 + chain: ropsten + connection: + ethereum-pos: + execution: + rpc: + url: "http://34.106.60.110:8545" + block-priority: 100 \ No newline at end of file From eb95c591f786452e143f788f51c9031983b8e2a8 Mon Sep 17 00:00:00 2001 From: terminal Date: Fri, 29 Jul 2022 15:32:48 +0400 Subject: [PATCH 02/11] refactoring fork choice rules out of abstract head and priority fork choice support for PoS Ethereum --- .../dshackle/data/BlockContainer.kt | 7 +- .../io/emeraldpay/dshackle/data/RingSet.kt | 44 +++++ .../dshackle/startup/ConfiguredUpstreams.kt | 60 ++++--- .../dshackle/upstream/AbstractHead.kt | 37 ++-- .../upstream/CurrentMultistreamHolder.kt | 11 ++ .../dshackle/upstream/DistanceExtractor.kt | 28 +++ .../dshackle/upstream/HeadLagObserver.kt | 11 +- .../dshackle/upstream/MergedHead.kt | 6 +- .../bitcoin/BitcoinHeadLagObserver.kt | 3 +- .../upstream/bitcoin/BitcoinMultistream.kt | 3 +- .../upstream/bitcoin/BitcoinRpcHead.kt | 3 +- .../upstream/bitcoin/BitcoinZMQHead.kt | 3 +- .../upstream/ethereum/DefaultEthereumHead.kt | 5 +- .../upstream/ethereum/EthereumFees.kt | 3 +- .../ethereum/EthereumHeadLagObserver.kt | 3 +- .../upstream/ethereum/EthereumMultistream.kt | 3 +- .../upstream/ethereum/EthereumPriorityFees.kt | 3 +- .../upstream/ethereum/EthereumRpcHead.kt | 6 +- .../upstream/ethereum/EthereumUpstream.kt | 1 - .../ethereum/EthereumUpstreamValidator.kt | 4 +- .../upstream/ethereum/EthereumWsHead.kt | 6 +- .../connectors/EthereumConnectorFactory.kt | 8 +- .../connectors/EthereumRpcConnector.kt | 11 +- .../connectors/EthereumWsConnector.kt | 4 +- .../EthereumPosHeadLagObserver.kt | 22 +++ .../ethereum_pos/EthereumPosMultiStream.kt | 142 +++++++++++++++ .../ethereum_pos/EthereumPosUpstream.kt | 101 +++++++++-- .../upstream/forkchoice/ForkChoice.kt | 17 ++ .../upstream/forkchoice/MostWorkForkChoice.kt | 32 ++++ .../NoChoiceWithPriorityForkChoice.kt | 36 ++++ .../upstream/forkchoice/PriorityForkChoice.kt | 35 ++++ .../upstream/grpc/BitcoinGrpcUpstream.kt | 3 +- .../upstream/grpc/EthereumGrpcUpstream.kt | 3 +- .../upstream/grpc/EthereumPosGrpcUpstream.kt | 162 ++++++++++++++++++ .../dshackle/upstream/grpc/GrpcHead.kt | 11 +- .../cache/HeightByHashAddingSpec.groovy | 2 +- .../dshackle/cache/ReceiptMemCacheSpec.groovy | 3 +- .../rpc/TrackBitcoinAddressSpec.groovy | 4 +- .../dshackle/rpc/TrackBitcoinTxSpec.groovy | 6 +- .../dshackle/rpc/TrackEthereumTxSpec.groovy | 5 +- .../dshackle/test/TestingCommons.groovy | 3 +- .../dshackle/upstream/AbstractHeadSpec.groovy | 26 ++- .../upstream/DistanceExtractorSpec.groovy | 74 ++++++++ .../dshackle/upstream/FilteredApisSpec.groovy | 3 +- .../upstream/HeadLagObserverSpec.groovy | 37 +--- .../dshackle/upstream/MergedHeadSpec.groovy | 11 +- .../ethereum/DefaultEthereumHeadSpec.groovy | 3 +- .../forkchoice/MostWorkForkChoiceSpec.groovy | 37 ++++ .../NoChoiceWithPriorityForkChoiceSpec.groovy | 51 ++++++ .../forkchoice/PriorityForkChoiceSpec.groovy | 41 +++++ .../upstream/grpc/GrpcHeadSpec.groovy | 5 +- 51 files changed, 995 insertions(+), 153 deletions(-) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosHeadLagObserver.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt create mode 100644 src/test/groovy/io/emeraldpay/dshackle/upstream/DistanceExtractorSpec.groovy create mode 100644 src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoiceSpec.groovy create mode 100644 src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoiceSpec.groovy create mode 100644 src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoiceSpec.groovy diff --git a/src/main/kotlin/io/emeraldpay/dshackle/data/BlockContainer.kt b/src/main/kotlin/io/emeraldpay/dshackle/data/BlockContainer.kt index f5c47d3f..ac411dbb 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/data/BlockContainer.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/data/BlockContainer.kt @@ -30,7 +30,8 @@ class BlockContainer( val full: Boolean, json: ByteArray?, val parsed: Any?, - val transactions: List = emptyList() + val transactions: List = emptyList(), + val nodeRating: Int = 0 ) : SourceContainer(json, parsed) { companion object { @@ -82,6 +83,10 @@ class BlockContainer( return true } + fun copyWithRating(nodeRating: Int): BlockContainer { + return BlockContainer(height, hash, difficulty, timestamp, full, json, parsed, transactions, nodeRating) + } + override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + height.hashCode() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt b/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt new file mode 100644 index 00000000..a3e1815e --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt @@ -0,0 +1,44 @@ +package io.emeraldpay.dshackle.data + +import java.util.concurrent.atomic.AtomicReference + +class RingSet( + private val maxSize: Int +): Set { + private var seqValues: AtomicReference> = AtomicReference(emptyList()) + private var set: Set = emptySet() + override val size: Int + get() = set.size + + fun add(element: T) { + if (set.contains(element)) { + return + } + seqValues.getAndUpdate { vals -> + vals.let { + if (vals.size > maxSize) { + vals.drop(1) + } else { + vals + } + }.plus(element).let { + set = HashSet(it) + it + } + } + } + + override fun isEmpty(): Boolean { + return set.isEmpty() + } + override fun contains(element: @UnsafeVariance T): Boolean { + return set.contains(element) + } + override fun iterator(): Iterator { + return set.iterator() + } + + override fun containsAll(elements: Collection<@UnsafeVariance T>): Boolean { + return set.containsAll(elements) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index bcf9e216..10e320ff 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -29,9 +29,13 @@ import io.emeraldpay.dshackle.upstream.bitcoin.ExtractBlock import io.emeraldpay.dshackle.upstream.bitcoin.ZMQServer import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods +import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice +import io.emeraldpay.dshackle.upstream.forkchoice.NoChoiceWithPriorityForkChoice import io.emeraldpay.dshackle.upstream.grpc.GrpcUpstreams import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse @@ -82,9 +86,9 @@ open class ConfiguredUpstreams( BlockchainType.BITCOIN -> { buildBitcoinUpstream(up.cast(UpstreamsConfig.BitcoinConnection::class.java), chain, options) } -// BlockchainType.ETHEREUM_POS -> { -// buildEthereumPosUpstream(up.cast(UpstreamsConfig.EthereumPosConnection::class.java), chain, options) -// } + BlockchainType.ETHEREUM_POS -> { + buildEthereumPosUpstream(up.cast(UpstreamsConfig.EthereumPosConnection::class.java), chain, options) + } else -> { log.error("Chain is unsupported: ${up.chain}") return@forEach @@ -139,20 +143,34 @@ open class ConfiguredUpstreams( } } -// private fun buildEthereumPosUpstream( -// config: UpstreamsConfig.Upstream, -// chain: Chain, -// options: UpstreamsConfig.Options -// ) : Upstream? { -// val conn = config.connection!! -// val execution = conn.execution -// if (execution == null) { -// log.warn("Upstream doesn't have execution layer configuration") -// return null -// } -// -// val connectorFactory = buildEthereumConnectorFactory(execution, chain) -// } + private fun buildEthereumPosUpstream( + config: UpstreamsConfig.Upstream, + chain: Chain, + options: UpstreamsConfig.Options + ) : Upstream? { + val conn = config.connection!! + val execution = conn.execution + if (execution == null) { + log.warn("Upstream doesn't have execution layer configuration") + return null + } + val urls = ArrayList() + val connectorFactory = buildEthereumConnectorFactory(execution, chain, urls, NoChoiceWithPriorityForkChoice(conn.blockPriority)) + val methods = buildMethods(config, chain) + if (connectorFactory == null) { + return null + } + val upstream = EthereumPosUpstream( + config.id!!, + chain, + options, config.role, + methods, + QuorumForLabels.QuorumItem(1, config.labels), + connectorFactory + ) + upstream.start() + return upstream + } private fun buildBitcoinUpstream( config: UpstreamsConfig.Upstream, @@ -180,7 +198,7 @@ open class ConfiguredUpstreams( val head: Head = conn.zeroMq?.let { zeroMq -> val server = ZMQServer(zeroMq.host, zeroMq.port, "hashblock") val zeroMqHead = BitcoinZMQHead(server, directApi, extractBlock) - MergedHead(listOf(rpcHead, zeroMqHead)) + MergedHead(listOf(rpcHead, zeroMqHead), MostWorkForkChoice()) } ?: rpcHead val methods = buildMethods(config, chain) @@ -206,7 +224,7 @@ open class ConfiguredUpstreams( val urls = ArrayList() val methods = buildMethods(config, chain) - val connectorFactory = buildEthereumConnectorFactory(conn, chain, urls) + val connectorFactory = buildEthereumConnectorFactory(conn, chain, urls, MostWorkForkChoice()) if (connectorFactory == null) { return null } @@ -272,11 +290,11 @@ open class ConfiguredUpstreams( } } - private fun buildEthereumConnectorFactory(conn: UpstreamsConfig.EthereumConnection, chain: Chain, urls: ArrayList): EthereumConnectorFactory? { + private fun buildEthereumConnectorFactory(conn: UpstreamsConfig.EthereumConnection, chain: Chain, urls: ArrayList, forkChoice: ForkChoice): EthereumConnectorFactory? { val wsFactoryApi = buildWsFactory(conn, urls) val httpFactory = buildHttpFactory(conn, urls) log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}") - val connectorFactory = EthereumConnectorFactory(conn.preferHttp, wsFactoryApi, httpFactory) + val connectorFactory = EthereumConnectorFactory(conn.preferHttp, wsFactoryApi, httpFactory, forkChoice) if (!connectorFactory.isValid()) { log.warn("Upstream configuration is invalid (probably no http endpoint)") return null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt index 9f85e7f6..ce4e7931 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt @@ -16,21 +16,24 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import org.slf4j.LoggerFactory import reactor.core.Disposable import reactor.core.publisher.Flux import reactor.core.publisher.Mono import reactor.core.publisher.Sinks import reactor.core.scheduler.Schedulers +import reactor.kotlin.core.publisher.toMono import java.util.concurrent.atomic.AtomicReference -abstract class AbstractHead : Head { +abstract class AbstractHead( + private val forkChoice: ForkChoice +) : Head { companion object { private val log = LoggerFactory.getLogger(AbstractHead::class.java) } - private val head = AtomicReference(null) private var stream = Sinks.many().multicast().directBestEffort() private var completed = false private val beforeBlockHandlers = ArrayList() @@ -44,10 +47,8 @@ abstract class AbstractHead : Head { return source .distinctUntilChanged { it.hash - }.filter { block -> - val curr = head.get() - curr == null || curr.difficulty < block.difficulty } + .filter { forkChoice.filter(it) } .doFinally { // close internal stream if upstream is finished, otherwise it gets stuck, // but technically it should never happen during normal work, only when the Head @@ -58,19 +59,16 @@ abstract class AbstractHead : Head { .subscribeOn(Schedulers.boundedElastic()) .subscribe { block -> notifyBeforeBlock() - val prev = head.getAndUpdate { curr -> - if (curr == null || curr.difficulty < block.difficulty) { - block - } else { - curr - } - } - if (prev == null || prev.hash != block.hash) { - log.debug("New block ${block.height} ${block.hash}") - val result = stream.tryEmitNext(block) - if (result.isFailure && result != Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER) { - log.warn("Failed to dispatch block: $result as ${this.javaClass}") + when (val choiceResult = forkChoice.choose(block)) { + is ForkChoice.ChoiceResult.Updated -> { + val newHead = choiceResult.nwhead + log.debug("New block ${newHead.height} ${newHead.hash}") + val result = stream.tryEmitNext(newHead) + if (result.isFailure && result != Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER) { + log.warn("Failed to dispatch block: $result as ${this.javaClass}") + } } + is ForkChoice.ChoiceResult.Same -> {} } } } @@ -90,14 +88,15 @@ abstract class AbstractHead : Head { } override fun getFlux(): Flux { + val curHead = forkChoice.getHead() return Flux.concat( - Mono.justOrEmpty(head.get()), + forkChoice.getHead().toMono(), stream.asFlux() ).onBackpressureLatest() } fun getCurrent(): BlockContainer? { - return head.get() + return forkChoice.getHead() } override fun getCurrentHeight(): Long? { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt index 9a99d6de..4de9bbef 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt @@ -25,6 +25,8 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultistream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.grpc.BlockchainType import io.emeraldpay.grpc.Chain @@ -68,6 +70,14 @@ open class CurrentMultistreamHolder( } processUpdate(change, up, current, factory) } + BlockchainType.ETHEREUM_POS -> { + val up = change.upstream.cast(EthereumPosUpstream::class.java) + val current = chainMapping[chain] + val factory = Callable { + EthereumPosMultistream(chain, ArrayList(), cachesFactory.getCaches(chain)) + } + processUpdate(change, up, current, factory) + } BlockchainType.BITCOIN -> { val up = change.upstream.cast(BitcoinUpstream::class.java) val current = chainMapping[chain] @@ -137,6 +147,7 @@ open class CurrentMultistreamHolder( val created = when (BlockchainType.from(chain)) { BlockchainType.ETHEREUM -> DefaultEthereumMethods(chain) BlockchainType.BITCOIN -> DefaultBitcoinMethods() + BlockchainType.ETHEREUM_POS -> DefaultEthereumMethods(chain) else -> throw IllegalStateException("Unsupported chain: $chain") } callTargets[chain] = created diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt new file mode 100644 index 00000000..77dc9b61 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt @@ -0,0 +1,28 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.data.BlockContainer + +class DistanceExtractor { + sealed class ChainDistance { + data class Distance(val dist: Long): ChainDistance() + object Fork: ChainDistance() + } + + companion object { + fun extractPowDistance(top: BlockContainer, curr: BlockContainer): ChainDistance { + return when { + curr.height > top.height -> if (curr.difficulty >= top.difficulty) ChainDistance.Distance(0) else ChainDistance.Fork + curr.height == top.height -> if (curr.difficulty == top.difficulty) ChainDistance.Distance(0) else ChainDistance.Fork + else -> ChainDistance.Distance(top.height - curr.height) + } + } + + fun extractPriorityDistance(top: BlockContainer, curr: BlockContainer): ChainDistance { + return when { + curr.height > top.height -> ChainDistance.Fork + curr.height == top.height -> if (curr.hash == top.hash) ChainDistance.Distance(0) else ChainDistance.Fork + else -> ChainDistance.Distance(top.height - curr.height) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HeadLagObserver.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HeadLagObserver.kt index 748e729d..a1c61c2f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HeadLagObserver.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HeadLagObserver.kt @@ -30,9 +30,11 @@ import java.time.Duration * Observer group of upstreams and defined a distance in blocks (lag) between a leader (best height/difficulty) and * other upstreams. */ +typealias Extractor = (top: BlockContainer, curr: BlockContainer) -> DistanceExtractor.ChainDistance abstract class HeadLagObserver( private val master: Head, - private val followers: Collection + private val followers: Collection, + private val distanceExtractor: Extractor ) : Lifecycle { private val log = LoggerFactory.getLogger(HeadLagObserver::class.java) @@ -85,10 +87,9 @@ abstract class HeadLagObserver( } open fun extractDistance(top: BlockContainer, curr: BlockContainer): Long { - return when { - curr.height > top.height -> if (curr.difficulty >= top.difficulty) 0 else forkDistance(top, curr) - curr.height == top.height -> if (curr.difficulty == top.difficulty) 0 else forkDistance(top, curr) - else -> top.height - curr.height + return when (val distance = distanceExtractor(top, curr)) { + is DistanceExtractor.ChainDistance.Distance -> distance.dist + is DistanceExtractor.ChainDistance.Fork -> forkDistance(top, curr) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/MergedHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/MergedHead.kt index 57a65ca1..0c87de88 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/MergedHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/MergedHead.kt @@ -18,13 +18,15 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.CachesEnabled +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import org.springframework.context.Lifecycle import reactor.core.Disposable import reactor.core.publisher.Flux class MergedHead( - private val sources: Iterable -) : AbstractHead(), Lifecycle, CachesEnabled { + private val sources: Iterable, + forkChoice: ForkChoice +) : AbstractHead(forkChoice), Lifecycle, CachesEnabled { private var subscription: Disposable? = null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinHeadLagObserver.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinHeadLagObserver.kt index ffc3740b..fbfb663d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinHeadLagObserver.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinHeadLagObserver.kt @@ -16,6 +16,7 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.DistanceExtractor import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.Upstream @@ -24,7 +25,7 @@ import org.slf4j.LoggerFactory class BitcoinHeadLagObserver( master: Head, followers: Collection -) : HeadLagObserver(master, followers) { +) : HeadLagObserver(master, followers, DistanceExtractor::extractPowDistance) { companion object { private val log = LoggerFactory.getLogger(BitcoinHeadLagObserver::class.java) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt index c7740f1e..8a9054b7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt @@ -27,6 +27,7 @@ import io.emeraldpay.dshackle.upstream.RequestPostprocessor import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.ethereum.LocalCallRouter +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.grpc.Chain @@ -84,7 +85,7 @@ open class BitcoinMultistream( } } } else { - val newHead = MergedHead(upstreams.map { it.getHead() }).apply { + val newHead = MergedHead(upstreams.map { it.getHead() }, MostWorkForkChoice()).apply { this.start() } val lagObserver = BitcoinHeadLagObserver(newHead, upstreams) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt index f0353f44..982d4fb2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt @@ -19,6 +19,7 @@ import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.AbstractHead import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import org.slf4j.LoggerFactory @@ -35,7 +36,7 @@ class BitcoinRpcHead( private val api: Reader, private val extractBlock: ExtractBlock, private val interval: Duration = Duration.ofSeconds(15) -) : Head, AbstractHead(), Lifecycle { +) : Head, AbstractHead(MostWorkForkChoice()), Lifecycle { companion object { private val log = LoggerFactory.getLogger(BitcoinRpcHead::class.java) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt index fa0d206d..ca916408 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt @@ -5,6 +5,7 @@ import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.AbstractHead import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import org.apache.commons.codec.binary.Hex @@ -20,7 +21,7 @@ class BitcoinZMQHead( private val server: ZMQServer, private val api: Reader, private val extractBlock: ExtractBlock, -) : Head, AbstractHead(), Lifecycle { +) : Head, AbstractHead(MostWorkForkChoice()), Lifecycle { companion object { private val log = LoggerFactory.getLogger(BitcoinZMQHead::class.java) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt index 8fbe804d..ded52346 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt @@ -20,13 +20,16 @@ import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.AbstractHead import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.etherjar.hex.HexQuantity import org.slf4j.LoggerFactory import reactor.core.publisher.Mono -open class DefaultEthereumHead : Head, AbstractHead() { +open class DefaultEthereumHead( + forkChoice: ForkChoice +) : Head, AbstractHead(forkChoice) { companion object { private val log = LoggerFactory.getLogger(DefaultEthereumHead::class.java) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumFees.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumFees.kt index 3e798ecf..0cf7c42c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumFees.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumFees.kt @@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.upstream.AbstractChainFees import io.emeraldpay.dshackle.upstream.ChainFees +import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.etherjar.domain.Wei import io.emeraldpay.etherjar.rpc.json.BlockJson import io.emeraldpay.etherjar.rpc.json.TransactionJson @@ -28,7 +29,7 @@ import reactor.util.function.Tuples import java.util.function.Function abstract class EthereumFees( - upstreams: EthereumMultistream, + upstreams: Multistream, private val reader: EthereumReader, heightLimit: Int, ) : AbstractChainFees, TransactionRefJson, TransactionJson>(heightLimit, upstreams, extractTx), ChainFees { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt index 9d9d3b87..c933214b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt @@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.DistanceExtractor import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.Upstream @@ -25,7 +26,7 @@ import org.slf4j.LoggerFactory class EthereumHeadLagObserver( master: Head, followers: Collection -) : HeadLagObserver(master, followers) { +) : HeadLagObserver(master, followers, DistanceExtractor::extractPowDistance) { companion object { private val log = LoggerFactory.getLogger(EthereumHeadLagObserver::class.java) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt index 0e4fcdfe..f9557cb7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt @@ -25,6 +25,7 @@ import io.emeraldpay.dshackle.upstream.MergedHead import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.grpc.Chain @@ -109,7 +110,7 @@ open class EthereumMultistream( } } else { val heads = upstreams.map { it.getHead() } - val newHead = MergedHead(heads).apply { + val newHead = MergedHead(heads, MostWorkForkChoice()).apply { this.start() } val lagObserver = EthereumHeadLagObserver(newHead, upstreams as Collection) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumPriorityFees.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumPriorityFees.kt index f3138799..274f6c61 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumPriorityFees.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumPriorityFees.kt @@ -16,6 +16,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.etherjar.domain.Wei import io.emeraldpay.etherjar.rpc.json.BlockJson import io.emeraldpay.etherjar.rpc.json.TransactionJson @@ -23,7 +24,7 @@ import io.emeraldpay.etherjar.rpc.json.TransactionRefJson import org.slf4j.LoggerFactory import java.util.function.Function -class EthereumPriorityFees(upstreams: EthereumMultistream, reader: EthereumReader, heightLimit: Int) : +class EthereumPriorityFees(upstreams: Multistream, reader: EthereumReader, heightLimit: Int) : EthereumFees(upstreams, reader, heightLimit) { companion object { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcHead.kt index 572f4fcf..104a9bba 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcHead.kt @@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import org.slf4j.LoggerFactory @@ -30,8 +31,9 @@ import java.util.concurrent.Executors class EthereumRpcHead( private val api: Reader, - private val interval: Duration = Duration.ofSeconds(10) -) : DefaultEthereumHead(), Lifecycle { + forkChoice: ForkChoice, + private val interval: Duration = Duration.ofSeconds(10), +) : DefaultEthereumHead(forkChoice), Lifecycle { companion object { val scheduler = diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt index 4248f78f..ef226380 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt @@ -63,7 +63,6 @@ open class EthereumUpstream( this.setStatus(UpstreamAvailability.OK) } else { log.debug("Start validation for upstream ${this.getId()}") - val validator = EthereumUpstreamValidator(this, getOptions()) validatorSubscription = validator.start() .subscribe(this::setStatus) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt index cc7feca8..d8cbc7bd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt @@ -20,7 +20,9 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.etherjar.rpc.json.SyncingJson @@ -34,7 +36,7 @@ import java.util.concurrent.Executors import java.util.concurrent.TimeoutException open class EthereumUpstreamValidator( - private val upstream: EthereumUpstream, + private val upstream: Upstream, private val options: UpstreamsConfig.Options ) { companion object { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsHead.kt index 117e901b..42c7b717 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWsHead.kt @@ -16,6 +16,7 @@ */ package io.emeraldpay.dshackle.upstream.ethereum +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsClient import org.slf4j.LoggerFactory import org.springframework.context.Lifecycle @@ -23,8 +24,9 @@ import reactor.core.Disposable import reactor.core.publisher.Flux class EthereumWsHead( - private val ws: WsConnection -) : DefaultEthereumHead(), Lifecycle { + private val ws: WsConnection, + forkChoice: ForkChoice +) : DefaultEthereumHead(forkChoice), Lifecycle { private val log = LoggerFactory.getLogger(EthereumWsHead::class.java) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt index 68c11b43..78f69556 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt @@ -5,13 +5,15 @@ import io.emeraldpay.dshackle.upstream.HttpFactory import io.emeraldpay.dshackle.upstream.HttpRpcFactory import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.grpc.Chain import org.slf4j.LoggerFactory open class EthereumConnectorFactory( private val preferHttp: Boolean, private val wsFactory: EthereumWsFactory?, - private val httpFactory: HttpFactory? + private val httpFactory: HttpFactory?, + private val forkChoice: ForkChoice ): ConnectorFactory { private val log = LoggerFactory.getLogger(EthereumConnectorFactory::class.java) @@ -24,11 +26,11 @@ open class EthereumConnectorFactory( override fun create(upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain): EthereumConnector { if (wsFactory!= null && !preferHttp) { - return EthereumWsConnector(wsFactory, upstream, validator, chain) + return EthereumWsConnector(wsFactory, upstream, validator, chain, forkChoice) } if (httpFactory == null) { throw java.lang.IllegalArgumentException("Can't create rpc connector if no http factory set") } - return EthereumRpcConnector(httpFactory.create(upstream.getId(), chain), wsFactory, upstream.getId()) + return EthereumRpcConnector(httpFactory.create(upstream.getId(), chain), wsFactory, upstream.getId(), forkChoice) } } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt index 1dca02ba..8ca9d135 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt @@ -6,6 +6,8 @@ import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.MergedHead import io.emeraldpay.dshackle.upstream.ethereum.* +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import org.slf4j.LoggerFactory @@ -16,6 +18,7 @@ class EthereumRpcConnector( private val directReader : Reader, wsFactory: EthereumWsFactory?, id : String, + forkChoice: ForkChoice ) : EthereumConnector, CachesEnabled { private val conn : WsConnection? private val head : Head @@ -28,14 +31,14 @@ class EthereumRpcConnector( if (wsFactory != null) { // do not set upstream to the WS, since it doesn't control the RPC upstream conn = wsFactory.create(null, null, null) - val wsHead = EthereumWsHead(conn) + val wsHead = EthereumWsHead(conn, forkChoice) // receive bew blocks through WebSockets, but also periodically verify with RPC in case if WS failed - val rpcHead = EthereumRpcHead(directReader, Duration.ofSeconds(60)) - head = MergedHead(listOf(rpcHead, wsHead)) + val rpcHead = EthereumRpcHead(directReader, forkChoice, Duration.ofSeconds(60)) + head = MergedHead(listOf(rpcHead, wsHead), forkChoice) } else { conn = null log.warn("Setting up connector for $id upstream with RPC-only access, less effective than WS+RPC") - head = EthereumRpcHead(directReader) + head = EthereumRpcHead(directReader, forkChoice) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt index fdb2e834..46bd437b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt @@ -7,6 +7,7 @@ import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsHead import io.emeraldpay.dshackle.upstream.ethereum.WsConnection +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsClient @@ -22,6 +23,7 @@ class EthereumWsConnector( upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain, + forkChoice: ForkChoice ) : EthereumConnector { private val conn: WsConnection private val api: Reader @@ -46,7 +48,7 @@ class EthereumWsConnector( ) conn = wsFactory.create(upstream, validator, metrics) - head = EthereumWsHead(conn) + head = EthereumWsHead(conn, forkChoice) api = JsonRpcWsClient(conn) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosHeadLagObserver.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosHeadLagObserver.kt new file mode 100644 index 00000000..87b4b42e --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosHeadLagObserver.kt @@ -0,0 +1,22 @@ +package io.emeraldpay.dshackle.upstream.ethereum + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.DistanceExtractor +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.HeadLagObserver +import io.emeraldpay.dshackle.upstream.Upstream +import org.slf4j.LoggerFactory + +class EthereumPostHeadLagObserver( + master: Head, + followers: Collection +) : HeadLagObserver(master, followers, DistanceExtractor::extractPriorityDistance) { + + companion object { + private val log = LoggerFactory.getLogger(EthereumPostHeadLagObserver::class.java) + } + + override fun forkDistance(top: BlockContainer, curr: BlockContainer): Long { + return 6 + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt new file mode 100644 index 00000000..aab4ba37 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2020 EmeraldPay, Inc + * Copyright (c) 2020 ETCDEV GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.emeraldpay.dshackle.upstream.ethereum + +import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.ChainFees +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.MergedHead +import io.emeraldpay.dshackle.upstream.Multistream +import io.emeraldpay.dshackle.upstream.Selector +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice +import io.emeraldpay.dshackle.upstream.forkchoice.PriorityForkChoice +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.publisher.Mono + +@Suppress("UNCHECKED_CAST") +open class EthereumPosMultistream( + chain: Chain, + val upstreams: MutableList, + caches: Caches +) : Multistream(chain, upstreams as MutableList, caches, CacheRequested(caches)) { + + companion object { + private val log = LoggerFactory.getLogger(EthereumPosMultistream::class.java) + } + + private var head: Head? = null + + private val reader: EthereumReader = EthereumReader(this, this.caches, getMethodsFactory()) + private val feeEstimation = EthereumPriorityFees(this, reader, 256) + init { + this.init() + } + + override fun init() { + if (upstreams.size > 0) { + head = updateHead() + } + super.init() + } + + override fun start() { + super.start() + reader.start() + } + + override fun stop() { + super.stop() + reader.stop() + } + + override fun isRunning(): Boolean { + return super.isRunning() || reader.isRunning + } + + open fun getReader(): EthereumReader { + return reader + } + + override fun getHead(): Head { + return head!! + } + + override fun setHead(head: Head) { + this.head = head + } + + override fun updateHead(): Head { + head?.let { + if (it is Lifecycle) { + it.stop() + } + } + lagObserver?.stop() + lagObserver = null + val head = if (upstreams.size == 1) { + val upstream = upstreams.first() + upstream.setLag(0) + upstream.getHead().apply { + if (this is Lifecycle) { + this.start() + } + } + } else { + val heads = upstreams.map { it.getHead() } + val newHead = MergedHead(heads, PriorityForkChoice()).apply { + this.start() + } + val lagObserver = EthereumPostHeadLagObserver(newHead, upstreams as Collection) + this.lagObserver = lagObserver + lagObserver.start() + newHead + } + onHeadUpdated(head) + return head + } + + override fun getLabels(): Collection { + return upstreams.flatMap { it.getLabels() } + } + + @Suppress("UNCHECKED_CAST") + override fun cast(selfType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return this as T + } + + override fun getRoutedApi(matcher: Selector.Matcher): Mono> { + return Mono.just(LocalCallRouter(reader, getMethods(), getHead())) + } + + open fun getSubscribe(): EthereumSubscribe { + throw Error("Does not supports subscription for PoS ethereum") + } + + override fun getFeeEstimation(): ChainFees { + return feeEstimation + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt index 298b784a..1a75cf21 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt @@ -1,43 +1,106 @@ -package io.emeraldpay.dshackle.upstream.ethereum_pos +/** + * Copyright (c) 2020 EmeraldPay, Inc + * 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.upstream.ethereum +import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.Capability -import io.emeraldpay.dshackle.upstream.DefaultUpstream -import io.emeraldpay.dshackle.upstream.Head -import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.CallMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.Disposable -class EthereumPosUpstream( +open class EthereumPosUpstream( id: String, + val chain: Chain, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, - node: QuorumForLabels.QuorumItem?, - private val ethereumUpstream: EthereumUpstream -) : DefaultUpstream(id, options, role, targets, node) { - override fun getCapabilities(): Set { - return ethereumUpstream.getCapabilities() + private val node: QuorumForLabels.QuorumItem?, + connectorFactory: ConnectorFactory +) : DefaultUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { + private val log = LoggerFactory.getLogger(EthereumPosUpstream::class.java) + private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) + private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) + + private var validatorSubscription: Disposable? = null + + override fun setCaches(caches: Caches) { + if (connector is CachesEnabled) { + connector.setCaches(caches) + } } - override fun getLabels(): Collection { - return ethereumUpstream.getLabels() + override fun start() { + log.info("Configured for ${chain.chainName}") + connector.start() + if (getOptions().disableValidation != null && getOptions().disableValidation!!) { + log.warn("Disable validation for upstream ${this.getId()}") + this.setLag(0) + this.setStatus(UpstreamAvailability.OK) + } else { + log.debug("Start validation for upstream ${this.getId()}") + validatorSubscription = validator.start() + .subscribe(this::setStatus) + } + } + override fun getHead(): Head { + return connector.getHead() } - override fun getHead() : Head { - return ethereumUpstream.getHead() + override fun stop() { + validatorSubscription?.dispose() + validatorSubscription = null + connector.stop() + } + + override fun isRunning(): Boolean { + return connector.isRunning + } + + override fun getApi(): Reader { + return connector.getApi() } override fun isGrpc(): Boolean { return false } - override fun getApi(): Reader { - return ethereumUpstream.getApi() + private val capabilities = if (options.providesBalance != false) { + setOf(Capability.RPC, Capability.BALANCE) + } else { + setOf(Capability.RPC) + } + + override fun getCapabilities(): Set { + return capabilities + } + + override fun getLabels(): Collection { + return node?.let { listOf(it.labels) } ?: emptyList() } @Suppress("UNCHECKED_CAST") @@ -47,4 +110,4 @@ class EthereumPosUpstream( } return this as T } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt new file mode 100644 index 00000000..aa664241 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt @@ -0,0 +1,17 @@ +package io.emeraldpay.dshackle.upstream.forkchoice + +import io.emeraldpay.dshackle.data.BlockContainer + +interface ForkChoice { + + sealed class ChoiceResult { + data class Updated(val nwhead: BlockContainer): ChoiceResult() + data class Same(val head: BlockContainer?): ChoiceResult() + } + + fun getHead(): BlockContainer? + + fun filter(block: BlockContainer): Boolean + + fun choose(block: BlockContainer): ChoiceResult +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt new file mode 100644 index 00000000..d4c5e9c0 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt @@ -0,0 +1,32 @@ +package io.emeraldpay.dshackle.upstream.forkchoice + +import io.emeraldpay.dshackle.data.BlockContainer +import java.util.concurrent.atomic.AtomicReference + +class MostWorkForkChoice : ForkChoice { + private val head = AtomicReference(null) + + override fun getHead() : BlockContainer? { + return head.get() + } + + override fun filter(block: BlockContainer): Boolean { + val curr = head.get() + return curr == null || curr.difficulty < block.difficulty + } + + override fun choose(block: BlockContainer): ForkChoice.ChoiceResult { + val nwhead = head.updateAndGet { curr -> + if (filter(block)) { + block + } else { + curr + } + } + if (nwhead.hash == block.hash) { + return ForkChoice.ChoiceResult.Updated(nwhead) + } + return ForkChoice.ChoiceResult.Same(nwhead) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt new file mode 100644 index 00000000..523f4547 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt @@ -0,0 +1,36 @@ +package io.emeraldpay.dshackle.upstream.forkchoice + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.data.RingSet +import java.util.concurrent.atomic.AtomicReference + +class NoChoiceWithPriorityForkChoice( + private val nodeRating: Int +): ForkChoice { + private val head = AtomicReference(null) + private val seenBlocks = RingSet(10) + + override fun getHead(): BlockContainer? { + return head.get() + } + + override fun filter(block: BlockContainer): Boolean { + return !seenBlocks.contains(block.hash) + } + + override fun choose(block: BlockContainer): ForkChoice.ChoiceResult { + val nwhead = head.updateAndGet { curr -> + if (!filter(block)) { + curr + } else { + seenBlocks.add(block.hash) + block.copyWithRating(nodeRating) + } + } + if (nwhead.hash == block.hash) { + return ForkChoice.ChoiceResult.Updated(nwhead) + } + return ForkChoice.ChoiceResult.Same(nwhead) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt new file mode 100644 index 00000000..e9ec338b --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt @@ -0,0 +1,35 @@ +package io.emeraldpay.dshackle.upstream.forkchoice + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.data.RingSet +import java.util.concurrent.atomic.AtomicReference + +class PriorityForkChoice: ForkChoice { + private val head = AtomicReference(null) + private val seenBlocks = RingSet(10) + + override fun getHead(): BlockContainer? { + return head.get() + } + + override fun filter(block: BlockContainer): Boolean { + val curr = head.get() + return (curr == null || curr.nodeRating <= block.nodeRating) && !seenBlocks.contains(block.hash) + } + + override fun choose(block: BlockContainer): ForkChoice.ChoiceResult { + val nwhead = head.updateAndGet { curr -> + if (!filter(block)) { + curr + } else { + seenBlocks.add(block.hash) + block + } + } + if (nwhead.hash == block.hash) { + return ForkChoice.ChoiceResult.Updated(nwhead) + } + return ForkChoice.ChoiceResult.Same(nwhead) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt index 542688d3..e4205af9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt @@ -29,6 +29,7 @@ import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream import io.emeraldpay.dshackle.upstream.bitcoin.ExtractBlock +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse @@ -95,7 +96,7 @@ class BitcoinGrpcUpstream( } } private val upstreamStatus = GrpcUpstreamStatus() - private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock) + private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice()) var timeout = Defaults.timeout private var capabilities: Set = emptySet() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt index 7a3c5006..b7824b01 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt @@ -27,6 +27,7 @@ import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse @@ -93,7 +94,7 @@ open class EthereumGrpcUpstream( private val log = LoggerFactory.getLogger(EthereumGrpcUpstream::class.java) private val upstreamStatus = GrpcUpstreamStatus() - private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock) + private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice()) private var capabilities: Set = emptySet() private val defaultReader: Reader = client.forSelector(Selector.empty) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt new file mode 100644 index 00000000..ffa5ae06 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2020 EmeraldPay, Inc + * 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.upstream.grpc + +import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.api.proto.ReactorBlockchainGrpc +import io.emeraldpay.dshackle.Defaults +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.startup.QuorumForLabels +import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.etherjar.domain.BlockHash +import io.emeraldpay.etherjar.rpc.RpcException +import io.emeraldpay.grpc.Chain +import org.reactivestreams.Publisher +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.publisher.Mono +import java.math.BigInteger +import java.time.Instant +import java.util.Locale +import java.util.concurrent.TimeoutException +import java.util.function.Function + +open class EthereumPosGrpcUpstream( + private val parentId: String, + role: UpstreamsConfig.UpstreamRole, + private val chain: Chain, + private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, + private val client: JsonRpcGrpcClient +) : DefaultUpstream( + "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", + UpstreamsConfig.Options.getDefaults(), + role, + null, null +), + GrpcUpstream, + Lifecycle { + + private val blockConverter: Function = Function { value -> + val block = BlockContainer( + value.height, + BlockId.from(BlockHash.from("0x" + value.blockId)), + BigInteger(1, value.weight.toByteArray()), + Instant.ofEpochMilli(value.timestamp), + false, + null, + null + ) + block + } + + private val reloadBlock: Function> = Function { existingBlock -> + // head comes without transaction data + // need to download transactions for the block + defaultReader.read(JsonRpcRequest("eth_getBlockByHash", listOf(existingBlock.hash.toHexWithPrefix(), false))) + .flatMap(JsonRpcResponse::requireResult) + .map { + BlockContainer.fromEthereumJson(it) + } + .timeout(timeout, Mono.error(TimeoutException("Timeout from upstream"))) + .doOnError { t -> + setStatus(UpstreamAvailability.UNAVAILABLE) + val msg = "Failed to download block data for chain $chain on $parentId" + if (t is RpcException || t is TimeoutException) { + log.warn("$msg. Message: ${t.message}") + } else { + log.error(msg, t) + } + } + } + + private val log = LoggerFactory.getLogger(EthereumGrpcUpstream::class.java) + private val upstreamStatus = GrpcUpstreamStatus() + private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice()) + private var capabilities: Set = emptySet() + + private val defaultReader: Reader = client.forSelector(Selector.empty) + var timeout = Defaults.timeout + + override fun start() { + } + + override fun isRunning(): Boolean { + return true + } + + override fun stop() { + } + + override fun update(conf: BlockchainOuterClass.DescribeChain) { + upstreamStatus.update(conf) + capabilities = RemoteCapabilities.extract(conf) + conf.status?.let { status -> onStatus(status) } + } + + override fun getQuorumByLabel(): QuorumForLabels { + return upstreamStatus.getNodes() + } + + // ------------------------------------------------------------------------------------------ + + override fun getLabels(): Collection { + return upstreamStatus.getLabels() + } + + override fun getMethods(): CallMethods { + return upstreamStatus.getCallMethods() + } + + override fun isAvailable(): Boolean { + return super.isAvailable() && grpcHead.getCurrent() != null && getQuorumByLabel().getAll().any { + it.quorum > 0 + } + } + + override fun getHead(): Head { + return grpcHead + } + + override fun getApi(): Reader { + return defaultReader + } + + @Suppress("UNCHECKED_CAST") + override fun cast(selfType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return this as T + } + + override fun getCapabilities(): Set { + return capabilities + } + + override fun isGrpc(): Boolean { + return true + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcHead.kt index 2e7e8bba..d8434fbe 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcHead.kt @@ -23,6 +23,7 @@ import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.upstream.AbstractHead import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.grpc.Chain import org.reactivestreams.Publisher import org.slf4j.LoggerFactory @@ -45,8 +46,9 @@ class GrpcHead( /** * Populate block data with all missing details, of any */ - private val enhancer: Function>? -) : AbstractHead(), Lifecycle { + private val enhancer: Function>?, + private val forkChoice: ForkChoice +) : AbstractHead(forkChoice), Lifecycle { companion object { private val log = LoggerFactory.getLogger(GrpcHead::class.java) @@ -94,10 +96,7 @@ class GrpcHead( var blocks = source.map(converter) .distinctUntilChanged { it.hash - }.filter { block -> - val curr = this.getCurrent() - curr == null || curr.difficulty < block.difficulty - } + }.filter { forkChoice.filter(it) } if (enhancer != null) { blocks = blocks.flatMap(enhancer) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashAddingSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashAddingSpec.groovy index 541a5e61..cdb5c8bd 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashAddingSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashAddingSpec.groovy @@ -27,7 +27,7 @@ class HeightByHashAddingSpec extends Specification { def block = new BlockContainer( 12079192L, BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32"), - BigInteger.ONE, Instant.now(), false, "".bytes, null, [] + BigInteger.ONE, Instant.now(), false, "".bytes, null, [], 0 ) def "use memory if available"() { diff --git a/src/test/groovy/io/emeraldpay/dshackle/cache/ReceiptMemCacheSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/cache/ReceiptMemCacheSpec.groovy index 01c88ba7..d950a765 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/cache/ReceiptMemCacheSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/cache/ReceiptMemCacheSpec.groovy @@ -86,7 +86,8 @@ class ReceiptMemCacheSpec extends Specification { false, "{}".bytes, null, - [TxId.from(receipt.transactionHash)] + [TxId.from(receipt.transactionHash)], + 0 ) when: diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinAddressSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinAddressSpec.groovy index 59a9008d..143febc5 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinAddressSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinAddressSpec.groovy @@ -271,7 +271,7 @@ class TrackBitcoinAddressSpec extends Specification { Head head = Mock(Head) { 1 * getFlux() >> Flux.concat( Flux.just( - new BlockContainer(0L, BlockId.from(hash1), BigInteger.ZERO, Instant.now(), false, null, null, []) + new BlockContainer(0L, BlockId.from(hash1), BigInteger.ZERO, Instant.now(), false, null, null, [], 0) ), blocks.asFlux() ) @@ -312,7 +312,7 @@ class TrackBitcoinAddressSpec extends Specification { StepVerifier.create(resp) .expectNext("0") .then { - blocks.tryEmitNext(new BlockContainer(1L, BlockId.from(hash1), BigInteger.ONE, Instant.now(), false, null, null, [])) + blocks.tryEmitNext(new BlockContainer(1L, BlockId.from(hash1), BigInteger.ONE, Instant.now(), false, null, null, [], 0)) } .expectNext("1230000") .then { diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinTxSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinTxSpec.groovy index 4a04dee5..b1f36eba 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinTxSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinTxSpec.groovy @@ -142,7 +142,7 @@ class TrackBitcoinTxSpec extends Specification { def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" // start with the current block def next = Flux.fromIterable([10, 12, 13, 14, 15]).map { h -> - new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, []) + new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, [], 0) } Head head = Mock(Head) { 1 * getFlux() >> next @@ -173,7 +173,7 @@ class TrackBitcoinTxSpec extends Specification { def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" // start with the current block def next = Flux.fromIterable([10, 12, 13]).map { h -> - new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, []) + new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, [], 0) } Head head = Mock(Head) { 1 * getFlux() >> next @@ -268,7 +268,7 @@ class TrackBitcoinTxSpec extends Specification { ]) } def next = Flux.fromIterable([10, 11, 12]).map { h -> - new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, []) + new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, [], 0) } Head head = Mock(Head) { _ * getFlux() >> next diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy index d371d4a4..71fedb56 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy @@ -198,7 +198,7 @@ class TrackEthereumTxSpec extends Specification { def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM, Instant.now(), TransactionId.from(txId), 6) def block = new BlockContainer( 100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes, null, - [TxId.from(txId)] + [TxId.from(txId)], 0 ) when: @@ -220,7 +220,8 @@ class TrackEthereumTxSpec extends Specification { def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM, Instant.now(), TransactionId.from(txId), 6) def block = new BlockContainer( 100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes, null, - [TxId.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")] + [TxId.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")], + 0 ) apiMock.answer("eth_getTransactionByHash", [txId], null) diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy index a9203234..795995db 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy @@ -111,7 +111,8 @@ class TestingCommons { false, null, null, - [] + [], + 0 ) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy index 439b811d..48bc614b 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/AbstractHeadSpec.groovy @@ -17,6 +17,9 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice +import org.jetbrains.annotations.NotNull import reactor.core.publisher.Flux import reactor.core.publisher.Sinks import reactor.test.StepVerifier @@ -31,7 +34,7 @@ class AbstractHeadSpec extends Specification { def blocks = [1L, 2, 3, 4].collect { i -> byte[] hash = new byte[32] hash[0] = i as byte - new BlockContainer(i, BlockId.from(hash), BigInteger.valueOf(i), Instant.now(), false, null, null, []) + new BlockContainer(i, BlockId.from(hash), BigInteger.valueOf(i), Instant.now(), false, null, null, [], 0) } def "Calls beforeBlock on each block"() { @@ -85,7 +88,7 @@ class AbstractHeadSpec extends Specification { .verify(Duration.ofSeconds(1)) } - def "Ignores block will less difficulty"() { + def "Ignores block that is filtered by forkchoice"() { setup: Sinks.Many source = Sinks.many().unicast().onBackpressureBuffer() def head = new TestHead() @@ -93,7 +96,7 @@ class AbstractHeadSpec extends Specification { blocks[1].height, BlockId.from(blocks[1].hash.value.clone().tap { it[1] = 0xff as byte }), blocks[1].difficulty - 1, Instant.now(), - false, null, null, [] + false, null, null, [], 0 ) when: head.follow(source.asFlux()) @@ -113,6 +116,23 @@ class AbstractHeadSpec extends Specification { } class TestHead extends AbstractHead { + TestHead() { + super(new ForkChoice() { + @Override + boolean filter(@NotNull BlockContainer block) { + return block.hash != BlockId.from("02ff000000000000000000000000000000000000000000000000000000000000") + } + @Override + ForkChoice.ChoiceResult choose(@NotNull BlockContainer block) { + return new ForkChoice.ChoiceResult.Updated(block) + } + + @Override + BlockContainer getHead() { + return null + } + }) + } } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/DistanceExtractorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/DistanceExtractorSpec.groovy new file mode 100644 index 00000000..0fe56a8a --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/DistanceExtractorSpec.groovy @@ -0,0 +1,74 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.etherjar.domain.BlockHash +import io.emeraldpay.etherjar.rpc.json.BlockJson +import spock.lang.Specification + +import java.time.Instant + +class DistanceExtractorSpec extends Specification { + def "Correct distance for PoW"() { + expect: + def top = new BlockJson().with { + it.number = topHeight + it.totalDifficulty = topDiff + it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915123") + it.timestamp = Instant.now() + return it + } + def curr = new BlockJson().with { + it.number = currHeight + it.totalDifficulty = currDiff + it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915123") + it.timestamp = Instant.now() + return it + } + delta as DistanceExtractor.ChainDistance == DistanceExtractor.@Companion.extractPowDistance(BlockContainer.from(top), BlockContainer.from(curr)) + where: + topHeight | topDiff | currHeight | currDiff | delta + 100 | 1000 | 100 | 1000 | new DistanceExtractor.ChainDistance.Distance(0) + 101 | 1010 | 100 | 1000 | new DistanceExtractor.ChainDistance.Distance(1) + 102 | 1020 | 100 | 1000 | new DistanceExtractor.ChainDistance.Distance(2) + 103 | 1030 | 100 | 1000 | new DistanceExtractor.ChainDistance.Distance(3) + 150 | 1500 | 100 | 1000 | new DistanceExtractor.ChainDistance.Distance(50) + + 100 | 1000 | 101 | 1010 | new DistanceExtractor.ChainDistance.Distance(0) + 100 | 1000 | 102 | 1020 | new DistanceExtractor.ChainDistance.Distance(0) + 100 | 1000 | 100 | 1010 | DistanceExtractor.ChainDistance.Fork.INSTANCE + 100 | 1100 | 100 | 1000 | DistanceExtractor.ChainDistance.Fork.INSTANCE + } + + def "Correct distance for priority"() { + setup: + def hash1 = "0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915123" + def hash2 = "0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915124" + expect: + def top = new BlockJson().with { + it.number = topHeight + it.totalDifficulty = 0 + it.hash = BlockHash.from(hashA == 0 ? hash1 : hash2) + it.timestamp = Instant.now() + return it + } + def curr = new BlockJson().with { + it.number = currHeight + it.totalDifficulty = 0 + it.hash = BlockHash.from(hashB == 0 ? hash1 : hash2) + it.timestamp = Instant.now() + return it + } + delta as DistanceExtractor.ChainDistance == DistanceExtractor.@Companion.extractPriorityDistance(BlockContainer.from(top), BlockContainer.from(curr)) + where: + topHeight | hashA | currHeight | hashB || delta + 100 | 0 | 100 | 0 || new DistanceExtractor.ChainDistance.Distance(0) + 101 | 0 | 100 | 1 || new DistanceExtractor.ChainDistance.Distance(1) + 102 | 0 | 100 | 1 || new DistanceExtractor.ChainDistance.Distance(2) + 103 | 0 | 100 | 1 || new DistanceExtractor.ChainDistance.Distance(3) + 150 | 0 | 100 | 1 || new DistanceExtractor.ChainDistance.Distance(50) + + 100 | 0 | 101 | 1 || DistanceExtractor.ChainDistance.Fork.INSTANCE + 100 | 0 | 102 | 1 || DistanceExtractor.ChainDistance.Fork.INSTANCE + 100 | 0 | 100 | 1 || DistanceExtractor.ChainDistance.Fork.INSTANCE + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy index eefdf46a..371fb461 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy @@ -23,6 +23,7 @@ import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.grpc.Chain import reactor.test.StepVerifier import spock.lang.Retry @@ -47,7 +48,7 @@ class FilteredApisSpec extends Specification { def httpFactory = Mock(HttpFactory) { create(_, _) >> TestingCommons.api().tap { it.id = "${i++}" } } - def connectorFactory = new EthereumConnectorFactory(false, null, httpFactory) + def connectorFactory = new EthereumConnectorFactory(false, null, httpFactory, new MostWorkForkChoice()) new EthereumUpstream( "test", Chain.ETHEREUM, diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/HeadLagObserverSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/HeadLagObserverSpec.groovy index fa511ed1..0a235796 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/HeadLagObserverSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/HeadLagObserverSpec.groovy @@ -111,45 +111,10 @@ class HeadLagObserverSpec extends Specification { .verifyComplete() } - def "Correct distance"() { - setup: - Head master = Mock() - HeadLagObserver observer = new TestHeadLagObserver(master, []) - expect: - def top = new BlockJson().with { - it.number = topHeight - it.totalDifficulty = topDiff - it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915123") - it.timestamp = Instant.now() - return it - } - def curr = new BlockJson().with { - it.number = currHeight - it.totalDifficulty = currDiff - it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915123") - it.timestamp = Instant.now() - return it - } - delta as Long == observer.extractDistance(BlockContainer.from(top), BlockContainer.from(curr)) - where: - topHeight | topDiff | currHeight | currDiff | delta - 100 | 1000 | 100 | 1000 | 0 - 101 | 1010 | 100 | 1000 | 1 - 102 | 1020 | 100 | 1000 | 2 - 103 | 1030 | 100 | 1000 | 3 - 150 | 1500 | 100 | 1000 | 50 - - 100 | 1000 | 101 | 1010 | 0 - 100 | 1000 | 102 | 1020 | 0 - 100 | 1000 | 100 | 1010 | 11 - 100 | 1100 | 100 | 1000 | 11 - - } - class TestHeadLagObserver extends HeadLagObserver { TestHeadLagObserver(@NotNull Head master, @NotNull Collection followers) { - super(master, followers) + super(master, followers, DistanceExtractor.@Companion::extractPowDistance) } @Override diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/MergedHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/MergedHeadSpec.groovy index 217f2f74..a5b747ef 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/MergedHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/MergedHeadSpec.groovy @@ -15,6 +15,7 @@ */ package io.emeraldpay.dshackle.upstream +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import org.springframework.context.Lifecycle import reactor.core.publisher.Flux import spock.lang.Specification @@ -36,7 +37,7 @@ class MergedHeadSpec extends Specification { } when: - def merged = new MergedHead([head1, head2, head3]) + def merged = new MergedHead([head1, head2, head3], new MostWorkForkChoice()) merged.start() then: @@ -44,11 +45,17 @@ class MergedHeadSpec extends Specification { } class TestHead1 extends AbstractHead { - + TestHead1() { + super(new MostWorkForkChoice()) + } } class TestHead2 extends AbstractHead implements Lifecycle { + TestHead2() { + super(new MostWorkForkChoice()) + } + @Override void start() { diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHeadSpec.groovy index 943edf8c..94fe5fde 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHeadSpec.groovy @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.test.TestingCommons +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.etherjar.domain.BlockHash import io.emeraldpay.etherjar.rpc.json.BlockJson import reactor.core.publisher.Flux @@ -30,7 +31,7 @@ import java.time.Instant class DefaultEthereumHeadSpec extends Specification { - DefaultEthereumHead head = new DefaultEthereumHead() + DefaultEthereumHead head = new DefaultEthereumHead(new MostWorkForkChoice()) ObjectMapper objectMapper = Global.objectMapper def blocks = (10L..20L).collect { i -> diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoiceSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoiceSpec.groovy new file mode 100644 index 00000000..dac1ec85 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoiceSpec.groovy @@ -0,0 +1,37 @@ +package io.emeraldpay.dshackle.upstream.forkchoice + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import spock.lang.Specification + +import java.time.Instant + +class MostWorkForkChoiceSpec extends Specification { + + def blocks = [1L, 2, 3, 4].collect { i -> + byte[] hash = new byte[32] + hash[0] = i as byte + new BlockContainer(i, BlockId.from(hash), BigInteger.valueOf(i), Instant.now(), false, null, null, [], 0) + } + + def "filters blocks"() { + def choice = new MostWorkForkChoice() + choice.choose(blocks[1]) + expect: + !choice.filter(blocks[0]) + choice.filter(blocks[2]) + } + + def "chooses correct block as head"() { + def choice = new MostWorkForkChoice() + choice.choose(blocks[1]) + when: + choice.choose(blocks[0]) + then: + choice.getHead() == blocks[1] + when: + choice.choose(blocks[2]) + then: + choice.getHead() == blocks[2] + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoiceSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoiceSpec.groovy new file mode 100644 index 00000000..20f90dae --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoiceSpec.groovy @@ -0,0 +1,51 @@ +package io.emeraldpay.dshackle.upstream.forkchoice + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import spock.lang.Specification + +import java.time.Instant + +class NoChoiceWithPriorityForkChoiceSpec extends Specification { + def blocks = [1L, 2, 3, 4].collect { i -> + byte[] hash = new byte[32] + hash[0] = i as byte + new BlockContainer(i, BlockId.from(hash), BigInteger.valueOf(i), Instant.now(), false, null, null, [], 0) + } + + def "filters blocks"() { + def blockR0 = blocks[0].copyWithRating(10) + def blockR1 = blocks[1].copyWithRating(10) + def choice = new NoChoiceWithPriorityForkChoice(10) + when: + choice.choose(blocks[0]) + then: + choice.getHead() == blockR0 + when: + choice.choose(blocks[1]) + then: + choice.getHead() == blockR1 + when: + choice.choose(blocks[0]) + then: + choice.getHead() == blocks[1] + } + + def "chooses blocks and adds rating"() { + def blockR0 = blocks[0].copyWithRating(10) + def blockR1 = blocks[1].copyWithRating(10) + def choice = new NoChoiceWithPriorityForkChoice(10) + when: + choice.choose(blocks[0]) + then: + choice.getHead() == blockR0 + when: + choice.choose(blocks[1]) + then: + choice.getHead() == blockR1 + when: + choice.choose(blocks[0]) + then: + choice.getHead() == blockR1 + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoiceSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoiceSpec.groovy new file mode 100644 index 00000000..25848281 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoiceSpec.groovy @@ -0,0 +1,41 @@ +package io.emeraldpay.dshackle.upstream.forkchoice + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import spock.lang.Specification + +import java.time.Instant + +class PriorityForkChoiceSpec extends Specification { + def blocks = [1L, 2, 3, 4].collect { i -> + byte[] hash = new byte[32] + hash[0] = i as byte + new BlockContainer(i, BlockId.from(hash), BigInteger.valueOf(i), Instant.now(), false, null, null, [], i.toInteger()) + } + def "filters blocks"() { + def choice = new PriorityForkChoice() + choice.choose(blocks[1]) + expect: + !choice.filter(blocks[0]) + choice.filter(blocks[2]) + !choice.filter(blocks[1]) + } + + def "chooses correct block according to node rating"() { + def choice = new PriorityForkChoice() + choice.choose(blocks[1]) + when: + choice.choose(blocks[0]) + then: + choice.getHead() == blocks[1] + when: + choice.choose(blocks[2]) + then: + choice.getHead() == blocks[2] + when: + def seenblock = blocks[1].copyWithRating(20) + choice.choose(seenblock) + then: + choice.getHead() == blocks[2] + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/GrpcHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/GrpcHeadSpec.groovy index 073c26d5..0f8a1e49 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/GrpcHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/GrpcHeadSpec.groovy @@ -21,6 +21,7 @@ import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.test.MockGrpcServer import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.grpc.Chain import io.grpc.stub.StreamObserver import reactor.test.StepVerifier @@ -60,7 +61,7 @@ class GrpcHeadSpec extends Specification { Chain.BITCOIN, Stub(DefaultUpstream), client, - convert, null + convert, null, new MostWorkForkChoice() ) when: def act = head.getFlux() @@ -121,7 +122,7 @@ class GrpcHeadSpec extends Specification { Chain.BITCOIN, Stub(DefaultUpstream), client, - convert, null + convert, null, new MostWorkForkChoice() ) when: def act = head.getFlux() From 1df7221c68b5b30515ea09bf99fcf51f7096becf Mon Sep 17 00:00:00 2001 From: terminal Date: Fri, 29 Jul 2022 17:42:19 +0400 Subject: [PATCH 03/11] add grpc support for PoS Ethereum --- .../dshackle/config/UpstreamsConfig.kt | 1 + .../dshackle/config/UpstreamsConfigReader.kt | 3 + .../dshackle/startup/ConfiguredUpstreams.kt | 13 +-- .../upstream/CurrentMultistreamHolder.kt | 5 +- .../upstream/ethereum/ERC20Balance.kt | 4 +- .../upstream/ethereum/EthereumRpcUpstream.kt | 98 +++++++++++++++++++ .../upstream/ethereum/EthereumUpstream.kt | 79 ++------------- .../ethereum_pos/EthereumPosMultiStream.kt | 1 - .../ethereum_pos/EthereumPosRpcUpstream.kt | 98 +++++++++++++++++++ .../ethereum_pos/EthereumPosUpstream.kt | 79 ++------------- .../upstream/grpc/EthereumGrpcUpstream.kt | 2 +- .../upstream/grpc/EthereumPosGrpcUpstream.kt | 13 +-- .../dshackle/upstream/grpc/GrpcUpstreams.kt | 21 +++- .../config/UpstreamsConfigReaderSpec.groovy | 3 - .../dshackle/rpc/StreamHeadSpec.groovy | 8 +- .../dshackle/rpc/TrackERC20AddressSpec.groovy | 8 -- ....groovy => EthereumRpcUpstreamMock.groovy} | 12 +-- .../test/MultistreamHolderMock.groovy | 12 +-- .../dshackle/test/TestingCommons.groovy | 26 ++--- .../CurrentMultistreamHolderSpec.groovy | 20 ++-- .../dshackle/upstream/FilteredApisSpec.groovy | 6 +- .../dshackle/upstream/MultistreamSpec.groovy | 6 +- .../upstream/ethereum/ERC20BalanceSpec.groovy | 6 +- .../ethereum/EthereumReaderSpec.groovy | 8 +- 24 files changed, 302 insertions(+), 230 deletions(-) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt rename src/test/groovy/io/emeraldpay/dshackle/test/{EthereumUpstreamMock.groovy => EthereumRpcUpstreamMock.groovy} (80%) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index 662105b0..f8cf5dd7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -102,6 +102,7 @@ open class UpstreamsConfig { var host: String? = null var port: Int = 0 var auth: AuthConfig.ClientTlsAuth? = null + var nodeRating: Int = 0 } class EthereumConnection : RpcConnection() { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index f280ec64..9ab5be40 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -106,6 +106,9 @@ class UpstreamsConfigReader( config.upstreams.add(upstream) val connection = UpstreamsConfig.GrpcConnection() upstream.connection = connection + getValueAsInt(connConfigNode, "node-rating")?.let { + connection.nodeRating = it + } getValueAsString(connConfigNode, "host")?.let { connection.host = it } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index 10e320ff..0eba3a3b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -29,8 +29,8 @@ import io.emeraldpay.dshackle.upstream.bitcoin.ExtractBlock import io.emeraldpay.dshackle.upstream.bitcoin.ZMQServer import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosUpstream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosRpcUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice @@ -160,7 +160,7 @@ open class ConfiguredUpstreams( if (connectorFactory == null) { return null } - val upstream = EthereumPosUpstream( + val upstream = EthereumPosRpcUpstream( config.id!!, chain, options, config.role, @@ -218,7 +218,7 @@ open class ConfiguredUpstreams( config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options - ) : EthereumUpstream? { + ) : EthereumRpcUpstream? { val conn = config.connection!! val urls = ArrayList() @@ -228,7 +228,7 @@ open class ConfiguredUpstreams( if (connectorFactory == null) { return null } - val upstream = EthereumUpstream( + val upstream = EthereumRpcUpstream( config.id!!, chain, options, config.role, @@ -251,7 +251,8 @@ open class ConfiguredUpstreams( endpoint.host!!, endpoint.port, endpoint.auth, - fileResolver + fileResolver, + endpoint.nodeRating ).apply { timeout = options.timeout } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt index 4de9bbef..d18ff731 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt @@ -24,10 +24,7 @@ import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosUpstream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.* import io.emeraldpay.grpc.BlockchainType import io.emeraldpay.grpc.Chain import org.slf4j.LoggerFactory diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/ERC20Balance.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/ERC20Balance.kt index c5250488..05d99b8d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/ERC20Balance.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/ERC20Balance.kt @@ -49,7 +49,7 @@ open class ERC20Balance { apis.request(1) return Flux.from(apis) .flatMap { - getBalance(it.cast(EthereumUpstream::class.java), token, address) + getBalance(it.cast(EthereumRpcUpstream::class.java), token, address) } .doOnNext { apis.resolve() @@ -57,7 +57,7 @@ open class ERC20Balance { .next() } - open fun getBalance(upstream: EthereumUpstream, token: ERC20Token, address: Address): Mono { + open fun getBalance(upstream: EthereumRpcUpstream, token: ERC20Token, address: Address): Mono { return upstream .getApi() .read(prepareEthCall(token, address, upstream.getHead())) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt new file mode 100644 index 00000000..e4b104cd --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2020 EmeraldPay, Inc + * 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.upstream.ethereum + +import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.cache.CachesEnabled +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.startup.QuorumForLabels +import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.Disposable + +open class EthereumRpcUpstream( + id: String, + val chain: Chain, + options: UpstreamsConfig.Options, + role: UpstreamsConfig.UpstreamRole, + targets: CallMethods?, + private val node: QuorumForLabels.QuorumItem?, + connectorFactory: ConnectorFactory +) : EthereumUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { + private val log = LoggerFactory.getLogger(EthereumRpcUpstream::class.java) + private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) + private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) + + private var validatorSubscription: Disposable? = null + + override fun setCaches(caches: Caches) { + if (connector is CachesEnabled) { + connector.setCaches(caches) + } + } + + override fun start() { + log.info("Configured for ${chain.chainName}") + connector.start() + if (getOptions().disableValidation != null && getOptions().disableValidation!!) { + log.warn("Disable validation for upstream ${this.getId()}") + this.setLag(0) + this.setStatus(UpstreamAvailability.OK) + } else { + log.debug("Start validation for upstream ${this.getId()}") + validatorSubscription = validator.start() + .subscribe(this::setStatus) + } + } + override fun getHead(): Head { + return connector.getHead() + } + + override fun stop() { + validatorSubscription?.dispose() + validatorSubscription = null + connector.stop() + } + + override fun isRunning(): Boolean { + return connector.isRunning + } + + override fun getApi(): Reader { + return connector.getApi() + } + + override fun isGrpc(): Boolean { + return false + } + + @Suppress("UNCHECKED_CAST") + override fun cast(selfType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return this as T + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt index ef226380..0b50610e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt @@ -16,78 +16,19 @@ */ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.cache.Caches -import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.Capability +import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.calls.CallMethods -import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory -import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector -import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import io.emeraldpay.grpc.Chain -import org.slf4j.LoggerFactory -import org.springframework.context.Lifecycle -import reactor.core.Disposable -open class EthereumUpstream( +abstract class EthereumUpstream( id: String, - val chain: Chain, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, - private val node: QuorumForLabels.QuorumItem?, - connectorFactory: ConnectorFactory -) : DefaultUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { - private val log = LoggerFactory.getLogger(EthereumUpstream::class.java) - private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) - private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) - - private var validatorSubscription: Disposable? = null - - override fun setCaches(caches: Caches) { - if (connector is CachesEnabled) { - connector.setCaches(caches) - } - } - - override fun start() { - log.info("Configured for ${chain.chainName}") - connector.start() - if (getOptions().disableValidation != null && getOptions().disableValidation!!) { - log.warn("Disable validation for upstream ${this.getId()}") - this.setLag(0) - this.setStatus(UpstreamAvailability.OK) - } else { - log.debug("Start validation for upstream ${this.getId()}") - validatorSubscription = validator.start() - .subscribe(this::setStatus) - } - } - override fun getHead(): Head { - return connector.getHead() - } - - override fun stop() { - validatorSubscription?.dispose() - validatorSubscription = null - connector.stop() - } - - override fun isRunning(): Boolean { - return connector.isRunning - } - - override fun getApi(): Reader { - return connector.getApi() - } - - override fun isGrpc(): Boolean { - return false - } + private val node: QuorumForLabels.QuorumItem? +) : DefaultUpstream(id, options, role, targets, node) { private val capabilities = if (options.providesBalance != false) { setOf(Capability.RPC, Capability.BALANCE) @@ -102,12 +43,4 @@ open class EthereumUpstream( override fun getLabels(): Collection { return node?.let { listOf(it.labels) } ?: emptyList() } - - @Suppress("UNCHECKED_CAST") - override fun cast(selfType: Class): T { - if (!selfType.isAssignableFrom(this.javaClass)) { - throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") - } - return this as T - } -} +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt index aab4ba37..e81b28e2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt @@ -25,7 +25,6 @@ import io.emeraldpay.dshackle.upstream.MergedHead import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.forkchoice.PriorityForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt new file mode 100644 index 00000000..7838c08e --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2020 EmeraldPay, Inc + * 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.upstream.ethereum + +import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.cache.CachesEnabled +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.startup.QuorumForLabels +import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory +import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.Disposable + +open class EthereumPosRpcUpstream( + id: String, + val chain: Chain, + options: UpstreamsConfig.Options, + role: UpstreamsConfig.UpstreamRole, + targets: CallMethods?, + private val node: QuorumForLabels.QuorumItem?, + connectorFactory: ConnectorFactory +) : EthereumPosUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { + private val log = LoggerFactory.getLogger(EthereumPosRpcUpstream::class.java) + private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) + private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) + + private var validatorSubscription: Disposable? = null + + override fun setCaches(caches: Caches) { + if (connector is CachesEnabled) { + connector.setCaches(caches) + } + } + + override fun start() { + log.info("Configured for ${chain.chainName}") + connector.start() + if (getOptions().disableValidation != null && getOptions().disableValidation!!) { + log.warn("Disable validation for upstream ${this.getId()}") + this.setLag(0) + this.setStatus(UpstreamAvailability.OK) + } else { + log.debug("Start validation for upstream ${this.getId()}") + validatorSubscription = validator.start() + .subscribe(this::setStatus) + } + } + override fun getHead(): Head { + return connector.getHead() + } + + override fun stop() { + validatorSubscription?.dispose() + validatorSubscription = null + connector.stop() + } + + override fun isRunning(): Boolean { + return connector.isRunning + } + + override fun getApi(): Reader { + return connector.getApi() + } + + override fun isGrpc(): Boolean { + return false + } + + @Suppress("UNCHECKED_CAST") + override fun cast(selfType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return this as T + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt index 1a75cf21..f98aa936 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt @@ -16,78 +16,19 @@ */ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.cache.Caches -import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.Capability +import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.calls.CallMethods -import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory -import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector -import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import io.emeraldpay.grpc.Chain -import org.slf4j.LoggerFactory -import org.springframework.context.Lifecycle -import reactor.core.Disposable -open class EthereumPosUpstream( +abstract class EthereumPosUpstream( id: String, - val chain: Chain, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, - private val node: QuorumForLabels.QuorumItem?, - connectorFactory: ConnectorFactory -) : DefaultUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { - private val log = LoggerFactory.getLogger(EthereumPosUpstream::class.java) - private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) - private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) - - private var validatorSubscription: Disposable? = null - - override fun setCaches(caches: Caches) { - if (connector is CachesEnabled) { - connector.setCaches(caches) - } - } - - override fun start() { - log.info("Configured for ${chain.chainName}") - connector.start() - if (getOptions().disableValidation != null && getOptions().disableValidation!!) { - log.warn("Disable validation for upstream ${this.getId()}") - this.setLag(0) - this.setStatus(UpstreamAvailability.OK) - } else { - log.debug("Start validation for upstream ${this.getId()}") - validatorSubscription = validator.start() - .subscribe(this::setStatus) - } - } - override fun getHead(): Head { - return connector.getHead() - } - - override fun stop() { - validatorSubscription?.dispose() - validatorSubscription = null - connector.stop() - } - - override fun isRunning(): Boolean { - return connector.isRunning - } - - override fun getApi(): Reader { - return connector.getApi() - } - - override fun isGrpc(): Boolean { - return false - } + private val node: QuorumForLabels.QuorumItem? +) : DefaultUpstream(id, options, role, targets, node) { private val capabilities = if (options.providesBalance != false) { setOf(Capability.RPC, Capability.BALANCE) @@ -102,12 +43,4 @@ open class EthereumPosUpstream( override fun getLabels(): Collection { return node?.let { listOf(it.labels) } ?: emptyList() } - - @Suppress("UNCHECKED_CAST") - override fun cast(selfType: Class): T { - if (!selfType.isAssignableFrom(this.javaClass)) { - throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") - } - return this as T - } -} +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt index b7824b01..0d594594 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt @@ -50,7 +50,7 @@ open class EthereumGrpcUpstream( private val chain: Chain, private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, private val client: JsonRpcGrpcClient -) : DefaultUpstream( +) : EthereumUpstream( "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", UpstreamsConfig.Options.getDefaults(), role, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt index ffa5ae06..3735c98a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt @@ -26,8 +26,8 @@ import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.CallMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream -import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice +import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosUpstream +import io.emeraldpay.dshackle.upstream.forkchoice.NoChoiceWithPriorityForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse @@ -48,9 +48,10 @@ open class EthereumPosGrpcUpstream( private val parentId: String, role: UpstreamsConfig.UpstreamRole, private val chain: Chain, - private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, - private val client: JsonRpcGrpcClient -) : DefaultUpstream( + remote: ReactorBlockchainGrpc.ReactorBlockchainStub, + client: JsonRpcGrpcClient, + nodeRating: Int +) : EthereumPosUpstream( "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", UpstreamsConfig.Options.getDefaults(), role, @@ -94,7 +95,7 @@ open class EthereumPosGrpcUpstream( private val log = LoggerFactory.getLogger(EthereumGrpcUpstream::class.java) private val upstreamStatus = GrpcUpstreamStatus() - private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice()) + private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock, NoChoiceWithPriorityForkChoice(nodeRating)) private var capabilities: Set = emptySet() private val defaultReader: Reader = client.forSelector(Selector.empty) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt index c55fcb5a..20fe2a64 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt @@ -56,7 +56,8 @@ class GrpcUpstreams( private val host: String, private val port: Int, private val auth: AuthConfig.ClientTlsAuth? = null, - private val fileResolver: FileResolver + private val fileResolver: FileResolver, + private val nodeRating: Int ) { private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java) @@ -192,6 +193,8 @@ class GrpcUpstreams( return getOrCreateEthereum(chain, metrics) } else if (blockchainType == BlockchainType.BITCOIN) { return getOrCreateBitcoin(chain, metrics) + } else if (blockchainType == BlockchainType.ETHEREUM_POS) { + return getOrCreateEthereumPos(chain, metrics) } else { throw IllegalArgumentException("Unsupported blockchain: $chain") } @@ -213,6 +216,22 @@ class GrpcUpstreams( } } + fun getOrCreateEthereumPos(chain: Chain, metrics: RpcMetrics): UpstreamChange { + lock.withLock { + val current = known[chain] + return if (current == null) { + val rpcClient = JsonRpcGrpcClient(client!!, chain, metrics) + val created = EthereumPosGrpcUpstream(id, role, chain, client!!, rpcClient, nodeRating) + created.timeout = this.timeout + known[chain] = created + created.start() + UpstreamChange(chain, created, UpstreamChange.ChangeType.ADDED) + } else { + UpstreamChange(chain, current, UpstreamChange.ChangeType.REVALIDATED) + } + } + } + fun getOrCreateBitcoin(chain: Chain, metrics: RpcMetrics): UpstreamChange { lock.withLock { val current = known[chain] diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy index d69c22b6..2244dddc 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy @@ -17,9 +17,6 @@ package io.emeraldpay.dshackle.config import io.emeraldpay.dshackle.test.TestingCommons -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream -import io.emeraldpay.grpc.Chain -import io.emeraldpay.etherjar.rpc.RpcClient import spock.lang.Specification class UpstreamsConfigReaderSpec extends Specification { diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy index 068d4240..160becb9 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/StreamHeadSpec.groovy @@ -22,10 +22,10 @@ import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.test.EthereumUpstreamMock +import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.test.MultistreamHolderMock -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.grpc.Chain import io.emeraldpay.etherjar.domain.BlockHash import io.emeraldpay.etherjar.rpc.json.BlockJson @@ -43,7 +43,7 @@ class StreamHeadSpec extends Specification { def "Errors on unavailable chain"() { setup: - def upstreams = new MultistreamHolderMock(Chain.ETHEREUM, Stub(EthereumUpstream)) + def upstreams = new MultistreamHolderMock(Chain.ETHEREUM, Stub(EthereumRpcUpstream)) def streamHead = new StreamHead(upstreams) when: def flux = streamHead.add( @@ -78,7 +78,7 @@ class StreamHeadSpec extends Specification { .build() } - def upstream = new EthereumUpstreamMock(Chain.ETHEREUM, TestingCommons.api()) + def upstream = new EthereumRpcUpstreamMock(Chain.ETHEREUM, TestingCommons.api()) def upstreams = new MultistreamHolderMock(Chain.ETHEREUM, upstream) def streamHead = new StreamHead(upstreams) when: diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackERC20AddressSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackERC20AddressSpec.groovy index 062820ed..de15a93e 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackERC20AddressSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackERC20AddressSpec.groovy @@ -3,18 +3,12 @@ package io.emeraldpay.dshackle.rpc import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.config.TokensConfig -import io.emeraldpay.dshackle.test.EthereumUpstreamMock -import io.emeraldpay.dshackle.test.MultistreamHolderMock -import io.emeraldpay.dshackle.test.ReaderMock import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.ethereum.ERC20Balance import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumSubscribe -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.ConnectLogs import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.etherjar.domain.BlockHash import io.emeraldpay.etherjar.domain.TransactionId import io.emeraldpay.etherjar.hex.Hex32 @@ -22,11 +16,9 @@ import io.emeraldpay.grpc.Chain import io.emeraldpay.etherjar.domain.Address import io.emeraldpay.etherjar.erc20.ERC20Token import io.emeraldpay.etherjar.hex.HexData -import io.emeraldpay.etherjar.rpc.json.TransactionCallJson import reactor.core.publisher.Flux import reactor.core.publisher.Mono import reactor.test.StepVerifier -import spock.lang.Ignore import spock.lang.Specification import java.time.Duration diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumRpcUpstreamMock.groovy similarity index 80% rename from src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy rename to src/test/groovy/io/emeraldpay/dshackle/test/EthereumRpcUpstreamMock.groovy index 43caf2ee..89bfa684 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumRpcUpstreamMock.groovy @@ -25,7 +25,7 @@ import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse @@ -35,7 +35,7 @@ import org.jetbrains.annotations.NotNull import org.reactivestreams.Publisher -class EthereumUpstreamMock extends EthereumUpstream { +class EthereumRpcUpstreamMock extends EthereumRpcUpstream { EthereumHeadMock ethereumHeadMock @@ -47,19 +47,19 @@ class EthereumUpstreamMock extends EthereumUpstream { ]) } - EthereumUpstreamMock(@NotNull Chain chain, @NotNull Reader api) { + EthereumRpcUpstreamMock(@NotNull Chain chain, @NotNull Reader api) { this(chain, api, allMethods()) } - EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api) { + EthereumRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api) { this(id, chain, api, allMethods()) } - EthereumUpstreamMock(@NotNull Chain chain, @NotNull Reader api, CallMethods methods) { + EthereumRpcUpstreamMock(@NotNull Chain chain, @NotNull Reader api, CallMethods methods) { this("test", chain, api, methods) } - EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods) { + EthereumRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods) { super(id, chain, UpstreamsConfig.Options.getDefaults(), UpstreamsConfig.UpstreamRole.PRIMARY, diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy index 994c995c..26275da6 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy @@ -28,7 +28,7 @@ import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumReader -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.grpc.BlockchainType import io.emeraldpay.grpc.Chain import org.jetbrains.annotations.NotNull @@ -48,8 +48,8 @@ class MultistreamHolderMock implements MultistreamHolder { if (BlockchainType.from(chain) == BlockchainType.ETHEREUM) { if (up instanceof EthereumMultistream) { upstreams[chain] = up - } else if (up instanceof EthereumUpstream) { - upstreams[chain] = new EthereumMultistreamMock(chain, [up as EthereumUpstream], Caches.default()) + } else if (up instanceof EthereumRpcUpstream) { + upstreams[chain] = new EthereumMultistreamMock(chain, [up as EthereumRpcUpstream], Caches.default()) } else { throw new IllegalArgumentException("Unsupported upstream type ${up.class}") } @@ -105,15 +105,15 @@ class MultistreamHolderMock implements MultistreamHolder { CallMethods customMethods = null Head customHead = null - EthereumMultistreamMock(@NotNull Chain chain, @NotNull List upstreams, @NotNull Caches caches) { + EthereumMultistreamMock(@NotNull Chain chain, @NotNull List upstreams, @NotNull Caches caches) { super(chain, upstreams, caches) } - EthereumMultistreamMock(@NotNull Chain chain, @NotNull List upstreams) { + EthereumMultistreamMock(@NotNull Chain chain, @NotNull List upstreams) { this(chain, upstreams, Caches.default()) } - EthereumMultistreamMock(@NotNull Chain chain, @NotNull EthereumUpstream upstream) { + EthereumMultistreamMock(@NotNull Chain chain, @NotNull EthereumRpcUpstream upstream) { this(chain, [upstream]) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy index 795995db..5f4662c4 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy @@ -28,7 +28,7 @@ import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.grpc.Chain @@ -46,35 +46,35 @@ class TestingCommons { return new ApiReaderMock() } - static EthereumUpstreamMock upstream() { - return new EthereumUpstreamMock(Chain.ETHEREUM, api()) + static EthereumRpcUpstreamMock upstream() { + return new EthereumRpcUpstreamMock(Chain.ETHEREUM, api()) } - static EthereumUpstreamMock upstream(String id) { - return new EthereumUpstreamMock(id, Chain.ETHEREUM, api()) + static EthereumRpcUpstreamMock upstream(String id) { + return new EthereumRpcUpstreamMock(id, Chain.ETHEREUM, api()) } - static EthereumUpstreamMock upstream(String id, Reader api) { - return new EthereumUpstreamMock(id, Chain.ETHEREUM, api) + static EthereumRpcUpstreamMock upstream(String id, Reader api) { + return new EthereumRpcUpstreamMock(id, Chain.ETHEREUM, api) } - static EthereumUpstreamMock upstream(Reader api) { - return new EthereumUpstreamMock(Chain.ETHEREUM, api) + static EthereumRpcUpstreamMock upstream(Reader api) { + return new EthereumRpcUpstreamMock(Chain.ETHEREUM, api) } - static EthereumUpstreamMock upstream(Reader api, String method) { + static EthereumRpcUpstreamMock upstream(Reader api, String method) { return upstream(api, [method]) } - static EthereumUpstreamMock upstream(Reader api, List methods) { - return new EthereumUpstreamMock(Chain.ETHEREUM, api, new DirectCallMethods(methods)) + static EthereumRpcUpstreamMock upstream(Reader api, List methods) { + return new EthereumRpcUpstreamMock(Chain.ETHEREUM, api, new DirectCallMethods(methods)) } static Multistream multistream(Reader api) { return multistream(upstream(api)) } - static Multistream multistream(EthereumUpstream up) { + static Multistream multistream(EthereumRpcUpstream up) { return new EthereumMultistream(Chain.ETHEREUM, [up], Caches.default()).tap { start() } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolderSpec.groovy index 3fa6ea32..a3b3952b 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolderSpec.groovy @@ -16,7 +16,7 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.startup.UpstreamChange -import io.emeraldpay.dshackle.test.EthereumUpstreamMock +import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.grpc.Chain import spock.lang.Specification @@ -26,7 +26,7 @@ class CurrentMultistreamHolderSpec extends Specification { def "add upstream"() { setup: def current = new CurrentMultistreamHolder(TestingCommons.emptyCaches()) - def up = new EthereumUpstreamMock("test", Chain.ETHEREUM, TestingCommons.api()) + def up = new EthereumRpcUpstreamMock("test", Chain.ETHEREUM, TestingCommons.api()) when: current.update(new UpstreamChange(Chain.ETHEREUM, up, UpstreamChange.ChangeType.ADDED)) then: @@ -37,9 +37,9 @@ class CurrentMultistreamHolderSpec extends Specification { def "add multiple upstreams"() { setup: def current = new CurrentMultistreamHolder(TestingCommons.emptyCaches()) - def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) - def up2 = new EthereumUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api()) - def up3 = new EthereumUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api()) + def up1 = new EthereumRpcUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) + def up2 = new EthereumRpcUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api()) + def up3 = new EthereumRpcUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api()) when: current.update(new UpstreamChange(Chain.ETHEREUM, up1, UpstreamChange.ChangeType.ADDED)) current.update(new UpstreamChange(Chain.ETHEREUM_CLASSIC, up2, UpstreamChange.ChangeType.ADDED)) @@ -53,10 +53,10 @@ class CurrentMultistreamHolderSpec extends Specification { def "remove upstream"() { setup: def current = new CurrentMultistreamHolder(TestingCommons.emptyCaches()) - def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) - def up2 = new EthereumUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api()) - def up3 = new EthereumUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api()) - def up1_del = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) + def up1 = new EthereumRpcUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) + def up2 = new EthereumRpcUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api()) + def up3 = new EthereumRpcUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api()) + def up1_del = new EthereumRpcUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) when: current.update(new UpstreamChange(Chain.ETHEREUM, up1, UpstreamChange.ChangeType.ADDED)) current.update(new UpstreamChange(Chain.ETHEREUM_CLASSIC, up2, UpstreamChange.ChangeType.ADDED)) @@ -71,7 +71,7 @@ class CurrentMultistreamHolderSpec extends Specification { def "available after adding"() { setup: def current = new CurrentMultistreamHolder(TestingCommons.emptyCaches()) - def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) + def up1 = new EthereumRpcUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api()) when: def act = current.isAvailable(Chain.ETHEREUM) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy index 371fb461..b5760836 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy @@ -21,7 +21,7 @@ import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.test.EthereumApiStub import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.grpc.Chain @@ -38,7 +38,7 @@ class FilteredApisSpec extends Specification { def "Verifies labels"() { setup: def i = 0 - List upstreams = [ + List upstreams = [ [test: "foo"], [test: "bar"], [test: "foo", test2: "baz"], @@ -49,7 +49,7 @@ class FilteredApisSpec extends Specification { create(_, _) >> TestingCommons.api().tap { it.id = "${i++}" } } def connectorFactory = new EthereumConnectorFactory(false, null, httpFactory, new MostWorkForkChoice()) - new EthereumUpstream( + new EthereumRpcUpstream( "test", Chain.ETHEREUM, new UpstreamsConfig.Options(), diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy index 10a036ac..5a97148f 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy @@ -20,7 +20,7 @@ import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.quorum.AlwaysQuorum import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.test.EthereumUpstreamMock +import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream @@ -38,8 +38,8 @@ class MultistreamSpec extends Specification { def "Aggregates methods"() { setup: - def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"])) - def up2 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test2", "eth_test3"])) + def up1 = new EthereumRpcUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"])) + def up2 = new EthereumRpcUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test2", "eth_test3"])) def aggr = new EthereumMultistream(Chain.ETHEREUM, [up1, up2], Caches.default()) when: aggr.onUpstreamsUpdated() diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/ERC20BalanceSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/ERC20BalanceSpec.groovy index d1f6cfa1..11235509 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/ERC20BalanceSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/ERC20BalanceSpec.groovy @@ -15,7 +15,7 @@ */ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.test.EthereumUpstreamMock +import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock import io.emeraldpay.dshackle.test.ReaderMock import io.emeraldpay.dshackle.upstream.ApiSource import io.emeraldpay.dshackle.upstream.FilteredApis @@ -47,7 +47,7 @@ class ERC20BalanceSpec extends Specification { JsonRpcResponse.ok('"0x0000000000000000000000000000000000000000000000000000001f28d72868"') ) - EthereumUpstream upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api) + EthereumRpcUpstream upstream = new EthereumRpcUpstreamMock(Chain.ETHEREUM, api) ERC20Token token = new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")) ERC20Balance query = new ERC20Balance() @@ -73,7 +73,7 @@ class ERC20BalanceSpec extends Specification { JsonRpcResponse.ok('"0x0000000000000000000000000000000000000000000000000000001f28d72868"') ) - EthereumUpstream upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api) + EthereumRpcUpstream upstream = new EthereumRpcUpstreamMock(Chain.ETHEREUM, api) ERC20Token token = new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")) ERC20Balance query = new ERC20Balance() diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy index f7fd5a7c..84c29069 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumReaderSpec.groovy @@ -23,7 +23,7 @@ import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxContainer import io.emeraldpay.dshackle.data.TxId -import io.emeraldpay.dshackle.test.EthereumUpstreamMock +import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.calls.CallMethods @@ -203,7 +203,7 @@ class EthereumReaderSpec extends Specification { api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0x10") // height 101 + 1 => 102 => 0x66 api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "0x66"], "0xff") - EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api) + EthereumRpcUpstreamMock upstream = new EthereumRpcUpstreamMock(Chain.ETHEREUM, api) def upstreams = TestingCommons.multistream(upstream) def reader = new EthereumReader(upstreams, Caches.default(), calls) reader.start() @@ -241,7 +241,7 @@ class EthereumReaderSpec extends Specification { api.answerOnce("eth_getTransactionReceipt", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"], [ transactionHash: "0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2" ]) - EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api) + EthereumRpcUpstreamMock upstream = new EthereumRpcUpstreamMock(Chain.ETHEREUM, api) def upstreams = TestingCommons.multistream(upstream) def reader = new EthereumReader(upstreams, Caches.default(), calls) reader.start() @@ -257,7 +257,7 @@ class EthereumReaderSpec extends Specification { def "Read receipt from cache if available"() { setup: def api = TestingCommons.api() - EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api) + EthereumRpcUpstreamMock upstream = new EthereumRpcUpstreamMock(Chain.ETHEREUM, api) def upstreams = TestingCommons.multistream(upstream) def receiptCache = Mock(ReceiptRedisCache) { 1 * it.read(TxId.from("0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")) >> From bdd1281458177cf2d7dc753ab25422e80ce3b27d Mon Sep 17 00:00:00 2001 From: terminal Date: Fri, 29 Jul 2022 19:19:56 +0400 Subject: [PATCH 04/11] refactor ethereum subscription to support ethereum pos --- .../io/emeraldpay/dshackle/rpc/NativeSubscribe.kt | 3 ++- .../dshackle/upstream/ethereum/EthereumMultistream.kt | 6 +++--- .../dshackle/upstream/ethereum/EthereumSubscribe.kt | 2 +- .../upstream/ethereum/subscribe/ConnectBlockUpdates.kt | 3 ++- .../upstream/ethereum/subscribe/ConnectLogs.kt | 5 +++-- .../upstream/ethereum/subscribe/ConnectNewHeads.kt | 3 ++- .../upstream/ethereum/subscribe/ConnectSyncing.kt | 3 ++- .../upstream/ethereum/subscribe/ProduceLogs.kt | 3 ++- .../upstream/ethereum_pos/EthereumPosMultiStream.kt | 10 ++++++---- 9 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt index 63640e3a..6827735e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt @@ -20,6 +20,7 @@ import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.SilentException import io.emeraldpay.dshackle.upstream.MultistreamHolder +import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.grpc.BlockchainType import io.emeraldpay.grpc.Chain @@ -83,7 +84,7 @@ open class NativeSubscribe( open fun subscribe(chain: Chain, method: String, params: Any?): Flux { val up = multistreamHolder.getUpstream(chain) ?: return Flux.error(SilentException.UnsupportedBlockchain(chain)) - return (up as EthereumMultistream) + return (up as EthereumLikeMultistream) .getSubscribe() .subscribe(method, params) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt index f9557cb7..62df3041 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt @@ -38,7 +38,7 @@ open class EthereumMultistream( chain: Chain, val upstreams: MutableList, caches: Caches -) : Multistream(chain, upstreams as MutableList, caches, CacheRequested(caches)) { +) : Multistream(chain, upstreams as MutableList, caches, CacheRequested(caches)), EthereumLikeMultistream { companion object { private val log = LoggerFactory.getLogger(EthereumMultistream::class.java) @@ -80,7 +80,7 @@ open class EthereumMultistream( return super.isRunning() || reader.isRunning } - open fun getReader(): EthereumReader { + override fun getReader(): EthereumReader { return reader } @@ -138,7 +138,7 @@ open class EthereumMultistream( return Mono.just(LocalCallRouter(reader, getMethods(), getHead())) } - open fun getSubscribe(): EthereumSubscribe { + override fun getSubscribe(): EthereumSubscribe { return subscribe } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumSubscribe.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumSubscribe.kt index 69487b1e..cb160fa3 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumSubscribe.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumSubscribe.kt @@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory import reactor.core.publisher.Flux open class EthereumSubscribe( - val upstream: EthereumMultistream + val upstream: EthereumLikeMultistream ) { companion object { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt index 3c59b7de..252276a7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt @@ -19,6 +19,7 @@ import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxId import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import org.slf4j.LoggerFactory import reactor.core.publisher.Flux @@ -32,7 +33,7 @@ import kotlin.concurrent.withLock import kotlin.concurrent.write class ConnectBlockUpdates( - private val upstream: EthereumMultistream + private val upstream: EthereumLikeMultistream ) { companion object { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt index 1309e4c2..ce99e941 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt @@ -15,6 +15,7 @@ */ package io.emeraldpay.dshackle.upstream.ethereum.subscribe +import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage import io.emeraldpay.etherjar.domain.Address @@ -25,7 +26,7 @@ import reactor.core.publisher.Flux import java.util.function.Function open class ConnectLogs( - upstream: EthereumMultistream, + upstream: EthereumLikeMultistream, private val connectBlockUpdates: ConnectBlockUpdates, ) { @@ -36,7 +37,7 @@ open class ConnectLogs( private val TOPIC_COMPARATOR = HexDataComparator() } - constructor(upstream: EthereumMultistream) : this(upstream, ConnectBlockUpdates(upstream)) + constructor(upstream: EthereumLikeMultistream) : this(upstream, ConnectBlockUpdates(upstream)) private val produceLogs = ProduceLogs(upstream) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt index 0bcc1f71..f5d1b052 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt @@ -15,6 +15,7 @@ */ package io.emeraldpay.dshackle.upstream.ethereum.subscribe +import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.NewHeadMessage import org.slf4j.LoggerFactory @@ -28,7 +29,7 @@ import kotlin.concurrent.withLock * Connects/reconnects to the upstream to produce NewHeads messages */ class ConnectNewHeads( - private val upstream: EthereumMultistream + private val upstream: EthereumLikeMultistream ) { companion object { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt index f1f2e821..fc0d7285 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt @@ -16,6 +16,7 @@ package io.emeraldpay.dshackle.upstream.ethereum.subscribe import io.emeraldpay.dshackle.upstream.UpstreamAvailability +import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import org.slf4j.LoggerFactory import reactor.core.publisher.Flux @@ -24,7 +25,7 @@ import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock class ConnectSyncing( - private val upstream: EthereumMultistream + private val upstream: EthereumLikeMultistream ) { companion object { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt index 5461072e..374193e0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt @@ -20,6 +20,7 @@ import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxId import io.emeraldpay.dshackle.reader.Reader +import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage import io.emeraldpay.etherjar.hex.HexData @@ -38,7 +39,7 @@ class ProduceLogs( private val log = LoggerFactory.getLogger(ProduceLogs::class.java) } - constructor(upstream: EthereumMultistream) : this(upstream.getReader().receipts()) + constructor(upstream: EthereumLikeMultistream) : this(upstream.getReader().receipts()) private val objectMapper = Global.objectMapper diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt index e81b28e2..90679289 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt @@ -38,7 +38,7 @@ open class EthereumPosMultistream( chain: Chain, val upstreams: MutableList, caches: Caches -) : Multistream(chain, upstreams as MutableList, caches, CacheRequested(caches)) { +) : Multistream(chain, upstreams as MutableList, caches, CacheRequested(caches)), EthereumLikeMultistream { companion object { private val log = LoggerFactory.getLogger(EthereumPosMultistream::class.java) @@ -48,6 +48,8 @@ open class EthereumPosMultistream( private val reader: EthereumReader = EthereumReader(this, this.caches, getMethodsFactory()) private val feeEstimation = EthereumPriorityFees(this, reader, 256) + private val subscribe = EthereumSubscribe(this) + init { this.init() } @@ -73,7 +75,7 @@ open class EthereumPosMultistream( return super.isRunning() || reader.isRunning } - open fun getReader(): EthereumReader { + override fun getReader(): EthereumReader { return reader } @@ -131,8 +133,8 @@ open class EthereumPosMultistream( return Mono.just(LocalCallRouter(reader, getMethods(), getHead())) } - open fun getSubscribe(): EthereumSubscribe { - throw Error("Does not supports subscription for PoS ethereum") + override fun getSubscribe(): EthereumSubscribe { + return subscribe } override fun getFeeEstimation(): ChainFees { From 38958a6296dec880ffe90ccf36306570abd2eb5f Mon Sep 17 00:00:00 2001 From: terminal Date: Fri, 29 Jul 2022 19:24:32 +0400 Subject: [PATCH 05/11] fix formatting --- .../dshackle/config/UpstreamsConfig.kt | 4 ++-- .../dshackle/config/UpstreamsConfigReader.kt | 6 +++--- .../io/emeraldpay/dshackle/data/RingSet.kt | 4 ++-- .../emeraldpay/dshackle/rpc/NativeSubscribe.kt | 1 - .../dshackle/startup/ConfiguredUpstreams.kt | 14 +++++++++----- .../emeraldpay/dshackle/upstream/AbstractHead.kt | 2 -- .../dshackle/upstream/DistanceExtractor.kt | 6 +++--- .../emeraldpay/dshackle/upstream/HttpFactory.kt | 2 +- .../dshackle/upstream/HttpRpcFactory.kt | 2 +- .../upstream/bitcoin/BitcoinMultistream.kt | 4 ++-- .../upstream/ethereum/EthereumRpcUpstream.kt | 8 +++++--- .../upstream/ethereum/EthereumUpstream.kt | 2 +- .../ethereum/EthereumUpstreamValidator.kt | 1 - .../ethereum/connectors/ConnectorFactory.kt | 2 +- .../ethereum/connectors/EthereumConnector.kt | 2 +- .../connectors/EthereumConnectorFactory.kt | 9 ++++----- .../ethereum/connectors/EthereumRpcConnector.kt | 16 +++++++++------- .../ethereum/connectors/EthereumWsConnector.kt | 4 ++-- .../ethereum/subscribe/ConnectBlockUpdates.kt | 1 - .../upstream/ethereum/subscribe/ConnectLogs.kt | 1 - .../ethereum/subscribe/ConnectNewHeads.kt | 1 - .../ethereum/subscribe/ConnectSyncing.kt | 1 - .../upstream/ethereum/subscribe/ProduceLogs.kt | 1 - .../ethereum_pos/EthereumPosRpcUpstream.kt | 8 +++++--- .../upstream/ethereum_pos/EthereumPosUpstream.kt | 2 +- .../dshackle/upstream/forkchoice/ForkChoice.kt | 6 +++--- .../upstream/forkchoice/MostWorkForkChoice.kt | 5 ++--- .../forkchoice/NoChoiceWithPriorityForkChoice.kt | 4 ++-- .../upstream/forkchoice/PriorityForkChoice.kt | 4 ++-- 29 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index f8cf5dd7..a3f88519 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -116,8 +116,8 @@ open class UpstreamsConfig { } class EthereumPosConnection : UpstreamConnection() { - var execution : EthereumConnection? = null - var blockPriority : Int = 0 + var execution: EthereumConnection? = null + var blockPriority: Int = 0 } data class BitcoinZeroMq( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index 9ab5be40..71dd6704 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -165,7 +165,7 @@ class UpstreamsConfigReader( return connection } - private fun readEthereumPosConnection(connConfigNode: MappingNode) : UpstreamsConfig.EthereumPosConnection { + private fun readEthereumPosConnection(connConfigNode: MappingNode): UpstreamsConfig.EthereumPosConnection { val connection = UpstreamsConfig.EthereumPosConnection() getMapping(connConfigNode, "execution")?.let { connection.execution = readEthereumConnection(it) @@ -175,7 +175,7 @@ class UpstreamsConfigReader( } return connection } - private fun readEthereumConnection(connConfigNode : MappingNode) : UpstreamsConfig.EthereumConnection { + private fun readEthereumConnection(connConfigNode: MappingNode): UpstreamsConfig.EthereumConnection { val connection = UpstreamsConfig.EthereumConnection() getMapping(connConfigNode, "rpc")?.let { node -> getValueAsString(node, "url")?.let { url -> @@ -211,7 +211,7 @@ class UpstreamsConfigReader( return connection } - private fun readUpstream(config: UpstreamsConfig, upNode: MappingNode, connFactory: () -> T) { + private fun readUpstream(config: UpstreamsConfig, upNode: MappingNode, connFactory: () -> T) { val upstream = UpstreamsConfig.Upstream() readUpstreamCommon(upNode, upstream) readUpstreamStandard(upNode, upstream) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt b/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt index a3e1815e..37b37af4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt @@ -4,7 +4,7 @@ import java.util.concurrent.atomic.AtomicReference class RingSet( private val maxSize: Int -): Set { +) : Set { private var seqValues: AtomicReference> = AtomicReference(emptyList()) private var set: Set = emptySet() override val size: Int @@ -41,4 +41,4 @@ class RingSet( override fun containsAll(elements: Collection<@UnsafeVariance T>): Boolean { return set.containsAll(elements) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt index 6827735e..a2b7b3b2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt @@ -21,7 +21,6 @@ import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.SilentException import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.grpc.BlockchainType import io.emeraldpay.grpc.Chain import io.grpc.Status diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index 0eba3a3b..994c42d1 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -20,7 +20,11 @@ import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.CurrentMultistreamHolder +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.HttpRpcFactory +import io.emeraldpay.dshackle.upstream.MergedHead +import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcHead import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcUpstream import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinZMQHead @@ -147,7 +151,7 @@ open class ConfiguredUpstreams( config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options - ) : Upstream? { + ): Upstream? { val conn = config.connection!! val execution = conn.execution if (execution == null) { @@ -176,7 +180,7 @@ open class ConfiguredUpstreams( config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options - ) : Upstream? { + ): Upstream? { val conn = config.connection!! val httpFactory = buildHttpFactory(conn) if (httpFactory == null) { @@ -218,7 +222,7 @@ open class ConfiguredUpstreams( config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options - ) : EthereumRpcUpstream? { + ): EthereumRpcUpstream? { val conn = config.connection!! val urls = ArrayList() @@ -291,7 +295,7 @@ open class ConfiguredUpstreams( } } - private fun buildEthereumConnectorFactory(conn: UpstreamsConfig.EthereumConnection, chain: Chain, urls: ArrayList, forkChoice: ForkChoice): EthereumConnectorFactory? { + private fun buildEthereumConnectorFactory(conn: UpstreamsConfig.EthereumConnection, chain: Chain, urls: ArrayList, forkChoice: ForkChoice): EthereumConnectorFactory? { val wsFactoryApi = buildWsFactory(conn, urls) val httpFactory = buildHttpFactory(conn, urls) log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}") diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt index ce4e7931..48ebfe63 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt @@ -20,11 +20,9 @@ import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import org.slf4j.LoggerFactory import reactor.core.Disposable import reactor.core.publisher.Flux -import reactor.core.publisher.Mono import reactor.core.publisher.Sinks import reactor.core.scheduler.Schedulers import reactor.kotlin.core.publisher.toMono -import java.util.concurrent.atomic.AtomicReference abstract class AbstractHead( private val forkChoice: ForkChoice diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt index 77dc9b61..677d1e7f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DistanceExtractor.kt @@ -4,8 +4,8 @@ import io.emeraldpay.dshackle.data.BlockContainer class DistanceExtractor { sealed class ChainDistance { - data class Distance(val dist: Long): ChainDistance() - object Fork: ChainDistance() + data class Distance(val dist: Long) : ChainDistance() + object Fork : ChainDistance() } companion object { @@ -25,4 +25,4 @@ class DistanceExtractor { } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt index 10d99560..359476ed 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt @@ -7,4 +7,4 @@ import io.emeraldpay.grpc.Chain interface HttpFactory { fun create(id: String?, chain: Chain): Reader -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt index 9261cc6c..adf0aafd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt @@ -42,4 +42,4 @@ open class HttpRpcFactory( tls ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt index 8a9054b7..3d8b0bba 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt @@ -86,8 +86,8 @@ open class BitcoinMultistream( } } else { val newHead = MergedHead(upstreams.map { it.getHead() }, MostWorkForkChoice()).apply { - this.start() - } + this.start() + } val lagObserver = BitcoinHeadLagObserver(newHead, upstreams) this.lagObserver = lagObserver lagObserver.start() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt index e4b104cd..b071af41 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt @@ -21,7 +21,9 @@ import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector @@ -42,8 +44,8 @@ open class EthereumRpcUpstream( connectorFactory: ConnectorFactory ) : EthereumUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { private val log = LoggerFactory.getLogger(EthereumRpcUpstream::class.java) - private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) - private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) + private val validator: EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) + private val connector: EthereumConnector = connectorFactory.create(this, validator, chain) private var validatorSubscription: Disposable? = null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt index 0b50610e..c14bd120 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt @@ -43,4 +43,4 @@ abstract class EthereumUpstream( override fun getLabels(): Collection { return node?.let { listOf(it.labels) } ?: emptyList() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt index d8cbc7bd..d1fdc07c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt @@ -22,7 +22,6 @@ import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability -import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.etherjar.rpc.json.SyncingJson diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt index 09c5f26d..0e0d21e5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/ConnectorFactory.kt @@ -7,4 +7,4 @@ import io.emeraldpay.grpc.Chain interface ConnectorFactory { fun create(upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain): EthereumConnector fun isValid(): Boolean -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt index dcd8f5b0..85ecf596 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnector.kt @@ -10,4 +10,4 @@ interface EthereumConnector : Lifecycle { fun getHead(): Head fun getApi(): Reader -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt index 78f69556..2850ee10 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumConnectorFactory.kt @@ -2,7 +2,6 @@ package io.emeraldpay.dshackle.upstream.ethereum.connectors import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.HttpFactory -import io.emeraldpay.dshackle.upstream.HttpRpcFactory import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice @@ -14,18 +13,18 @@ open class EthereumConnectorFactory( private val wsFactory: EthereumWsFactory?, private val httpFactory: HttpFactory?, private val forkChoice: ForkChoice -): ConnectorFactory { +) : ConnectorFactory { private val log = LoggerFactory.getLogger(EthereumConnectorFactory::class.java) override fun isValid(): Boolean { if (preferHttp && httpFactory == null) { - return false; + return false } return true } override fun create(upstream: DefaultUpstream, validator: EthereumUpstreamValidator, chain: Chain): EthereumConnector { - if (wsFactory!= null && !preferHttp) { + if (wsFactory != null && !preferHttp) { return EthereumWsConnector(wsFactory, upstream, validator, chain, forkChoice) } if (httpFactory == null) { @@ -33,4 +32,4 @@ open class EthereumConnectorFactory( } return EthereumRpcConnector(httpFactory.create(upstream.getId(), chain), wsFactory, upstream.getId(), forkChoice) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt index 8ca9d135..03c541b1 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumRpcConnector.kt @@ -5,9 +5,11 @@ import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.MergedHead -import io.emeraldpay.dshackle.upstream.ethereum.* +import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcHead +import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory +import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsHead +import io.emeraldpay.dshackle.upstream.ethereum.WsConnection import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice -import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import org.slf4j.LoggerFactory @@ -15,13 +17,13 @@ import org.springframework.context.Lifecycle import java.time.Duration class EthereumRpcConnector( - private val directReader : Reader, + private val directReader: Reader, wsFactory: EthereumWsFactory?, - id : String, + id: String, forkChoice: ForkChoice ) : EthereumConnector, CachesEnabled { - private val conn : WsConnection? - private val head : Head + private val conn: WsConnection? + private val head: Head companion object { private val log = LoggerFactory.getLogger(EthereumRpcConnector::class.java) @@ -76,4 +78,4 @@ class EthereumRpcConnector( override fun getHead(): Head { return head } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt index 46bd437b..c4844dda 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/connectors/EthereumWsConnector.kt @@ -58,7 +58,7 @@ class EthereumWsConnector( } override fun isRunning(): Boolean { - return head.isRunning + return head.isRunning } override fun stop() { @@ -73,4 +73,4 @@ class EthereumWsConnector( override fun getHead(): Head { return head } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt index 252276a7..0a424faf 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectBlockUpdates.kt @@ -20,7 +20,6 @@ import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxId import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import org.slf4j.LoggerFactory import reactor.core.publisher.Flux import reactor.core.scheduler.Schedulers diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt index ce99e941..9482a10f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectLogs.kt @@ -16,7 +16,6 @@ package io.emeraldpay.dshackle.upstream.ethereum.subscribe import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage import io.emeraldpay.etherjar.domain.Address import io.emeraldpay.etherjar.hex.Hex32 diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt index f5d1b052..c3b7bb4a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectNewHeads.kt @@ -16,7 +16,6 @@ package io.emeraldpay.dshackle.upstream.ethereum.subscribe import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.NewHeadMessage import org.slf4j.LoggerFactory import reactor.core.publisher.Flux diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt index fc0d7285..d65af1e7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ConnectSyncing.kt @@ -17,7 +17,6 @@ package io.emeraldpay.dshackle.upstream.ethereum.subscribe import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import org.slf4j.LoggerFactory import reactor.core.publisher.Flux import java.time.Duration diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt index 374193e0..44da804d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/ProduceLogs.kt @@ -21,7 +21,6 @@ import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxId import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeMultistream -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage import io.emeraldpay.etherjar.hex.HexData import io.emeraldpay.etherjar.rpc.json.TransactionReceiptJson diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt index 7838c08e..7cd6cca6 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt @@ -21,7 +21,9 @@ import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.startup.QuorumForLabels -import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector @@ -42,8 +44,8 @@ open class EthereumPosRpcUpstream( connectorFactory: ConnectorFactory ) : EthereumPosUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { private val log = LoggerFactory.getLogger(EthereumPosRpcUpstream::class.java) - private val validator : EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) - private val connector : EthereumConnector = connectorFactory.create(this, validator, chain) + private val validator: EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) + private val connector: EthereumConnector = connectorFactory.create(this, validator, chain) private var validatorSubscription: Disposable? = null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt index f98aa936..4ce8253a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt @@ -43,4 +43,4 @@ abstract class EthereumPosUpstream( override fun getLabels(): Collection { return node?.let { listOf(it.labels) } ?: emptyList() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt index aa664241..2db6006a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/ForkChoice.kt @@ -5,8 +5,8 @@ import io.emeraldpay.dshackle.data.BlockContainer interface ForkChoice { sealed class ChoiceResult { - data class Updated(val nwhead: BlockContainer): ChoiceResult() - data class Same(val head: BlockContainer?): ChoiceResult() + data class Updated(val nwhead: BlockContainer) : ChoiceResult() + data class Same(val head: BlockContainer?) : ChoiceResult() } fun getHead(): BlockContainer? @@ -14,4 +14,4 @@ interface ForkChoice { fun filter(block: BlockContainer): Boolean fun choose(block: BlockContainer): ChoiceResult -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt index d4c5e9c0..cc276b45 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/MostWorkForkChoice.kt @@ -6,7 +6,7 @@ import java.util.concurrent.atomic.AtomicReference class MostWorkForkChoice : ForkChoice { private val head = AtomicReference(null) - override fun getHead() : BlockContainer? { + override fun getHead(): BlockContainer? { return head.get() } @@ -28,5 +28,4 @@ class MostWorkForkChoice : ForkChoice { } return ForkChoice.ChoiceResult.Same(nwhead) } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt index 523f4547..177ae220 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt @@ -7,7 +7,7 @@ import java.util.concurrent.atomic.AtomicReference class NoChoiceWithPriorityForkChoice( private val nodeRating: Int -): ForkChoice { +) : ForkChoice { private val head = AtomicReference(null) private val seenBlocks = RingSet(10) @@ -33,4 +33,4 @@ class NoChoiceWithPriorityForkChoice( } return ForkChoice.ChoiceResult.Same(nwhead) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt index e9ec338b..50685b3a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/PriorityForkChoice.kt @@ -5,7 +5,7 @@ import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.RingSet import java.util.concurrent.atomic.AtomicReference -class PriorityForkChoice: ForkChoice { +class PriorityForkChoice : ForkChoice { private val head = AtomicReference(null) private val seenBlocks = RingSet(10) @@ -32,4 +32,4 @@ class PriorityForkChoice: ForkChoice { } return ForkChoice.ChoiceResult.Same(nwhead) } -} \ No newline at end of file +} From 04edbe46084b2b89e6e90d9cdadfeeac06f56e0b Mon Sep 17 00:00:00 2001 From: terminal Date: Mon, 1 Aug 2022 13:41:49 +0400 Subject: [PATCH 06/11] add interface --- .../dshackle/upstream/ethereum/EthereumLikeMultistream.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLikeMultistream.kt diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLikeMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLikeMultistream.kt new file mode 100644 index 00000000..40d3a437 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLikeMultistream.kt @@ -0,0 +1,8 @@ +package io.emeraldpay.dshackle.upstream.ethereum + +import io.emeraldpay.dshackle.upstream.Upstream + +interface EthereumLikeMultistream : Upstream { + fun getReader(): EthereumReader + fun getSubscribe(): EthereumSubscribe +} From c6f22ce91af712d8fedc89dec2a01403b72eebfe Mon Sep 17 00:00:00 2001 From: terminal Date: Mon, 1 Aug 2022 13:51:16 +0400 Subject: [PATCH 07/11] fix some issues after merge --- .../io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt | 3 +-- .../dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index 18bc1b82..6fd9b254 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -101,7 +101,7 @@ class UpstreamsConfigReader( val connConfigNode = getMapping(connNode, "grpc")!! val upstream = UpstreamsConfig.Upstream() readUpstreamCommon(upNode, upstream) - readUpstreamGrpc(upNode, upstream) + readUpstreamGrpc(upNode) if (isValid(upstream)) { config.upstreams.add(upstream) val connection = UpstreamsConfig.GrpcConnection() @@ -245,7 +245,6 @@ class UpstreamsConfigReader( internal fun readUpstreamGrpc( upNode: MappingNode, - upstream: UpstreamsConfig.Upstream ) { // Dshackle gRPC connection dispatches requests to different upstreams, which may // be on different blockchains, and each may have different set of labels. diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt index 3735c98a..1f0e7807 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt @@ -48,7 +48,7 @@ open class EthereumPosGrpcUpstream( private val parentId: String, role: UpstreamsConfig.UpstreamRole, private val chain: Chain, - remote: ReactorBlockchainGrpc.ReactorBlockchainStub, + private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, client: JsonRpcGrpcClient, nodeRating: Int ) : EthereumPosUpstream( @@ -121,6 +121,10 @@ open class EthereumPosGrpcUpstream( return upstreamStatus.getNodes() } + override fun getBlockchainApi(): ReactorBlockchainGrpc.ReactorBlockchainStub { + return remote + } + // ------------------------------------------------------------------------------------------ override fun getLabels(): Collection { From 5f85149c67478cd9d5ccf4fb7e4f86c33090e5ee Mon Sep 17 00:00:00 2001 From: terminal Date: Mon, 1 Aug 2022 21:26:44 +0400 Subject: [PATCH 08/11] ethereum pos doc and some config naming --- docs/04-upstream-config.adoc | 16 ++++++++++++++++ docs/reference-configuration.adoc | 16 ++++++++++++++++ .../dshackle/config/UpstreamsConfig.kt | 4 ++-- .../dshackle/config/UpstreamsConfigReader.kt | 6 +++--- .../dshackle/startup/ConfiguredUpstreams.kt | 4 ++-- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/docs/04-upstream-config.adoc b/docs/04-upstream-config.adoc index ebe27fb0..eb6d3544 100644 --- a/docs/04-upstream-config.adoc +++ b/docs/04-upstream-config.adoc @@ -17,6 +17,13 @@ Those protocols can be configures with additional security, TLS and authenticati - Most of Ethereum nodes support WebSocket connection, in addition to the JSON RPC. If it's available on your node, it's suggested to configure both JSON RPC and WebSocket connection +==== Ethereum PoS + +- The support for PoS Ethereum isn't fully implemented yet. However, we support it in a simplified way. +As we can no longer rely on the difficulty parameter of a block for fork choice algorithm we implement upstream rating. +In short terms, we just consider the upstream with the highest rating to be always correct when reporting its head. +Unless it's down then we will fallback on the upstream with the second highest rating, etc. + ==== Bitcoin - Bitcoind needs to be configured to index/track addresses that you're going to request. @@ -72,6 +79,15 @@ cluster: basic-auth: username: ${INFURA_USER} password: ${INFURA_PASSWD} + - id: ethereum-pos + chain: ropsten + connection: + ethereum-pos: + execution: + rpc: + url: ${ROPSTEN_NODE_RPC_URL} + ws: + url: ${ROPSTEN_NODE_WS_URL} ---- There are two main segments for upstreams configuration: diff --git a/docs/reference-configuration.adoc b/docs/reference-configuration.adoc index c6e5e773..4a61982e 100644 --- a/docs/reference-configuration.adoc +++ b/docs/reference-configuration.adoc @@ -769,6 +769,22 @@ Default is 15Mb |=== +==== PoS Ethereum Connection Options +.Connection Config for PoS Ethereum Upstream +[cols="2a,5"] +|=== +| Option | Description + +| `execution` +a| Here you can specify any option from plain ethereum connection options listed above + +This is your connection to an execution layer of PoS Ethereum + +| `upstream-rating` +a| Rating for this upstream. We will always consider the head of the chain to be + +the latest block we saw from the upstream with the highest rating. + +|=== + ==== Bitcoin Connection Options .Connection Config for Bitcoin Upstream diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index a3f88519..168f2f30 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -102,7 +102,7 @@ open class UpstreamsConfig { var host: String? = null var port: Int = 0 var auth: AuthConfig.ClientTlsAuth? = null - var nodeRating: Int = 0 + var upstreamRating: Int = 0 } class EthereumConnection : RpcConnection() { @@ -117,7 +117,7 @@ open class UpstreamsConfig { class EthereumPosConnection : UpstreamConnection() { var execution: EthereumConnection? = null - var blockPriority: Int = 0 + var upstreamRating: Int = 0 } data class BitcoinZeroMq( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index 6fd9b254..83af5646 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -107,7 +107,7 @@ class UpstreamsConfigReader( val connection = UpstreamsConfig.GrpcConnection() upstream.connection = connection getValueAsInt(connConfigNode, "node-rating")?.let { - connection.nodeRating = it + connection.upstreamRating = it } getValueAsString(connConfigNode, "host")?.let { connection.host = it @@ -170,8 +170,8 @@ class UpstreamsConfigReader( getMapping(connConfigNode, "execution")?.let { connection.execution = readEthereumConnection(it) } - getValueAsInt(connConfigNode, "block-priority")?.let { - connection.blockPriority = it + getValueAsInt(connConfigNode, "node-rating")?.let { + connection.upstreamRating = it } return connection } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index 18dc569f..5097deee 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -159,7 +159,7 @@ open class ConfiguredUpstreams( return null } val urls = ArrayList() - val connectorFactory = buildEthereumConnectorFactory(execution, chain, urls, NoChoiceWithPriorityForkChoice(conn.blockPriority)) + val connectorFactory = buildEthereumConnectorFactory(execution, chain, urls, NoChoiceWithPriorityForkChoice(conn.upstreamRating)) val methods = buildMethods(config, chain) if (connectorFactory == null) { return null @@ -256,7 +256,7 @@ open class ConfiguredUpstreams( endpoint.port, endpoint.auth, fileResolver, - endpoint.nodeRating + endpoint.upstreamRating ).apply { timeout = options.timeout } From e749dadcdd8aae0b2380069c2c344446e131375e Mon Sep 17 00:00:00 2001 From: terminal Date: Mon, 1 Aug 2022 21:32:20 +0400 Subject: [PATCH 09/11] use linkedhashset for ringset --- .../io/emeraldpay/dshackle/data/RingSet.kt | 34 ++++++++----------- .../NoChoiceWithPriorityForkChoice.kt | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt b/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt index 37b37af4..26a03a5b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/data/RingSet.kt @@ -5,40 +5,36 @@ import java.util.concurrent.atomic.AtomicReference class RingSet( private val maxSize: Int ) : Set { - private var seqValues: AtomicReference> = AtomicReference(emptyList()) - private var set: Set = emptySet() + private var setRef: AtomicReference> = AtomicReference(LinkedHashSet()) override val size: Int - get() = set.size + get() = setRef.get().size fun add(element: T) { - if (set.contains(element)) { - return - } - seqValues.getAndUpdate { vals -> - vals.let { - if (vals.size > maxSize) { - vals.drop(1) - } else { - vals + setRef.getAndUpdate { set -> + if (!set.contains(element)) { + val copyset = LinkedHashSet(set) + copyset.add(element) + if (copyset.size > maxSize) { + copyset.remove(set.elementAt(0)) } - }.plus(element).let { - set = HashSet(it) - it + copyset + } else { + set } } } override fun isEmpty(): Boolean { - return set.isEmpty() + return setRef.get().isEmpty() } override fun contains(element: @UnsafeVariance T): Boolean { - return set.contains(element) + return setRef.get().contains(element) } override fun iterator(): Iterator { - return set.iterator() + return setRef.get().iterator() } override fun containsAll(elements: Collection<@UnsafeVariance T>): Boolean { - return set.containsAll(elements) + return setRef.get().containsAll(elements) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt index 177ae220..951647dd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/forkchoice/NoChoiceWithPriorityForkChoice.kt @@ -9,7 +9,7 @@ class NoChoiceWithPriorityForkChoice( private val nodeRating: Int ) : ForkChoice { private val head = AtomicReference(null) - private val seenBlocks = RingSet(10) + private val seenBlocks = RingSet(100) override fun getHead(): BlockContainer? { return head.get() From ded1ebcaa504913207095bccf2d8efd63d37a210 Mon Sep 17 00:00:00 2001 From: terminal Date: Thu, 11 Aug 2022 18:51:45 +0400 Subject: [PATCH 10/11] add latency to access log --- .../dshackle/monitoring/accesslog/AccessHandlerGrpc.kt | 3 ++- .../dshackle/monitoring/accesslog/AccessHandlerHttp.kt | 6 ++++-- .../io/emeraldpay/dshackle/monitoring/accesslog/Events.kt | 2 ++ .../dshackle/monitoring/accesslog/EventsBuilder.kt | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerGrpc.kt b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerGrpc.kt index 952f2250..6b380d7b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerGrpc.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerGrpc.kt @@ -25,6 +25,7 @@ import io.grpc.ServerInterceptor import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service +import java.time.Instant @Service class AccessHandlerGrpc( @@ -119,7 +120,7 @@ class AccessHandlerGrpc( ): ServerCall.Listener { return process( call, headers, next, - EventsBuilder.NativeCall() as EventsBuilder.RequestReply<*, ReqT, RespT> + EventsBuilder.NativeCall(Instant.now()) as EventsBuilder.RequestReply<*, ReqT, RespT> ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerHttp.kt b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerHttp.kt index 503361dc..4b525349 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerHttp.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessHandlerHttp.kt @@ -127,11 +127,13 @@ class AccessHandlerHttp( private val accessLogWriter: AccessLogWriter, private val channel: Events.Channel ) : RequestHandler { + protected var startTs: Instant? = null protected var request: BlockchainOuterClass.NativeCallRequest? = null protected val responses = ArrayList() protected val updateLock = ReentrantLock() override fun onRequest(request: BlockchainOuterClass.NativeCallRequest) { + this.startTs = Instant.now() this.request = request } @@ -164,7 +166,7 @@ class AccessHandlerHttp( if (request == null) { return } - val builder = EventsBuilder.NativeCall() + val builder = EventsBuilder.NativeCall(startTs!!) builder.withChain(blockchain.id) builder.start(httpRequest) builder.onRequest(request!!) @@ -182,7 +184,7 @@ class AccessHandlerHttp( if (request == null) { return } - val builder = EventsBuilder.NativeCall() + val builder = EventsBuilder.NativeCall(startTs!!) builder.withChain(blockchain.id) builder.start(wsRequest) builder.onRequest(request!!) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt index 9aee432b..5542c40e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt @@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.monitoring.accesslog import com.fasterxml.jackson.annotation.JsonInclude import io.emeraldpay.grpc.Chain import org.slf4j.LoggerFactory +import java.time.Duration import java.time.Instant import java.util.UUID @@ -104,6 +105,7 @@ class Events { val selector: String? = null, val quorum: Long? = null, val minAvailability: String? = null, + val latency: Long, val succeed: Boolean, val rpcError: Int? = null, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt index b7726b77..f65918db 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt @@ -30,6 +30,7 @@ import reactor.netty.http.server.HttpServerRequest import reactor.netty.http.websocket.WebsocketInbound import java.net.InetAddress import java.net.InetSocketAddress +import java.time.Duration import java.time.Instant import java.util.Locale import java.util.UUID @@ -294,7 +295,7 @@ class EventsBuilder { } } - class NativeCall : + class NativeCall(private val startTs : Instant) : Base(), RequestReply { val items = ArrayList() @@ -330,6 +331,7 @@ class EventsBuilder { index = index++, succeed = msg.succeed, blockchain = chain, + latency = Duration.between(Instant.now(), startTs).toMillis(), nativeCall = item, payloadSizeBytes = item.payloadSizeBytes, id = UUID.randomUUID(), @@ -354,6 +356,7 @@ class EventsBuilder { index = index++, succeed = !reply.isError(), blockchain = chain, + latency = Duration.between(startTs, Instant.now()).toMillis(), nativeCall = item, payloadSizeBytes = item.payloadSizeBytes, id = UUID.randomUUID(), From 3369eb35e2ba194dcf81f93edff248c559ec82e5 Mon Sep 17 00:00:00 2001 From: terminal Date: Fri, 12 Aug 2022 15:20:45 +0400 Subject: [PATCH 11/11] move emeral-java-api to submodule --- .gitmodules | 3 +++ emerald-java-client | 1 + settings.gradle | 6 ++++++ 3 files changed, 10 insertions(+) create mode 100644 .gitmodules create mode 160000 emerald-java-client diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..cbd279b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "emerald-java-client"] + path = emerald-java-client + url = git@github.com:p2p-org/emerald-java-client.git diff --git a/emerald-java-client b/emerald-java-client new file mode 160000 index 00000000..934a7d0f --- /dev/null +++ b/emerald-java-client @@ -0,0 +1 @@ +Subproject commit 934a7d0fd01e2920c3dc1db3097384e17a0166af diff --git a/settings.gradle b/settings.gradle index 0778dbab..c0b940b8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,8 @@ enableFeaturePreview("VERSION_CATALOGS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +includeBuild('./emerald-java-client') { + dependencySubstitution { + substitute module('io.emeraldpay:emerald-api:0.12-alpha.1') using project(':') + } +} \ No newline at end of file