diff --git a/emerald-grpc b/emerald-grpc index 1934aae8f..263a8cf15 160000 --- a/emerald-grpc +++ b/emerald-grpc @@ -1 +1 @@ -Subproject commit 1934aae8f950109af8b5712c3e25fa59d05dd2b4 +Subproject commit 263a8cf15c333bf942f368dc5947f692bc4bcbfb diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt index 7628db642..663d9c0e6 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/ChainEventMapper.kt @@ -146,6 +146,8 @@ class ChainEventMapper { LowerBoundType.BLOCK -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOCK LowerBoundType.TX -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_TX LowerBoundType.LOGS -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_LOGS + LowerBoundType.TRACE -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_TRACE + LowerBoundType.DEBUG -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_DEBUG } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt index c8e2a4ac1..35fed383a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt @@ -106,6 +106,8 @@ class StreamHead( LowerBoundType.BLOCK -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_BLOCK LowerBoundType.TX -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_TX LowerBoundType.LOGS -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_LOGS + LowerBoundType.TRACE -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_TRACE + LowerBoundType.DEBUG -> BlockchainOuterClass.LowerBoundType.LOWER_BOUND_DEBUG } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumDebugTraceLowerBoundErrorHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumDebugTraceLowerBoundErrorHandler.kt new file mode 100644 index 000000000..56e9f22b5 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumDebugTraceLowerBoundErrorHandler.kt @@ -0,0 +1,63 @@ +package io.emeraldpay.dshackle.upstream.error + +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.ethereum.EthereumLowerBoundStateDetector.Companion.stateErrors +import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType + +object EthereumDebugTraceLowerBoundErrorHandler : EthereumLowerBoundErrorHandler() { + private val zeroTagIndexMethods = setOf( + "trace_block", + "arbtrace_block", + "debug_traceBlockByNumber", + ) + private val firstTagIndexMethods = setOf( + "trace_callMany", + "arbtrace_callMany", + "debug_traceCall", + ) + private val secondTagIndexMethods = setOf( + "trace_call", + "arbtrace_call", + ) + + private val applicableMethods = zeroTagIndexMethods + firstTagIndexMethods + secondTagIndexMethods + + private val errors = stateErrors + .plus( + setOf( + "historical state not available", + ), + ) + private val errorRegexp = Regex("block .* not found") + + override fun handle(upstream: Upstream, request: ChainRequest, errorMessage: String?) { + val type = if (request.method.startsWith("debug")) LowerBoundType.DEBUG else LowerBoundType.TRACE + try { + if (canHandle(request, errorMessage)) { + parseTagParam(request, tagIndex(request.method))?.let { + upstream.updateLowerBound(it, type) + } + } + } catch (e: RuntimeException) { + log.warn("Couldn't update the {} lower bound of {}, reason - {}", type, upstream.getId(), e.message) + } + } + + override fun canHandle(request: ChainRequest, errorMessage: String?): Boolean { + return (errors.any { errorMessage?.contains(it) ?: false } || (errorMessage?.matches(errorRegexp) ?: false)) && + applicableMethods.contains(request.method) + } + + override fun tagIndex(method: String): Int { + return if (firstTagIndexMethods.contains(method)) { + 1 + } else if (secondTagIndexMethods.contains(method)) { + 2 + } else if (zeroTagIndexMethods.contains(method)) { + 0 + } else { + -1 + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumLowerBoundErrorHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumLowerBoundErrorHandler.kt new file mode 100644 index 000000000..e20fe359e --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumLowerBoundErrorHandler.kt @@ -0,0 +1,24 @@ +package io.emeraldpay.dshackle.upstream.error + +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.rpcclient.ListParams +import org.slf4j.LoggerFactory + +abstract class EthereumLowerBoundErrorHandler : ErrorHandler { + protected val log = LoggerFactory.getLogger(this::class.java) + + protected fun parseTagParam(request: ChainRequest, tagIndex: Int): Long? { + if (tagIndex != -1 && request.params is ListParams) { + val params = request.params.list + if (params.size >= tagIndex) { + val tag = params[tagIndex] + if (tag is String && tag.startsWith("0x")) { + return tag.substring(2).toLong(16) + } + } + } + return null + } + + protected abstract fun tagIndex(method: String): Int +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandler.kt index 064d03a7d..41297fce4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandler.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandler.kt @@ -4,12 +4,8 @@ import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumLowerBoundStateDetector.Companion.stateErrors import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType -import io.emeraldpay.dshackle.upstream.rpcclient.ListParams -import org.slf4j.LoggerFactory - -object EthereumStateLowerBoundErrorHandler : ErrorHandler { - private val log = LoggerFactory.getLogger(this::class.java) +object EthereumStateLowerBoundErrorHandler : EthereumLowerBoundErrorHandler() { private val firstTagIndexMethods = setOf( "eth_call", "debug_traceCall", @@ -41,20 +37,7 @@ object EthereumStateLowerBoundErrorHandler : ErrorHandler { return stateErrors.any { errorMessage?.contains(it) ?: false } && applicableMethods.contains(request.method) } - private fun parseTagParam(request: ChainRequest, tagIndex: Int): Long? { - if (tagIndex != -1 && request.params is ListParams) { - val params = request.params.list - if (params.size >= tagIndex) { - val tag = params[tagIndex] - if (tag is String && tag.startsWith("0x")) { - return tag.substring(2).toLong(16) - } - } - } - return null - } - - private fun tagIndex(method: String): Int { + override fun tagIndex(method: String): Int { return if (firstTagIndexMethods.contains(method)) { 1 } else if (secondTagIndexMethods.contains(method)) { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandler.kt index b1293036e..8256f8dd7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandler.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandler.kt @@ -16,6 +16,7 @@ interface ErrorHandler { object UpstreamErrorHandler { private val errorHandlers = listOf( EthereumStateLowerBoundErrorHandler, + EthereumDebugTraceLowerBoundErrorHandler, ) private val errorHandlerExecutor = Executors.newFixedThreadPool( 2 * SchedulersConfig.threadsMultiplier, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundStateDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundStateDetector.kt index 3b785b956..1ac749dd8 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundStateDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundStateDetector.kt @@ -69,10 +69,20 @@ class EthereumLowerBoundStateDetector( throw IllegalStateException("No state data") } } + }.flatMap { + Flux.just(it, lowerBoundFromState(it, LowerBoundType.DEBUG), lowerBoundFromState(it, LowerBoundType.TRACE)) } } + private fun lowerBoundFromState(stateLowerBoundData: LowerBoundData, newType: LowerBoundType): LowerBoundData { + val currentBound = lowerBounds.getLastBound(newType) + if (currentBound == null || stateLowerBoundData.lowerBound >= currentBound.lowerBound) { + return stateLowerBoundData.copy(type = newType) + } + return LowerBoundData(currentBound.lowerBound, newType) + } + override fun types(): Set { - return setOf(LowerBoundType.STATE) + return setOf(LowerBoundType.STATE, LowerBoundType.DEBUG, LowerBoundType.TRACE) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt index c297469a8..3e82d9ad2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundData.kt @@ -20,7 +20,7 @@ data class LowerBoundData( } enum class LowerBoundType { - UNKNOWN, STATE, SLOT, BLOCK, TX, LOGS + UNKNOWN, STATE, SLOT, BLOCK, TX, LOGS, TRACE, DEBUG } fun BlockchainOuterClass.LowerBoundType.fromProtoType(): LowerBoundType { @@ -32,5 +32,7 @@ fun BlockchainOuterClass.LowerBoundType.fromProtoType(): LowerBoundType { BlockchainOuterClass.LowerBoundType.UNRECOGNIZED -> LowerBoundType.UNKNOWN BlockchainOuterClass.LowerBoundType.LOWER_BOUND_TX -> LowerBoundType.TX BlockchainOuterClass.LowerBoundType.LOWER_BOUND_LOGS -> LowerBoundType.LOGS + BlockchainOuterClass.LowerBoundType.LOWER_BOUND_TRACE -> LowerBoundType.TRACE + BlockchainOuterClass.LowerBoundType.LOWER_BOUND_DEBUG -> LowerBoundType.DEBUG } } diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumDebugTraceLowerBoundErrorHandlerTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumDebugTraceLowerBoundErrorHandlerTest.kt new file mode 100644 index 000000000..499ef017e --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumDebugTraceLowerBoundErrorHandlerTest.kt @@ -0,0 +1,52 @@ +package io.emeraldpay.dshackle.upstream.error + +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType +import io.emeraldpay.dshackle.upstream.rpcclient.ListParams +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.of +import org.junit.jupiter.params.provider.MethodSource +import org.mockito.Mockito.mock +import org.mockito.kotlin.verify + +class EthereumDebugTraceLowerBoundErrorHandlerTest { + + @ParameterizedTest + @MethodSource("requests") + fun `update lower bound`(request: ChainRequest, type: LowerBoundType) { + val upstream = mock() + val handler = EthereumDebugTraceLowerBoundErrorHandler + + handler.handle(upstream, request, "missing trie node d5648cc9aef48154159d53800f2f") + + verify(upstream).updateLowerBound(213229736, type) + } + + @Test + fun `update lower bound base on regexp`() { + val upstream = mock() + val handler = EthereumDebugTraceLowerBoundErrorHandler + + handler.handle(upstream, ChainRequest("trace_block", ListParams("0xCB5A0A8")), "block #1 not found") + + verify(upstream).updateLowerBound(213229736, LowerBoundType.TRACE) + } + + companion object { + @JvmStatic + fun requests(): List = + listOf( + of(ChainRequest("trace_block", ListParams("0xCB5A0A8")), LowerBoundType.TRACE), + of(ChainRequest("arbtrace_block", ListParams("0xCB5A0A8")), LowerBoundType.TRACE), + of(ChainRequest("debug_traceBlockByNumber", ListParams("0xCB5A0A8", mapOf("tracer" to "tracer"))), LowerBoundType.DEBUG), + of(ChainRequest("trace_callMany", ListParams(arrayOf(mapOf("val" to 1)), "0xCB5A0A8")), LowerBoundType.TRACE), + of(ChainRequest("arbtrace_callMany", ListParams(arrayOf(mapOf("val" to 1)), "0xCB5A0A8")), LowerBoundType.TRACE), + of(ChainRequest("debug_traceCall", ListParams(mapOf("val" to 1), "0xCB5A0A8", mapOf("val" to 1))), LowerBoundType.DEBUG), + of(ChainRequest("trace_call", ListParams(mapOf("val" to 1), arrayOf(mapOf("val" to 1)), "0xCB5A0A8")), LowerBoundType.TRACE), + of(ChainRequest("arbtrace_call", ListParams(mapOf("val" to 1), arrayOf(mapOf("val" to 1)), "0xCB5A0A8")), LowerBoundType.TRACE), + ) + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/RecursiveLowerBoundServiceTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/RecursiveLowerBoundServiceTest.kt index 3a0a03ed6..7c4b2cd41 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/RecursiveLowerBoundServiceTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/RecursiveLowerBoundServiceTest.kt @@ -86,6 +86,8 @@ class RecursiveLowerBoundServiceTest { .expectSubscription() .expectNoEvent(Duration.ofSeconds(15)) .expectNextMatches { it.lowerBound == 17964844L && it.type == LowerBoundType.STATE } + .expectNextMatches { it.lowerBound == 17964844L && it.type == LowerBoundType.DEBUG } + .expectNextMatches { it.lowerBound == 17964844L && it.type == LowerBoundType.TRACE } .expectNextMatches { it.lowerBound == 17964844L && it.type == LowerBoundType.BLOCK } .expectNextMatches { it.lowerBound == 17964844L && it.type == LowerBoundType.TX } .expectNextMatches { it.lowerBound == 17964844L && it.type == LowerBoundType.LOGS } @@ -97,6 +99,8 @@ class RecursiveLowerBoundServiceTest { .hasSameElementsAs( listOf( LowerBoundData(17964844L, LowerBoundType.STATE), + LowerBoundData(17964844L, LowerBoundType.DEBUG), + LowerBoundData(17964844L, LowerBoundType.TRACE), LowerBoundData(17964844L, LowerBoundType.BLOCK), LowerBoundData(17964844L, LowerBoundType.TX), LowerBoundData(17964844L, LowerBoundType.LOGS),