diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumLowerBoundErrorHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumLowerBoundErrorHandler.kt index 587d02d4..e13409b9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumLowerBoundErrorHandler.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumLowerBoundErrorHandler.kt @@ -12,8 +12,28 @@ abstract class EthereumLowerBoundErrorHandler : ErrorHandler { override fun handle(upstream: Upstream, request: ChainRequest, errorMessage: String?) { try { if (canHandle(request, errorMessage)) { - parseTagParam(request, tagIndex(request.method))?.let { - upstream.updateLowerBound(it, type()) + parseTagParam(request, tagIndex(request.method))?.let { parsed -> + 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) { diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandlerTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandlerTest.kt index ab1908e4..3d2ce0c2 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandlerTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumStateLowerBoundErrorHandlerTest.kt @@ -1,6 +1,7 @@ package io.emeraldpay.dshackle.upstream.error import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType 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.MethodSource import org.mockito.Mockito.anyLong -import org.mockito.Mockito.mock import org.mockito.kotlin.any +import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever class EthereumStateLowerBoundErrorHandlerTest { @ParameterizedTest @MethodSource("requests") fun `update lower bound`(request: ChainRequest) { - val upstream = mock() + val upstream = mockUpstreamWithHead(300_000_000L) val handler = EthereumStateLowerBoundErrorHandler handler.handle(upstream, request, "missing trie node d5648cc9aef48154159d53800f2f") @@ -28,6 +30,53 @@ class EthereumStateLowerBoundErrorHandlerTest { 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() + whenever(head.getCurrentHeight()).thenReturn(height) + val upstream = mock() + whenever(upstream.getHead()).thenReturn(head) + return upstream + } + @Test fun `no update lower bound if error is not about state`() { val upstream = mock() diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumTraceLowerBoundErrorHandlerTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumTraceLowerBoundErrorHandlerTest.kt index 2d5d824a..170a402f 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumTraceLowerBoundErrorHandlerTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/EthereumTraceLowerBoundErrorHandlerTest.kt @@ -1,6 +1,7 @@ package io.emeraldpay.dshackle.upstream.error import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType 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.of 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.whenever class EthereumTraceLowerBoundErrorHandlerTest { @ParameterizedTest @MethodSource("requests") fun `update lower bound`(request: ChainRequest) { - val upstream = mock() + val upstream = mockUpstreamWithHead(300_000_000L) val handler = EthereumTraceLowerBoundErrorHandler handler.handle(upstream, request, "missing trie node d5648cc9aef48154159d53800f2f") @@ -27,7 +32,7 @@ class EthereumTraceLowerBoundErrorHandlerTest { @Test fun `update lower bound base on regexp`() { - val upstream = mock() + val upstream = mockUpstreamWithHead(300_000_000L) val handler = EthereumTraceLowerBoundErrorHandler handler.handle(upstream, ChainRequest("trace_block", ListParams("0xCB5A0A8")), "block #1 not found") @@ -35,6 +40,40 @@ class EthereumTraceLowerBoundErrorHandlerTest { 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() + whenever(head.getCurrentHeight()).thenReturn(height) + val upstream = mock() + whenever(upstream.getHead()).thenReturn(head) + return upstream + } + companion object { @JvmStatic fun requests(): List = diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandlerTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandlerTest.kt index 5504d12b..d2da73d9 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandlerTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/error/UpstreamErrorHandlerTest.kt @@ -1,18 +1,23 @@ package io.emeraldpay.dshackle.upstream.error import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.Head 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.mockito.Mockito.mock +import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever class UpstreamErrorHandlerTest { @Test fun `use lower bound error handler`() { + val head = mock() + whenever(head.getCurrentHeight()).thenReturn(300_000_000L) val upstream = mock() + whenever(upstream.getHead()).thenReturn(head) val request = ChainRequest("eth_getCode", ListParams("0x343", "0xCB5A0A8")) val handler = UpstreamErrorHandler