implement lowerbound fix (#830)

* implement fix
This commit is contained in:
EugeneDrpc
2026-05-15 16:05:24 +02:00
committed by GitHub
parent b99b74fbe1
commit 6bf8680359
4 changed files with 121 additions and 8 deletions

View File

@@ -12,8 +12,28 @@ abstract class EthereumLowerBoundErrorHandler : ErrorHandler {
override fun handle(upstream: Upstream, request: ChainRequest, errorMessage: String?) { override fun handle(upstream: Upstream, request: ChainRequest, errorMessage: String?) {
try { try {
if (canHandle(request, errorMessage)) { if (canHandle(request, errorMessage)) {
parseTagParam(request, tagIndex(request.method))?.let { parseTagParam(request, tagIndex(request.method))?.let { parsed ->
upstream.updateLowerBound(it, type()) val currentHeight = upstream.getHead().getCurrentHeight()
if (currentHeight == null) {
log.warn(
"Skip {} lower bound update for {}: head height unknown (parsed={})",
type(),
upstream.getId(),
parsed,
)
return@let
}
if (parsed > currentHeight) {
log.warn(
"Skip {} lower bound update for {}: parsed tag {} exceeds head {}",
type(),
upstream.getId(),
parsed,
currentHeight,
)
return@let
}
upstream.updateLowerBound(parsed, type())
} }
} }
} catch (e: RuntimeException) { } catch (e: RuntimeException) {

View File

@@ -1,6 +1,7 @@
package io.emeraldpay.dshackle.upstream.error package io.emeraldpay.dshackle.upstream.error
import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.ChainRequest
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
@@ -10,17 +11,18 @@ import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.Arguments.of import org.junit.jupiter.params.provider.Arguments.of
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyLong
import org.mockito.Mockito.mock
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never import org.mockito.kotlin.never
import org.mockito.kotlin.verify import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
class EthereumStateLowerBoundErrorHandlerTest { class EthereumStateLowerBoundErrorHandlerTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("requests") @MethodSource("requests")
fun `update lower bound`(request: ChainRequest) { fun `update lower bound`(request: ChainRequest) {
val upstream = mock<Upstream>() val upstream = mockUpstreamWithHead(300_000_000L)
val handler = EthereumStateLowerBoundErrorHandler val handler = EthereumStateLowerBoundErrorHandler
handler.handle(upstream, request, "missing trie node d5648cc9aef48154159d53800f2f") handler.handle(upstream, request, "missing trie node d5648cc9aef48154159d53800f2f")
@@ -28,6 +30,53 @@ class EthereumStateLowerBoundErrorHandlerTest {
verify(upstream).updateLowerBound(213229736, LowerBoundType.STATE) verify(upstream).updateLowerBound(213229736, LowerBoundType.STATE)
} }
@Test
fun `no update lower bound if parsed tag exceeds head`() {
val upstream = mockUpstreamWithHead(100_000_000L)
EthereumStateLowerBoundErrorHandler.handle(
upstream,
ChainRequest("eth_getBalance", ListParams("0x343", "0xCB5A0A8")),
"missing trie node d5648cc9aef48154159d53800f2f",
)
verify(upstream, never()).updateLowerBound(anyLong(), any())
}
@Test
fun `no update lower bound if head height is null`() {
val upstream = mockUpstreamWithHead(null)
EthereumStateLowerBoundErrorHandler.handle(
upstream,
ChainRequest("eth_getBalance", ListParams("0x343", "0xCB5A0A8")),
"missing trie node d5648cc9aef48154159d53800f2f",
)
verify(upstream, never()).updateLowerBound(anyLong(), any())
}
@Test
fun `no update lower bound for the Base prod incident value`() {
val upstream = mockUpstreamWithHead(30_000_000L)
EthereumStateLowerBoundErrorHandler.handle(
upstream,
ChainRequest("eth_getBalance", ListParams("0x343", "0xD150C7F1")),
"missing trie node d5648cc9aef48154159d53800f2f",
)
verify(upstream, never()).updateLowerBound(anyLong(), any())
}
private fun mockUpstreamWithHead(height: Long?): Upstream {
val head = mock<Head>()
whenever(head.getCurrentHeight()).thenReturn(height)
val upstream = mock<Upstream>()
whenever(upstream.getHead()).thenReturn(head)
return upstream
}
@Test @Test
fun `no update lower bound if error is not about state`() { fun `no update lower bound if error is not about state`() {
val upstream = mock<Upstream>() val upstream = mock<Upstream>()

View File

@@ -1,6 +1,7 @@
package io.emeraldpay.dshackle.upstream.error package io.emeraldpay.dshackle.upstream.error
import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.ChainRequest
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
@@ -9,15 +10,19 @@ import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.Arguments.of import org.junit.jupiter.params.provider.Arguments.of
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.mockito.Mockito.mock import org.mockito.Mockito.anyLong
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
class EthereumTraceLowerBoundErrorHandlerTest { class EthereumTraceLowerBoundErrorHandlerTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("requests") @MethodSource("requests")
fun `update lower bound`(request: ChainRequest) { fun `update lower bound`(request: ChainRequest) {
val upstream = mock<Upstream>() val upstream = mockUpstreamWithHead(300_000_000L)
val handler = EthereumTraceLowerBoundErrorHandler val handler = EthereumTraceLowerBoundErrorHandler
handler.handle(upstream, request, "missing trie node d5648cc9aef48154159d53800f2f") handler.handle(upstream, request, "missing trie node d5648cc9aef48154159d53800f2f")
@@ -27,7 +32,7 @@ class EthereumTraceLowerBoundErrorHandlerTest {
@Test @Test
fun `update lower bound base on regexp`() { fun `update lower bound base on regexp`() {
val upstream = mock<Upstream>() val upstream = mockUpstreamWithHead(300_000_000L)
val handler = EthereumTraceLowerBoundErrorHandler val handler = EthereumTraceLowerBoundErrorHandler
handler.handle(upstream, ChainRequest("trace_block", ListParams("0xCB5A0A8")), "block #1 not found") handler.handle(upstream, ChainRequest("trace_block", ListParams("0xCB5A0A8")), "block #1 not found")
@@ -35,6 +40,40 @@ class EthereumTraceLowerBoundErrorHandlerTest {
verify(upstream).updateLowerBound(213229736, LowerBoundType.TRACE) verify(upstream).updateLowerBound(213229736, LowerBoundType.TRACE)
} }
@Test
fun `no update lower bound if parsed tag exceeds head`() {
val upstream = mockUpstreamWithHead(100_000_000L)
EthereumTraceLowerBoundErrorHandler.handle(
upstream,
ChainRequest("trace_block", ListParams("0xCB5A0A8")),
"missing trie node d5648cc9aef48154159d53800f2f",
)
verify(upstream, never()).updateLowerBound(anyLong(), any())
}
@Test
fun `no update lower bound if head height is null`() {
val upstream = mockUpstreamWithHead(null)
EthereumTraceLowerBoundErrorHandler.handle(
upstream,
ChainRequest("trace_block", ListParams("0xCB5A0A8")),
"missing trie node d5648cc9aef48154159d53800f2f",
)
verify(upstream, never()).updateLowerBound(anyLong(), any())
}
private fun mockUpstreamWithHead(height: Long?): Upstream {
val head = mock<Head>()
whenever(head.getCurrentHeight()).thenReturn(height)
val upstream = mock<Upstream>()
whenever(upstream.getHead()).thenReturn(head)
return upstream
}
companion object { companion object {
@JvmStatic @JvmStatic
fun requests(): List<Arguments> = fun requests(): List<Arguments> =

View File

@@ -1,18 +1,23 @@
package io.emeraldpay.dshackle.upstream.error package io.emeraldpay.dshackle.upstream.error
import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.ChainRequest
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.verify import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
class UpstreamErrorHandlerTest { class UpstreamErrorHandlerTest {
@Test @Test
fun `use lower bound error handler`() { fun `use lower bound error handler`() {
val head = mock<Head>()
whenever(head.getCurrentHeight()).thenReturn(300_000_000L)
val upstream = mock<Upstream>() val upstream = mock<Upstream>()
whenever(upstream.getHead()).thenReturn(head)
val request = ChainRequest("eth_getCode", ListParams("0x343", "0xCB5A0A8")) val request = ChainRequest("eth_getCode", ListParams("0x343", "0xCB5A0A8"))
val handler = UpstreamErrorHandler val handler = UpstreamErrorHandler