diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfig.kt index e6255b4c..8a4a973b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfig.kt @@ -17,6 +17,8 @@ package io.emeraldpay.dshackle.config class CacheConfig { + var requestsCacheEnabled = true + var redis: Redis? = null class Redis( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfigReader.kt index aedc2762..1f779231 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/CacheConfigReader.kt @@ -33,6 +33,9 @@ class CacheConfigReader : YamlConfigReader(), ConfigReader { override fun read(input: MappingNode?): CacheConfig? { return getMapping(input, "cache")?.let { node -> val config = CacheConfig() + getValueAsBool(node, "requests-cache-enabled")?.let { + config.requestsCacheEnabled = it + } getMapping(node, "redis")?.let { redisNode -> val redis = CacheConfig.Redis() val enabled = getValueAsBool(redisNode, "enabled") ?: true @@ -52,7 +55,7 @@ class CacheConfigReader : YamlConfigReader(), ConfigReader { config.redis = redis } } - if (config.redis == null) { + if (config.redis == null && config.requestsCacheEnabled) { return null } config diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 9a9f5c52..99aeda3c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -23,6 +23,7 @@ import io.emeraldpay.dshackle.BlockchainType import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.SilentException +import io.emeraldpay.dshackle.config.CacheConfig import io.emeraldpay.dshackle.quorum.CallQuorum import io.emeraldpay.dshackle.quorum.NotLaggingQuorum import io.emeraldpay.dshackle.quorum.QuorumReaderFactory @@ -55,7 +56,8 @@ import java.util.concurrent.atomic.AtomicInteger @Service open class NativeCall( private val multistreamHolder: MultistreamHolder, - private val signer: ResponseSigner + private val signer: ResponseSigner, + cacheConfig: CacheConfig ) { private val log = LoggerFactory.getLogger(NativeCall::class.java) @@ -63,6 +65,8 @@ open class NativeCall( private val nullValue: ByteArray = "null".toByteArray() + private val localRouterEnabled = cacheConfig.requestsCacheEnabled + var quorumReaderFactory: QuorumReaderFactory = QuorumReaderFactory.default() private val ethereumCallSelectors = EnumMap(Chain::class.java) @@ -121,9 +125,6 @@ open class NativeCall( result.setErrorMessage(error.message).setErrorCode(error.id) } } else { - if (it.result == null || it.result.isEmpty() || nullValue.contentEquals(it.result)) { - log.warn("Empty result [${it.result}] on building response, method ${it.ctx?.payload?.method}, params ${it.ctx?.payload?.params}") - } result.payload = ByteString.copyFrom(it.result) } if (it.nonce != null && it.signature != null) { @@ -273,7 +274,7 @@ open class NativeCall( if (method in DefaultEthereumMethods.newFilterMethods) CreateFilterDecorator() else NoneResultDecorator() fun fetch(ctx: ValidCallContext): Mono { - return ctx.upstream.getRoutedApi(ctx.matcher) + return ctx.upstream.getRoutedApi(localRouterEnabled) .flatMap { api -> api.read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce, ctx.forwardedSelector)) .flatMap(JsonRpcResponse::requireResult) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt index 3a9a082d..74038cff 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt @@ -148,7 +148,7 @@ abstract class Multistream( /** * Finds an API that leverages caches and other optimizations/transformations of the request. */ - abstract fun getRoutedApi(matcher: Selector.Matcher): Mono> + abstract fun getRoutedApi(localEnabled: Boolean): Mono> override fun getApi(): Reader { throw NotImplementedError("Immediate direct API is not implemented for Aggregated Upstream") 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 1cfe55a2..a36b4a1c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt @@ -106,7 +106,7 @@ open class BitcoinMultistream( .switchIfEmpty(Mono.error(Exception("No API available for $chain"))) } - override fun getRoutedApi(matcher: Selector.Matcher): Mono> { + override fun getRoutedApi(localEnabled: Boolean): Mono> { return Mono.just(callRouter) } 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 67f0cf5c..e741c99f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt @@ -154,8 +154,8 @@ open class EthereumMultistream( return this as T } - override fun getRoutedApi(matcher: Selector.Matcher): Mono> { - return Mono.just(LocalCallRouter(reader, getMethods(), getHead())) + override fun getRoutedApi(localEnabled: Boolean): Mono> { + return Mono.just(LocalCallRouter(reader, getMethods(), getHead(), localEnabled)) } override fun getSubscribe(): EthereumSubscribe { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouter.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouter.kt index 0201405c..045ff299 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouter.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouter.kt @@ -39,7 +39,8 @@ import java.math.BigInteger class LocalCallRouter( private val reader: EthereumCachingReader, private val methods: CallMethods, - private val head: Head + private val head: Head, + private val localEnabled: Boolean ) : Reader { companion object { @@ -56,7 +57,9 @@ class LocalCallRouter( return Mono.just(methods.executeHardcoded(key.method)) .map { JsonRpcResponse(it, null) } } - + if (!localEnabled) { + return Mono.empty() + } if (!methods.isCallable(key.method)) { return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method")) } 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 9d797483..f85d561b 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 @@ -147,8 +147,8 @@ open class EthereumPosMultiStream( return this as T } - override fun getRoutedApi(matcher: Selector.Matcher): Mono> { - return Mono.just(LocalCallRouter(reader, getMethods(), getHead())) + override fun getRoutedApi(localEnabled: Boolean): Mono> { + return Mono.just(LocalCallRouter(reader, getMethods(), getHead(), localEnabled)) } override fun getSubscribe(): EthereumSubscribe { diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/CacheConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/CacheConfigReaderSpec.groovy index 8b0901ed..555890c6 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/CacheConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/CacheConfigReaderSpec.groovy @@ -47,4 +47,14 @@ class CacheConfigReaderSpec extends Specification { //later may be not null if we support something else besides Redis act == null } + + def "Local read disabled"() { + setup: + def config = this.class.getClassLoader().getResourceAsStream("cache-local-router-disabled.yaml") + when: + def act = reader.read(config) + + then: + !act.requestsCacheEnabled + } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 9339d98a..b36e3e9b 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -21,10 +21,10 @@ import com.google.protobuf.ByteString import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.config.CacheConfig import io.emeraldpay.dshackle.quorum.QuorumReaderFactory import io.emeraldpay.dshackle.quorum.QuorumRpcReader import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.startup.ConfiguredUpstreams import io.emeraldpay.dshackle.test.MultistreamHolderMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.quorum.AlwaysQuorum @@ -52,14 +52,19 @@ class NativeCallSpec extends Specification { ObjectMapper objectMapper = Global.objectMapper - def nativeCall(MultistreamHolder upstreams = null, ResponseSigner signer = null) { + def nativeCall(MultistreamHolder upstreams = null, ResponseSigner signer = null, Boolean enableCache = true) { + if (upstreams == null) { upstreams = Stub(MultistreamHolder) } if (signer == null) { signer = Stub(ResponseSigner) } - new NativeCall(upstreams, signer) + + def config = new CacheConfig() + config.requestsCacheEnabled = enableCache + + new NativeCall(upstreams, signer, config) } def "Tries router first"() { diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy index fc8fe7b1..c7f84377 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy @@ -327,8 +327,9 @@ class MultistreamSpec extends Specification { super(chain, upstreams, caches) } + @NotNull @Override - Mono> getRoutedApi(@NotNull Selector.Matcher matcher) { + Mono> getRoutedApi(boolean localEnabled) { return null } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouterSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouterSpec.groovy index 12e6cb44..9072508e 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouterSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/LocalCallRouterSpec.groovy @@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.config.CacheConfig import io.emeraldpay.dshackle.reader.EmptyReader import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.EmptyHead @@ -29,7 +30,8 @@ class LocalCallRouterSpec extends Specification { ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM)) ), methods, - new EmptyHead() + new EmptyHead(), + true ) when: def act = router.read(new JsonRpcRequest("eth_coinbase", [])).block(Duration.ofSeconds(1)) @@ -47,7 +49,8 @@ class LocalCallRouterSpec extends Specification { ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM)) ), methods, - new EmptyHead() + new EmptyHead(), + true ) when: def act = router.read(new JsonRpcRequest("eth_getTransactionByHash", ["test"], 10)) @@ -69,7 +72,7 @@ class LocalCallRouterSpec extends Specification { } } def methods = new DefaultEthereumMethods(Chain.ETHEREUM) - def router = new LocalCallRouter(reader, methods, head) + def router = new LocalCallRouter(reader, methods, head, true) when: def act = router.getBlockByNumber(["latest", false]) @@ -95,7 +98,7 @@ class LocalCallRouterSpec extends Specification { } } def methods = new DefaultEthereumMethods(Chain.ETHEREUM) - def router = new LocalCallRouter(reader, methods, head) + def router = new LocalCallRouter(reader, methods, head, true) when: def act = router.getBlockByNumber(["earliest", false]) @@ -121,7 +124,7 @@ class LocalCallRouterSpec extends Specification { } } def methods = new DefaultEthereumMethods(Chain.ETHEREUM) - def router = new LocalCallRouter(reader, methods, head) + def router = new LocalCallRouter(reader, methods, head, true) when: def act = router.getBlockByNumber(["0x123ef", false]) diff --git a/src/test/resources/cache-local-router-disabled.yaml b/src/test/resources/cache-local-router-disabled.yaml new file mode 100644 index 00000000..a726008e --- /dev/null +++ b/src/test/resources/cache-local-router-disabled.yaml @@ -0,0 +1,2 @@ +cache: + requests-cache-enabled: false