From 5a154ac817f4b8e4f0d7e4b8f8b568c982bf3a06 Mon Sep 17 00:00:00 2001 From: Nikolay G Date: Wed, 22 Nov 2023 13:45:00 +0100 Subject: [PATCH] Integrate drpc-logs-oracle (#1096) (#335) * add oracle jar to build (#1096) * add oracle config (#1096) * add eth_getLogsEstimate (#1096) * add scheduler for oracle (#1096) * fix tests :[ (#1096) --- build.gradle | 10 ++- .../kotlin/io/emeraldpay/dshackle/Config.kt | 6 ++ .../emeraldpay/dshackle/config/IndexConfig.kt | 21 +++++ .../dshackle/config/IndexConfigReader.kt | 62 +++++++++++++ .../emeraldpay/dshackle/config/MainConfig.kt | 1 + .../dshackle/config/MainConfigReader.kt | 4 + .../config/context/MultistreamsConfig.kt | 18 +++- .../config/context/SchedulersConfig.kt | 5 ++ .../configure/BitcoinUpstreamCreator.kt | 4 +- .../configure/EthereumUpstreamCreator.kt | 4 +- .../configure/GenericUpstreamCreator.kt | 4 +- .../startup/configure/UpstreamCreator.kt | 6 +- .../dshackle/upstream/CallTargetsHolder.kt | 8 +- .../dshackle/upstream/LogsOracle.kt | 42 +++++++++ .../upstream/calls/DefaultEthereumMethods.kt | 12 +++ .../ethereum/EthereumChainSpecific.kt | 4 +- .../upstream/ethereum/EthereumLocalReader.kt | 83 ++++++++++++++++++ .../upstream/generic/AbstractChainSpecific.kt | 2 + .../upstream/generic/ChainSpecific.kt | 5 +- .../upstream/generic/GenericMultistream.kt | 12 ++- .../polkadot/PolkadotChainSpecific.kt | 2 + src/main/resources/LogsOracle.jar | Bin 0 -> 20955 bytes .../dshackle/rpc/NativeCallSpec.groovy | 12 +-- .../dshackle/test/GenericUpstreamMock.groovy | 2 +- .../test/MultistreamHolderMock.groovy | 5 +- .../dshackle/test/TestingCommons.groovy | 10 ++- .../dshackle/upstream/FilteredApisSpec.groovy | 2 +- .../dshackle/upstream/MultistreamSpec.groovy | 12 +-- .../calls/DefaultEthereumMethodsSpec.groovy | 18 ++-- .../calls/ManagedCallMethodsSpec.groovy | 10 +-- .../ethereum/EthereumCachingReaderSpec.groovy | 24 ++--- .../ethereum/EthereumLocalReaderSpec.groovy | 30 ++++--- .../config/reload/ReloadConfigTest.kt | 2 + 33 files changed, 369 insertions(+), 73 deletions(-) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfig.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfigReader.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/LogsOracle.kt create mode 100644 src/main/resources/LogsOracle.jar diff --git a/build.gradle b/build.gradle index d4f15101..76d750de 100644 --- a/build.gradle +++ b/build.gradle @@ -123,8 +123,16 @@ dependencies { implementation(variantOf(libs.netty.tcnative.boringssl) { classifier("linux-x86_64") }) implementation(variantOf(libs.netty.tcnative.boringssl) { classifier("osx-x86_64") }) implementation 'dshackle:foundation:1.0.0' + + implementation files('src/main/resources/LogsOracle.jar') } +// Enable 'Foreign Function & Memory API' (JEP 434) +// Drop after update on Java 21 +tasks.withType(JavaCompile) { options.compilerArgs += "--enable-preview" } +tasks.withType(Test) { jvmArgs += "--enable-preview" } +tasks.withType(JavaExec) { jvmArgs += "--enable-preview" } + compileKotlin { compilerOptions.jvmTarget.set(JvmTarget.JVM_20) } @@ -327,4 +335,4 @@ ktlint { } } -compileKotlin.dependsOn chainscodegen \ No newline at end of file +compileKotlin.dependsOn chainscodegen diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Config.kt b/src/main/kotlin/io/emeraldpay/dshackle/Config.kt index b33ab61b..da05fbca 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Config.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Config.kt @@ -21,6 +21,7 @@ import io.emeraldpay.dshackle.config.CacheConfig import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.CompressionConfig import io.emeraldpay.dshackle.config.HealthConfig +import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.config.MainConfigReader import io.emeraldpay.dshackle.config.MonitoringConfig @@ -130,6 +131,11 @@ open class Config( return mainConfig.cache ?: CacheConfig() } + @Bean + open fun indexConfig(@Autowired mainConfig: MainConfig): IndexConfig { + return mainConfig.index ?: IndexConfig() + } + @Bean open fun signatureConfig(@Autowired mainConfig: MainConfig): SignatureConfig { return mainConfig.signature ?: SignatureConfig() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfig.kt new file mode 100644 index 00000000..00202c6e --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfig.kt @@ -0,0 +1,21 @@ +package io.emeraldpay.dshackle.config + +import io.emeraldpay.dshackle.Chain + +class IndexConfig { + var items: HashMap = HashMap() + + class Index( + var rpc: String, + var store: String, + var ram_limit: Long?, + ) + + fun isChainEnabled(chain: Chain): Boolean { + return items.containsKey(chain) + } + + fun getByChain(chain: Chain): Index? { + return items.get(chain) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfigReader.kt new file mode 100644 index 00000000..1297670d --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/IndexConfigReader.kt @@ -0,0 +1,62 @@ +package io.emeraldpay.dshackle.config + +import io.emeraldpay.dshackle.Chain +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.foundation.YamlConfigReader +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory +import org.springframework.util.unit.DataSize +import org.yaml.snakeyaml.nodes.MappingNode + +class IndexConfigReader : YamlConfigReader() { + + companion object { + private val log = LoggerFactory.getLogger(IndexConfigReader::class.java) + } + + override fun read(input: MappingNode?): IndexConfig? { + return getList(input, "index")?.let { items -> + val config = IndexConfig() + + items.value.map { + val blockchainRaw = getValueAsString(it, "chain") + if (blockchainRaw == null || StringUtils.isEmpty(blockchainRaw) || Global.chainById(blockchainRaw) == Chain.UNSPECIFIED) { + throw InvalidConfigYamlException(filename, it.startMark, "Invalid blockchain or not specified") + } + + val blockchain = Global.chainById(blockchainRaw) + if (config.items.containsKey(blockchain)) { + throw InvalidConfigYamlException(filename, it.startMark, "Duplicated indexes") + } + + val rpc = getValueAsString(it, "rpc") + if (rpc == null || StringUtils.isEmpty(rpc)) { + throw InvalidConfigYamlException(filename, it.startMark, "Invalid rpc specified") + } + + val store = getValueAsString(it, "store") + if (store == null || StringUtils.isEmpty(store)) { + throw InvalidConfigYamlException(filename, it.startMark, "Invalid store directory or not specified") + } + + val limit = getMapping(it, "limit") + val ram_limit = limit?.let { + val raw = getValueAsString(limit, "ram") + if (raw == null || StringUtils.isEmpty(raw)) { + return null + } + + try { + DataSize.parse(raw).toBytes() + } catch (e: IllegalArgumentException) { + throw InvalidConfigYamlException(filename, it.startMark, "Invalid limit for index") + } + } + + config.items.put(blockchain, IndexConfig.Index(rpc, store, ram_limit)) + } + + config + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt index 9d6b125a..9146b376 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfig.kt @@ -21,6 +21,7 @@ class MainConfig { var tls: AuthConfig.ServerTlsAuth? = null var passthrough: Boolean = false var cache: CacheConfig? = null + var index: IndexConfig? = null var proxy: ProxyConfig? = null var upstreams: UpstreamsConfig? = null var tokens: TokensConfig? = null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt index 90cdef65..7522c562 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/MainConfigReader.kt @@ -29,6 +29,7 @@ class MainConfigReader( private val optionsReader = ChainOptionsReader() private val upstreamsConfigReader = UpstreamsConfigReader(fileResolver, optionsReader) private val cacheConfigReader = CacheConfigReader() + private val indexConfigReader = IndexConfigReader() private val tokensConfigReader = TokensConfigReader() private val monitoringConfigReader = MonitoringConfigReader() private val accessLogReader = AccessLogReader() @@ -63,6 +64,9 @@ class MainConfigReader( cacheConfigReader.read(input)?.let { config.cache = it } + indexConfigReader.read(input)?.let { + config.index = it + } tokensConfigReader.read(input)?.let { config.tokens = it } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/context/MultistreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/context/MultistreamsConfig.kt index daa38e6a..dc0a299d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/context/MultistreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/context/MultistreamsConfig.kt @@ -3,6 +3,7 @@ package io.emeraldpay.dshackle.config.context import io.emeraldpay.dshackle.BlockchainType.BITCOIN import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.cache.CachesFactory +import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.upstream.CallTargetsHolder import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream @@ -26,6 +27,9 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory) headScheduler: Scheduler, tracer: Tracer, multistreamEventsScheduler: Scheduler, + indexConfig: IndexConfig, + @Qualifier("logsOracleScheduler") + logsOracleScheduler: Scheduler, ): List { return Chain.entries .filterNot { it == Chain.UNSPECIFIED } @@ -33,7 +37,15 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory) if (chain.type == BITCOIN) { bitcoinMultistream(chain, cachesFactory, headScheduler, multistreamEventsScheduler) } else { - genericMultistream(chain, cachesFactory, headScheduler, tracer, multistreamEventsScheduler) + genericMultistream( + chain, + cachesFactory, + headScheduler, + tracer, + multistreamEventsScheduler, + indexConfig.getByChain(chain), + logsOracleScheduler, + ) } } } @@ -44,6 +56,8 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory) headScheduler: Scheduler, tracer: Tracer, multistreamEventsScheduler: Scheduler, + logsOracleConfig: IndexConfig.Index?, + logsOracleScheduler: Scheduler, ): Multistream { val name = "multi-$chain" val cs = ChainSpecificRegistry.resolve(chain) @@ -58,6 +72,8 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory) cs.makeCachingReaderBuilder(tracer), cs::localReaderBuilder, cs.subscriptionBuilder(headScheduler), + logsOracleConfig, + logsOracleScheduler, ).also { register(it, name) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt index ac2892e9..a5383564 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/context/SchedulersConfig.kt @@ -30,6 +30,11 @@ open class SchedulersConfig { return makeScheduler("head-scheduler", 4, monitoringConfig) } + @Bean + open fun logsOracleScheduler(monitoringConfig: MonitoringConfig): Scheduler { + return makeScheduler("logs-oracle", 4, monitoringConfig) + } + @Bean open fun multistreamEventsScheduler(monitoringConfig: MonitoringConfig): Scheduler { return makeScheduler("events-scheduler", 4, monitoringConfig) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/BitcoinUpstreamCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/BitcoinUpstreamCreator.kt index c6c5ccc3..4836420e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/BitcoinUpstreamCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/BitcoinUpstreamCreator.kt @@ -3,6 +3,7 @@ package io.emeraldpay.dshackle.startup.configure import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.config.ChainsConfig +import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.foundation.ChainOptions import io.emeraldpay.dshackle.startup.QuorumForLabels @@ -24,11 +25,12 @@ import java.util.concurrent.atomic.AtomicInteger @Component class BitcoinUpstreamCreator( chainsConfig: ChainsConfig, + indexConfig: IndexConfig, callTargets: CallTargetsHolder, private val genericConnectorFactoryCreator: ConnectorFactoryCreator, private val fileResolver: FileResolver, private val headScheduler: Scheduler, -) : UpstreamCreator(chainsConfig, callTargets) { +) : UpstreamCreator(chainsConfig, indexConfig, callTargets) { private var seq = AtomicInteger(0) override fun createUpstream( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt index 3f653db8..211a9219 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt @@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.startup.configure import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.ChainsConfig +import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.foundation.ChainOptions import io.emeraldpay.dshackle.upstream.CallTargetsHolder @@ -11,9 +12,10 @@ import org.springframework.stereotype.Component @Component class EthereumUpstreamCreator( chainsConfig: ChainsConfig, + indexConfig: IndexConfig, callTargets: CallTargetsHolder, genericConnectorFactoryCreator: ConnectorFactoryCreator, -) : GenericUpstreamCreator(chainsConfig, callTargets, genericConnectorFactoryCreator) { +) : GenericUpstreamCreator(chainsConfig, indexConfig, callTargets, genericConnectorFactoryCreator) { override fun createUpstream( upstreamsConfig: UpstreamsConfig.Upstream<*>, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt index 9d13939a..eff68248 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt @@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.startup.configure import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.ChainsConfig +import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.foundation.ChainOptions import io.emeraldpay.dshackle.startup.QuorumForLabels @@ -19,9 +20,10 @@ import kotlin.math.abs @Component open class GenericUpstreamCreator( chainsConfig: ChainsConfig, + indexConfig: IndexConfig, callTargets: CallTargetsHolder, private val genericConnectorFactoryCreator: ConnectorFactoryCreator, -) : UpstreamCreator(chainsConfig, callTargets) { +) : UpstreamCreator(chainsConfig, indexConfig, callTargets) { private val hashes: MutableMap = HashMap() override fun createUpstream( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/UpstreamCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/UpstreamCreator.kt index 8eba6746..786b9a88 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/UpstreamCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/UpstreamCreator.kt @@ -3,6 +3,7 @@ package io.emeraldpay.dshackle.startup.configure import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.ChainsConfig +import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.foundation.ChainOptions import io.emeraldpay.dshackle.upstream.CallTargetsHolder @@ -14,6 +15,7 @@ import org.slf4j.LoggerFactory abstract class UpstreamCreator( private val chainsConfig: ChainsConfig, + private val indexConfig: IndexConfig, private val callTargets: CallTargetsHolder, ) { protected val log: Logger = LoggerFactory.getLogger(this::class.java) @@ -45,7 +47,7 @@ abstract class UpstreamCreator( protected fun buildMethods(config: UpstreamsConfig.Upstream<*>, chain: Chain): CallMethods { return if (config.methods != null || config.methodGroups != null) { ManagedCallMethods( - delegate = callTargets.getDefaultMethods(chain), + delegate = callTargets.getDefaultMethods(chain, indexConfig.isChainEnabled(chain)), enabled = config.methods?.enabled?.map { it.name }?.toSet() ?: emptySet(), disabled = config.methods?.disabled?.map { it.name }?.toSet() ?: emptySet(), groupsEnabled = config.methodGroups?.enabled ?: emptySet(), @@ -61,7 +63,7 @@ abstract class UpstreamCreator( } } } else { - callTargets.getDefaultMethods(chain) + callTargets.getDefaultMethods(chain, indexConfig.isChainEnabled(chain)) } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt index 3f2e51af..56bddb33 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt @@ -18,14 +18,14 @@ import org.springframework.stereotype.Component class CallTargetsHolder { private val callTargets = HashMap() - fun getDefaultMethods(chain: Chain): CallMethods { - return callTargets[chain] ?: return setupDefaultMethods(chain) + fun getDefaultMethods(chain: Chain, hasLogsOracle: Boolean): CallMethods { + return callTargets[chain] ?: return setupDefaultMethods(chain, hasLogsOracle) } - private fun setupDefaultMethods(chain: Chain): CallMethods { + private fun setupDefaultMethods(chain: Chain, hasLogsOracle: Boolean): CallMethods { val created = when (chain.type) { BITCOIN -> DefaultBitcoinMethods() - ETHEREUM -> DefaultEthereumMethods(chain) + ETHEREUM -> DefaultEthereumMethods(chain, hasLogsOracle) STARKNET -> DefaultStarknetMethods(chain) POLKADOT -> DefaultPolkadotMethods() SOLANA -> DefaultSolanaMethods() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/LogsOracle.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/LogsOracle.kt new file mode 100644 index 00000000..5a14f9d3 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/LogsOracle.kt @@ -0,0 +1,42 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.config.IndexConfig +import org.slf4j.LoggerFactory +import reactor.core.Disposable +import reactor.core.publisher.Mono +import reactor.core.scheduler.Scheduler + +class LogsOracle( + private val config: IndexConfig.Index, + private val upstream: Multistream, + private val scheduler: Scheduler, +) { + + private val log = LoggerFactory.getLogger(LogsOracle::class.java) + + private var subscription: Disposable? = null + private val db = org.drpc.logsoracle.LogsOracle(config.store, config.store, config.ram_limit ?: 0L) + + fun start() { + subscription = upstream.getHead().getFlux() + .doOnError { t -> log.warn("Failed to subscribe head for oracle", t) } + .subscribe { println(it.height); db.UpdateHeight(it.height) } + } + + fun stop() { + db.close() + + subscription?.dispose() + subscription = null + } + + fun estimate( + fromBlock: Long?, + toBlock: Long?, + address: List, + topics: List>, + ): Mono { + return Mono.fromCallable { db.Query(fromBlock, toBlock, address, topics) } + .subscribeOn(scheduler) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt index 2f52c9c3..885b90ea 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt @@ -32,6 +32,7 @@ import io.emeraldpay.etherjar.rpc.RpcException */ class DefaultEthereumMethods( private val chain: Chain, + private val hasLogsOracle: Boolean = false, ) : CallMethods { private val version = "\"EmeraldDshackle/${Global.version}\"" @@ -137,6 +138,7 @@ class DefaultEthereumMethods( specialMethods + headVerifiedMethods - chainUnsupportedMethods(chain) + + getDrpcVendorMethods(chain) + getChainSpecificMethods(chain) } @@ -148,6 +150,7 @@ class DefaultEthereumMethods( firstValueMethods.contains(method) -> AlwaysQuorum() anyResponseMethods.contains(method) -> NotLaggingQuorum(4) headVerifiedMethods.contains(method) -> NotLaggingQuorum(1) + getDrpcVendorMethods(chain).contains(method) -> AlwaysQuorum() possibleNotIndexedMethods.contains(method) -> NotNullQuorum() specialMethods.contains(method) -> { when (method) { @@ -174,6 +177,15 @@ class DefaultEthereumMethods( } } + private fun getDrpcVendorMethods(chain: Chain): List { + val supported = mutableListOf() + + // Currently tested on eth mainnet only, should potentially work for all compatible ones. + if (chain == Chain.ETHEREUM__MAINNET && hasLogsOracle) { supported.add("drpc_getLogsEstimate") } + + return supported + } + private fun getChainSpecificMethods(chain: Chain): List { return when (chain) { Chain.OPTIMISM__MAINNET, Chain.OPTIMISM__GOERLI -> listOf( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt index 648d9dd7..58201b99 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt @@ -11,6 +11,7 @@ import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.LabelsDetector +import io.emeraldpay.dshackle.upstream.LogsOracle import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamValidator @@ -48,8 +49,9 @@ object EthereumChainSpecific : AbstractPollChainSpecific() { cachingReader: CachingReader, methods: CallMethods, head: Head, + logsOracle: LogsOracle?, ): Mono { - return Mono.just(EthereumLocalReader(cachingReader as EthereumCachingReader, methods, head)) + return Mono.just(EthereumLocalReader(cachingReader as EthereumCachingReader, methods, head, logsOracle)) } override fun subscriptionBuilder(headScheduler: Scheduler): (Multistream) -> EgressSubscription { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt index 26212358..24c4a2cb 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt @@ -20,6 +20,7 @@ import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxId import io.emeraldpay.dshackle.reader.JsonRpcReader import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.LogsOracle import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse @@ -41,6 +42,7 @@ class EthereumLocalReader( private val reader: EthereumCachingReader, private val methods: CallMethods, private val head: Head, + private val logsOracle: LogsOracle?, ) : JsonRpcReader { override fun read(key: JsonRpcRequest): Mono { @@ -120,6 +122,9 @@ class EthereumLocalReader( .read(hash) .map { it.data to it.upstreamId } } + method == "drpc_getLogsEstimate" -> { + getLogsEstimate(params) + } else -> null } } @@ -167,4 +172,82 @@ class EthereumLocalReader( return reader.blocksByHeightAsCont() .read(number).map { it.data.json!! to it.upstreamId } } + + fun getLogsEstimate(params: List): Mono>? { + if (logsOracle == null) { + throw NotImplementedError() + } + if (params.size != 1 || params[0] == null) { + throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Must provide 1 parameters") + } + + val req = params[0] as LinkedHashMap + + val fromBlock = try { parseBlockRef(req.get("fromBlock") as String?) } catch (_: IllegalArgumentException) { + throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Invalid 'fromBlock' parameter") + } + val toBlock = try { parseBlockRef(req.get("toBlock") as String?) } catch (_: IllegalArgumentException) { + throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Invalid 'toBlock' parameter") + } + val address: List = try { + val it = req.get("address") ?: listOf() + + if (it is String) { + listOf(it) + } else { + it as List + } + } catch (_: Exception) { + throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Invalid 'address' parameter") + } + val topics: List> = try { + val tpcs = req.get("topics")?.let { it as List } ?: listOf() + if (tpcs.size > 4) { + throw IllegalArgumentException() + } + + tpcs.map { + if (it is String) { + listOf(it) + } else { + it as List + } + } + } catch (_: Exception) { + throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Invalid 'topics' parameter") + } + + return logsOracle.estimate(fromBlock, toBlock, address, topics) + .map { it.toString().toByteArray() to null } + } + + private fun parseBlockRef(blockRef: String?): Long? { + when { + blockRef == null -> { + return null + } + blockRef == "latest" -> { + return head.getCurrentHeight() ?: return null + } + blockRef == "earliest" -> { + return 0 + } + blockRef == "finalized" || blockRef == "safe" || blockRef == "pending" -> { + return null + } + blockRef.startsWith("0x") -> { + val quantity = HexQuantity.from(blockRef) ?: throw IllegalArgumentException() + return quantity.value.let { + if (it < BigInteger.valueOf(Long.MAX_VALUE) && it >= BigInteger.ZERO) { + it.toLong() + } else { + throw IllegalArgumentException() + } + } + } + else -> { + throw IllegalArgumentException() + } + } + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt index 840e2477..fba52c94 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt @@ -12,6 +12,7 @@ import io.emeraldpay.dshackle.upstream.EmptyEgressSubscription import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.LabelsDetector +import io.emeraldpay.dshackle.upstream.LogsOracle import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.NoIngressSubscription import io.emeraldpay.dshackle.upstream.NoopCachingReader @@ -31,6 +32,7 @@ abstract class AbstractChainSpecific : ChainSpecific { cachingReader: CachingReader, methods: CallMethods, head: Head, + logsOracle: LogsOracle?, ): Mono { return Mono.just(LocalReader(methods)) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt index c613c88a..9249f660 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt @@ -17,6 +17,7 @@ import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.LabelsDetector +import io.emeraldpay.dshackle.upstream.LogsOracle import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamValidator @@ -34,7 +35,7 @@ import reactor.core.publisher.Mono import reactor.core.scheduler.Scheduler typealias SubscriptionBuilder = (Multistream) -> EgressSubscription -typealias LocalReaderBuilder = (CachingReader, CallMethods, Head) -> Mono +typealias LocalReaderBuilder = (CachingReader, CallMethods, Head, LogsOracle?) -> Mono typealias CachingReaderBuilder = (Multistream, Caches, Factory) -> CachingReader interface ChainSpecific { @@ -46,7 +47,7 @@ interface ChainSpecific { fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest - fun localReaderBuilder(cachingReader: CachingReader, methods: CallMethods, head: Head): Mono + fun localReaderBuilder(cachingReader: CachingReader, methods: CallMethods, head: Head, logsOracle: LogsOracle?): Mono fun subscriptionBuilder(headScheduler: Scheduler): (Multistream) -> EgressSubscription diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt index 5e9b03e8..c83cf6bc 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt @@ -19,6 +19,7 @@ package io.emeraldpay.dshackle.upstream.generic import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.reader.JsonRpcReader @@ -31,6 +32,7 @@ import io.emeraldpay.dshackle.upstream.EmptyHead import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.Lifecycle +import io.emeraldpay.dshackle.upstream.LogsOracle import io.emeraldpay.dshackle.upstream.MergedHead import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Selector @@ -58,6 +60,8 @@ open class GenericMultistream( cachingReaderBuilder: CachingReaderBuilder, private val localReaderBuilder: LocalReaderBuilder, private val subscriptionBuilder: SubscriptionBuilder, + logsOracleConfig: IndexConfig.Index? = null, + private val logsOracleScheduler: Scheduler, ) : Multistream(chain, caches, callSelector, multistreamEventsScheduler) { private val cachingReader = cachingReaderBuilder(this, caches, getMethodsFactory()) @@ -76,6 +80,10 @@ open class GenericMultistream( headScheduler, ) + private val logsOracle: LogsOracle? = logsOracleConfig?.let { + LogsOracle(logsOracleConfig, this, logsOracleScheduler) + } + private var subscription: EgressSubscription = subscriptionBuilder(this) private val filteredHeads: MutableMap = @@ -86,12 +94,14 @@ open class GenericMultistream( head.start() onHeadUpdated(head) cachingReader.start() + logsOracle?.start() } override fun stop() { super.stop() cachingReader.stop() filteredHeads.clear() + logsOracle?.stop() } override fun addHead(upstream: Upstream) { @@ -181,7 +191,7 @@ open class GenericMultistream( } override fun getLocalReader(): Mono { - return localReaderBuilder(cachingReader, getMethods(), getHead()) + return localReaderBuilder(cachingReader, getMethods(), getHead(), logsOracle) } override fun getEgressSubscription(): EgressSubscription { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt index 48d05ae9..1bdb96d5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt @@ -14,6 +14,7 @@ import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.LabelsDetector +import io.emeraldpay.dshackle.upstream.LogsOracle import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.NoopCachingReader import io.emeraldpay.dshackle.upstream.Upstream @@ -74,6 +75,7 @@ object PolkadotChainSpecific : AbstractPollChainSpecific() { cachingReader: CachingReader, methods: CallMethods, head: Head, + logsOracle: LogsOracle?, ): Mono { return Mono.just(LocalReader(methods)) } diff --git a/src/main/resources/LogsOracle.jar b/src/main/resources/LogsOracle.jar new file mode 100644 index 0000000000000000000000000000000000000000..7194f50dd11dc3b5a93c69d266da68043db3a8be GIT binary patch literal 20955 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 literal 0 HcmV?d00001 diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 06fb63bb..04e36064 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -398,7 +398,7 @@ class NativeCallSpec extends Specification { def "Prepare call adds height selector for not-lagging quorum"() { setup: def methods = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), ["foo_bar"] as Set, [] as Set, [] as Set, [] as Set ) methods.setQuorum("foo_bar", "not_lagging") @@ -439,7 +439,7 @@ class NativeCallSpec extends Specification { def "Prepare call adds decorator for eth_newFilter"() { setup: def methods = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), ["eth_newFilter"] as Set, [] as Set, [] as Set, [] as Set ) methods.setQuorum("eth_newFilter", "always") @@ -470,7 +470,7 @@ class NativeCallSpec extends Specification { def "Prepare call adds decorator for eth_getFilterChanges"() { setup: def methods = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), ["eth_getFilterChanges"] as Set, [] as Set, [] as Set, [] as Set ) def multistream = new MultistreamHolderMock.EthereumMultistreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.upstream()) @@ -500,7 +500,7 @@ class NativeCallSpec extends Specification { def "Prepare call adds decorator for eth_uninstallFilter"() { setup: def methods = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), ["eth_uninstallFilter"] as Set, [] as Set, [] as Set, [] as Set ) def multistream = new MultistreamHolderMock.EthereumMultistreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.upstream()) @@ -600,7 +600,7 @@ class NativeCallSpec extends Specification { } def quorum = new AlwaysQuorum() def methods = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), [] as Set, [] as Set, ["filter"] as Set, [] as Set ) def multistream = new MultistreamHolderMock.EthereumMultistreamMock(Chain.ETHEREUM__MAINNET, new ArrayList()) @@ -636,7 +636,7 @@ class NativeCallSpec extends Specification { } def quorum = new AlwaysQuorum() def methods = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), [] as Set, [] as Set, ["filter"] as Set, [] as Set ) def multistream = new MultistreamHolderMock.EthereumMultistreamMock(Chain.ETHEREUM__MAINNET, new ArrayList()) diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy index 56da1932..1ce79a88 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy @@ -36,7 +36,7 @@ class GenericUpstreamMock extends GenericUpstream { static CallMethods allMethods() { new AggregatedCallMethods([ - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), new DefaultBitcoinMethods(), new DirectCallMethods(["eth_test"]) ]) diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy index 5cf1e836..d88a8403 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy @@ -51,7 +51,8 @@ class MultistreamHolderMock implements MultistreamHolder { Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()) + io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), + null, Schedulers.immediate() ) upstreams[chain].addUpstream(up) } else { @@ -104,7 +105,7 @@ class MultistreamHolderMock implements MultistreamHolder { super(chain, Schedulers.immediate(), null, upstreams, caches, Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(new BraveTracer(null, null, null)), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), null, Schedulers.immediate()) } EthereumMultistreamMock(@NotNull Chain chain, @NotNull List upstreams) { diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy index 3afa1302..288cddf2 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy @@ -98,6 +98,8 @@ class TestingCommons { EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), + null, + Schedulers.immediate() ).tap { it.processUpstreamsEvents( new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up, UpstreamChangeEvent.ChangeType.ADDED) @@ -124,14 +126,18 @@ class TestingCommons { return new GenericMultistream(chain, Schedulers.immediate(), null, [], emptyCaches().getCaches(chain), Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), + null, + Schedulers.immediate()) } static Multistream multistreamClassicWithoutUpstreams(Chain chain) { return new GenericMultistream(chain, Schedulers.immediate(), null, [], emptyCaches().getCaches(chain), Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), + null, + Schedulers.immediate()) } static FileResolver fileResolver() { diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy index 3a629d4e..056993a7 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy @@ -39,7 +39,7 @@ import static java.util.List.of class FilteredApisSpec extends Specification { - def ethereumTargets = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def ethereumTargets = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) def "Verifies labels"() { setup: diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy index c57129eb..594b00c7 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy @@ -59,7 +59,7 @@ class MultistreamSpec extends Specification { Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), null, Schedulers.immediate()) when: aggr.onUpstreamsUpdated() def act = aggr.getMethods() @@ -194,7 +194,7 @@ class MultistreamSpec extends Specification { Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), null, Schedulers.immediate()) expect: multistream.getHead(new Selector.LabelMatcher("provider", ["internal"])).is(up1.ethereumHeadMock) @@ -267,7 +267,7 @@ class MultistreamSpec extends Specification { Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), null, Schedulers.immediate()) when: ms.processUpstreamsEvents( new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED) @@ -298,7 +298,7 @@ class MultistreamSpec extends Specification { Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), null, Schedulers.immediate()) def head1 = createBlock(250, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448401b") def head2 = createBlock(270, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448402b") def head3 = createBlock(100, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448412b") @@ -333,7 +333,7 @@ class MultistreamSpec extends Specification { Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), null, Schedulers.immediate()) multistream.processUpstreamsEvents( new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED) ) @@ -371,7 +371,7 @@ class MultistreamSpec extends Specification { Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()), EthereumChainSpecific.INSTANCE.&localReaderBuilder, - StarknetChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())) + StarknetChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()), null, Schedulers.immediate()) } @NotNull diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethodsSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethodsSpec.groovy index f952c5d3..ddf3b128 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethodsSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethodsSpec.groovy @@ -7,7 +7,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "eth_chainId is available"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) when: def act = methods.isAvailable("eth_chainId") then: @@ -16,7 +16,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "eth_chainId is hardcoded"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) when: def act = methods.isHardcoded("eth_chainId") then: @@ -25,7 +25,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "eth_chainId is not callable"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) when: def act = methods.isCallable("eth_chainId") then: @@ -34,7 +34,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "Provides hardcoded correct chainId"() { expect: - new String(new DefaultEthereumMethods(chain).executeHardcoded("eth_chainId")) == id + new String(new DefaultEthereumMethods(chain, false).executeHardcoded("eth_chainId")) == id where: chain | id Chain.ETHEREUM__MAINNET | '"0x1"' @@ -44,7 +44,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "Optimism chain unsupported methods"() { setup: - def methods = new DefaultEthereumMethods(Chain.OPTIMISM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.OPTIMISM__MAINNET, false) when: def acc = methods.isAvailable("eth_getAccounts") def trans = methods.isAvailable("eth_sendTransaction") @@ -55,7 +55,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "Has supported specific methods"() { expect: - new DefaultEthereumMethods(chain).getSupportedMethods().containsAll(methods) + new DefaultEthereumMethods(chain, false).getSupportedMethods().containsAll(methods) where: chain | methods Chain.POLYGON__MAINNET | ["bor_getAuthor", @@ -69,7 +69,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "Has no filter methods by default"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) when: def act = methods.getSupportedMethods().findAll { it.containsIgnoreCase("filter") } then: @@ -78,7 +78,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "Has no trace methods by default"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) when: def act = methods.getSupportedMethods().findAll { it.containsIgnoreCase("trace") } then: @@ -87,7 +87,7 @@ class DefaultEthereumMethodsSpec extends Specification { def "Default eth methods are available"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) expect: methods.isAvailable(method) where: diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethodsSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethodsSpec.groovy index fa4c1de4..a5350d19 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethodsSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/ManagedCallMethodsSpec.groovy @@ -94,7 +94,7 @@ class ManagedCallMethodsSpec extends Specification { def "Use custom quorum if provided"() { setup: def managed = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), ["eth_test", "eth_foo", "eth_bar"] as Set, [] as Set, [] as Set, @@ -120,7 +120,7 @@ class ManagedCallMethodsSpec extends Specification { def "Doesn't reuse same instance"() { def managed = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), ["eth_test"] as Set, [] as Set, [] as Set, @@ -145,7 +145,7 @@ class ManagedCallMethodsSpec extends Specification { def "Test enable method group"() { setup: def managed = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), [] as Set, [] as Set, ["filter"] as Set, @@ -169,7 +169,7 @@ class ManagedCallMethodsSpec extends Specification { def "Test enable method group minus one"() { setup: def managed = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), [] as Set, ["eth_newPendingTransactionFilter"] as Set, ["filter"] as Set, @@ -193,7 +193,7 @@ class ManagedCallMethodsSpec extends Specification { def "Test disabled group not disable enabled method"() { setup: def managed = new ManagedCallMethods( - new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET), + new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false), ["eth_newPendingTransactionFilter"] as Set, [] as Set, [] as Set, diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy index ff4d1704..43d541ce 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy @@ -43,7 +43,7 @@ class EthereumDirectReaderSpec extends Specification { transactions = [] } def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -70,7 +70,7 @@ class EthereumDirectReaderSpec extends Specification { def "Produce empty result on non-existing block"() { setup: def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -103,7 +103,7 @@ class EthereumDirectReaderSpec extends Specification { transactions = [] } def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -136,7 +136,7 @@ class EthereumDirectReaderSpec extends Specification { blockHash = BlockHash.from(hash1) } def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -169,7 +169,7 @@ class EthereumDirectReaderSpec extends Specification { blockHash = BlockHash.from(hash1) } def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -199,7 +199,7 @@ class EthereumDirectReaderSpec extends Specification { blockHash = BlockHash.from(hash1) } def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } def caches = Mock(Caches) { // note that the Caches needs a Height value, otherwise it's not cached @@ -228,7 +228,7 @@ class EthereumDirectReaderSpec extends Specification { def "Produce empty on non-existing tx"() { setup: def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -258,7 +258,7 @@ class EthereumDirectReaderSpec extends Specification { } } def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -289,7 +289,7 @@ class EthereumDirectReaderSpec extends Specification { } } def calls = Mock(Factory) { - 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() @@ -323,7 +323,7 @@ class EthereumDirectReaderSpec extends Specification { transactions = [] } def calls = Mock(Factory) { - 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } def result = Mono.just( new RpcReader.Result( @@ -363,7 +363,7 @@ class EthereumDirectReaderSpec extends Specification { transactions = [] } def calls = Mock(Factory) { - 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } def result = Mono.just( new RpcReader.Result( @@ -400,7 +400,7 @@ class EthereumDirectReaderSpec extends Specification { } } def calls = Mock(Factory) { - 4 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + 4 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } EthereumDirectReader reader = new EthereumDirectReader( up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy index 0807ce58..f4fd4602 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy @@ -21,16 +21,17 @@ class EthereumLocalReaderSpec extends Specification { def "Calls hardcoded"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) def router = new EthereumLocalReader( new EthereumCachingReader( TestingCommons.multistream(TestingCommons.api()), Caches.default(), - ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)), + ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false)), TestingCommons.tracerMock() ), methods, - new EmptyHead() + new EmptyHead(), + null ) when: def act = router.read(new JsonRpcRequest("eth_coinbase", [])).block(Duration.ofSeconds(1)) @@ -40,16 +41,17 @@ class EthereumLocalReaderSpec extends Specification { def "Returns empty if nonce set"() { setup: - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) def router = new EthereumLocalReader( new EthereumCachingReader( TestingCommons.multistream(TestingCommons.api()), Caches.default(), - ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)), + ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false)), TestingCommons.tracerMock() ), methods, - new EmptyHead() + new EmptyHead(), + null ) when: def act = router.read(new JsonRpcRequest("eth_getTransactionByHash", ["test"], 10)) @@ -72,8 +74,8 @@ class EthereumLocalReaderSpec extends Specification { ) } } - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) - def router = new EthereumLocalReader(reader, methods, head) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) + def router = new EthereumLocalReader(reader, methods, head, null) when: def act = router.getBlockByNumber(["latest", false]) @@ -100,8 +102,8 @@ class EthereumLocalReaderSpec extends Specification { ) } } - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) - def router = new EthereumLocalReader(reader, methods, head) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) + def router = new EthereumLocalReader(reader, methods, head, null) when: def act = router.getBlockByNumber(["earliest", false]) @@ -128,8 +130,8 @@ class EthereumLocalReaderSpec extends Specification { ) } } - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) - def router = new EthereumLocalReader(reader, methods, head) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) + def router = new EthereumLocalReader(reader, methods, head, null) when: def act = router.getBlockByNumber(["0x123ef", false]) @@ -152,8 +154,8 @@ class EthereumLocalReaderSpec extends Specification { _ * txByHashAsCont() >> new EmptyReader<>() _ * blocksByHeightAsCont() >> new EmptyReader<>() } - def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) - def router = new EthereumLocalReader(reader, methods, head) + def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) + def router = new EthereumLocalReader(reader, methods, head, null) when: def act = router.getBlockByNumber(["0x0", true]) diff --git a/src/test/kotlin/io/emeraldpay/dshackle/config/reload/ReloadConfigTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/config/reload/ReloadConfigTest.kt index 4750c1ab..13af79d6 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/config/reload/ReloadConfigTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/config/reload/ReloadConfigTest.kt @@ -179,6 +179,8 @@ class ReloadConfigTest { cs.makeCachingReaderBuilder(mock()), cs::localReaderBuilder, cs.subscriptionBuilder(Schedulers.boundedElastic()), + null, + Schedulers.fromExecutor(Executors.newFixedThreadPool(6)), ) }