Skip to content

Commit

Permalink
Handle trace and debug bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
Кирилл committed Sep 12, 2024
1 parent 5fe044b commit ed1de1b
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 22 deletions.
2 changes: 1 addition & 1 deletion emerald-grpc
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ErrorHandler {
object UpstreamErrorHandler {
private val errorHandlers = listOf(
EthereumStateLowerBoundErrorHandler,
EthereumDebugTraceLowerBoundErrorHandler,
)
private val errorHandlerExecutor = Executors.newFixedThreadPool(
2 * SchedulersConfig.threadsMultiplier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<LowerBoundType> {
return setOf(LowerBoundType.STATE)
return setOf(LowerBoundType.STATE, LowerBoundType.DEBUG, LowerBoundType.TRACE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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<Upstream>()
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<Upstream>()
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<Arguments> =
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),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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),
Expand Down

0 comments on commit ed1de1b

Please sign in to comment.