diff --git a/docs/04-upstream-config.adoc b/docs/04-upstream-config.adoc index 6bb82cee..282e6591 100644 --- a/docs/04-upstream-config.adoc +++ b/docs/04-upstream-config.adoc @@ -218,6 +218,27 @@ NOTE: It's especially useful when used together with upstream labels.If an archi possible to specify that the client wants to execute method `trace_transaction` only on an archive node(s), which has complete historical data for tracing. +=== Static Methods + +You can overwrite existing methods or add new ones using a static response: + +[source, yaml] +---- +upstreams: + - id: my-node + chain: ethereum + methods: + enabled: + - name: net_version + static: "\"100000\"" + - name: eth_chainId + static: "0x186a0" + - name: eth_custom_array + static: '["custom_array_response"]' + - name: eth_custom_bool + static: "false" +---- + === Authentication ==== TLS @@ -234,4 +255,4 @@ NOTE: Please note that `key` must be encoded with _PKCS 8_ For JSON RPC and Websockets a Basic Authentication can be used: - `username` - username -- `password` - password \ No newline at end of file +- `password` - password diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index ec44ed86..09f4fae4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -173,6 +173,7 @@ open class UpstreamsConfig { class Method( val name: String, - val quorum: String? = null + val quorum: String? = null, + val static: String? = null ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index b91957b8..2089d69d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -249,7 +249,8 @@ class UpstreamsConfigReader( getValueAsString(m, "name")?.let { name -> UpstreamsConfig.Method( name = name, - quorum = getValueAsString(m, "quorum") + quorum = getValueAsString(m, "quorum"), + static = getValueAsString(m, "static") ) } }?.filterNotNull()?.toSet() ?: emptySet() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index d7951d0e..7a9cf8d9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -123,6 +123,9 @@ open class ConfiguredUpstreams( if (m.quorum != null) { it.setQuorum(m.name, m.quorum) } + if (m.static != null) { + it.setStaticResponse(m.name, m.static) + } } } } else { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethods.kt index ca2299c1..38d09ae0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethods.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethods.kt @@ -16,11 +16,13 @@ */ package io.emeraldpay.dshackle.upstream.calls +import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.quorum.AlwaysQuorum import io.emeraldpay.dshackle.quorum.CallQuorum import io.emeraldpay.dshackle.quorum.NonEmptyQuorum import io.emeraldpay.dshackle.quorum.NotLaggingQuorum import org.slf4j.LoggerFactory +import java.io.IOException import java.util.Collections /** @@ -44,6 +46,7 @@ class ManagedCallMethods( enabled + delegated - disabled ) private val quorum: MutableMap = HashMap() + private val staticResponse: MutableMap = HashMap() init { enabled.forEach { m -> @@ -64,6 +67,10 @@ class ManagedCallMethods( this.quorum[method] = quorum } + fun setStaticResponse(method: String, response: String) { + this.staticResponse[method] = response + } + override fun getQuorumFor(method: String): CallQuorum { return when { Collections.binarySearch(delegated, method) >= 0 -> delegate.getQuorumFor(method) @@ -84,10 +91,22 @@ class ManagedCallMethods( } override fun isHardcoded(method: String): Boolean { - return delegate.isHardcoded(method) + return this.staticResponse.containsKey(method) || delegate.isHardcoded(method) } override fun executeHardcoded(method: String): ByteArray { + if (this.staticResponse.containsKey(method)) { + var json: String = this.staticResponse[method].orEmpty() + // Check if it's valid JSON + val mapper = ObjectMapper() + try { + mapper.readTree(json) + } catch (e: IOException) { + // Encode and default to string + json = mapper.writeValueAsString(json) + } + return json.toByteArray() + } return delegate.executeHardcoded(method) } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy index 5cdaccba..6694b2e8 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy @@ -22,8 +22,8 @@ class ConfiguredUpstreamsSpec extends Specification { ) def methods = new UpstreamsConfig.Methods( [ - new UpstreamsConfig.Method("foo_bar", null), - new UpstreamsConfig.Method("foo_bar", "not_empty") + new UpstreamsConfig.Method("foo_bar", null, null), + new UpstreamsConfig.Method("foo_bar", "not_empty", null) ] as Set, [] as Set ) @@ -35,4 +35,27 @@ class ConfiguredUpstreamsSpec extends Specification { act instanceof ManagedCallMethods act.getQuorumFor("foo_bar") instanceof NonEmptyQuorum } + + def "Got static response from extra methods"() { + setup: + def currentUpstreams = Mock(CurrentMultistreamHolder) { + _ * getDefaultMethods(Chain.ETHEREUM) >> new DefaultEthereumMethods(Chain.ETHEREUM) + } + def configurer = new ConfiguredUpstreams( + currentUpstreams, Stub(FileResolver), Stub(UpstreamsConfig), Stub(CachesFactory) + ) + def methods = new UpstreamsConfig.Methods( + [ + new UpstreamsConfig.Method("foo_bar", null, "static_response") + ] as Set, + [] as Set + ) + def upstream = new UpstreamsConfig.Upstream() + upstream.methods = methods + when: + def act = configurer.buildMethods(upstream, Chain.ETHEREUM) + then: + act instanceof ManagedCallMethods + new String(act.executeHardcoded("foo_bar")) == "\"static_response\"" + } }