Check chain settings via ws (#544)

This commit is contained in:
KirillPamPam
2024-08-12 18:27:47 +04:00
committed by GitHub
parent e8410d7638
commit 96487b6c5e
20 changed files with 449 additions and 59 deletions

View File

@@ -0,0 +1,248 @@
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.foundation.ChainOptions
import io.emeraldpay.dshackle.reader.ChainReader
import io.emeraldpay.dshackle.upstream.BlockValidator
import io.emeraldpay.dshackle.upstream.ChainRequest
import io.emeraldpay.dshackle.upstream.ChainResponse
import io.emeraldpay.dshackle.upstream.DefaultUpstream
import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionRefJson
import io.emeraldpay.dshackle.upstream.forkchoice.AlwaysForkChoice
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsClient
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.publisher.Sinks
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier
import java.math.BigInteger
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.concurrent.atomic.AtomicReference
class GenericWsHeadTest {
@Test
fun `validate chain settings and then head sub`() {
val block = block()
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn Mono.empty()
}
val wsSub = mock<WsSubscriptions> {
on { connectionInfoFlux() } doReturn Flux.empty()
on { subscribe(ChainRequest("eth_subscribe", ListParams("newHeads"))) } doReturn
WsSubscriptions.SubscribeData(Flux.just(Global.objectMapper.writeValueAsBytes(block)), "id", AtomicReference("subId"))
}
val connection = mock<WsConnection> {
on { isConnected } doReturn true
on { callRpc(ChainRequest("eth_chainId", ListParams())) } doReturn Mono.just(ChainResponse("\"0x1\"".toByteArray(), null))
on { callRpc(ChainRequest("net_version", ListParams())) } doReturn Mono.just(ChainResponse("\"1\"".toByteArray(), null))
}
val wsPool = mock<WsConnectionPool> {
on { getConnection() } doReturn connection
}
val wsClient = JsonRpcWsClient(wsPool)
val upstream = mock<DefaultUpstream> {
on { getId() } doReturn "id"
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions().buildOptions()
}
val wsHead = GenericWsHead(
AlwaysForkChoice(),
BlockValidator.ALWAYS_VALID,
reader,
wsSub,
Schedulers.boundedElastic(),
Schedulers.boundedElastic(),
upstream,
EthereumChainSpecific,
wsClient,
Duration.ofSeconds(60),
)
StepVerifier.create(wsHead.getFlux())
.then { wsHead.start() }
.expectNext(BlockContainer.from(block))
.thenCancel()
.verify(Duration.ofSeconds(1))
verify(connection).callRpc(ChainRequest("eth_chainId", ListParams()))
verify(connection).callRpc(ChainRequest("net_version", ListParams()))
verify(wsSub).subscribe(ChainRequest("eth_subscribe", ListParams("newHeads")))
}
@Test
fun `validate chain settings and then fatal error`() {
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn Mono.empty()
}
val wsSub = mock<WsSubscriptions> {
on { connectionInfoFlux() } doReturn Flux.empty()
}
val connection = mock<WsConnection> {
on { isConnected } doReturn true
on { callRpc(ChainRequest("eth_chainId", ListParams())) } doReturn Mono.just(ChainResponse("\"0x1\"".toByteArray(), null))
on { callRpc(ChainRequest("net_version", ListParams())) } doReturn Mono.just(ChainResponse("\"155\"".toByteArray(), null))
}
val wsPool = mock<WsConnectionPool> {
on { getConnection() } doReturn connection
}
val wsClient = JsonRpcWsClient(wsPool)
val upstream = mock<DefaultUpstream> {
on { getId() } doReturn "id"
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions().buildOptions()
}
val wsHead = GenericWsHead(
AlwaysForkChoice(),
BlockValidator.ALWAYS_VALID,
reader,
wsSub,
Schedulers.boundedElastic(),
Schedulers.boundedElastic(),
upstream,
EthereumChainSpecific,
wsClient,
Duration.ofSeconds(60),
)
StepVerifier.create(wsHead.headLiveness())
.then { wsHead.start() }
.expectNext(HeadLivenessState.FATAL_ERROR)
.thenCancel()
.verify(Duration.ofSeconds(3))
verify(connection).callRpc(ChainRequest("eth_chainId", ListParams()))
verify(connection).callRpc(ChainRequest("net_version", ListParams()))
verify(wsSub, never()).subscribe(ChainRequest("eth_subscribe", ListParams("newHeads")))
}
@Test
fun `no validate chain settings if it's disabled`() {
val block = block()
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn Mono.empty()
}
val wsSub = mock<WsSubscriptions> {
on { connectionInfoFlux() } doReturn Flux.empty()
on { subscribe(ChainRequest("eth_subscribe", ListParams("newHeads"))) } doReturn
WsSubscriptions.SubscribeData(Flux.just(Global.objectMapper.writeValueAsBytes(block)), "id", AtomicReference("subId"))
}
val connection = mock<WsConnection>()
val wsPool = mock<WsConnectionPool> {
on { getConnection() } doReturn connection
}
val wsClient = JsonRpcWsClient(wsPool)
val upstream = mock<DefaultUpstream> {
on { getId() } doReturn "id"
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions(disableUpstreamValidation = true).buildOptions()
}
val wsHead = GenericWsHead(
AlwaysForkChoice(),
BlockValidator.ALWAYS_VALID,
reader,
wsSub,
Schedulers.boundedElastic(),
Schedulers.boundedElastic(),
upstream,
EthereumChainSpecific,
wsClient,
Duration.ofSeconds(60),
)
StepVerifier.create(wsHead.getFlux())
.then { wsHead.start() }
.expectNext(BlockContainer.from(block))
.thenCancel()
.verify(Duration.ofSeconds(3))
verify(connection, never()).callRpc(ChainRequest("eth_chainId", ListParams()))
verify(connection, never()).callRpc(ChainRequest("net_version", ListParams()))
verify(wsSub).subscribe(ChainRequest("eth_subscribe", ListParams("newHeads")))
}
@Test
fun `validate chain settings, getting an error and then head sub`() {
val block = block()
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn Mono.empty()
}
val connectionInfoSink = Sinks.many().multicast().directBestEffort<WsConnection.ConnectionInfo>()
val wsSub = mock<WsSubscriptions> {
on { connectionInfoFlux() } doReturn connectionInfoSink.asFlux()
on { subscribe(ChainRequest("eth_subscribe", ListParams("newHeads"))) } doReturn
WsSubscriptions.SubscribeData(Flux.just(Global.objectMapper.writeValueAsBytes(block)), "id", AtomicReference("subId"))
}
val connection = mock<WsConnection> {
on { isConnected } doReturn true
on { callRpc(ChainRequest("eth_chainId", ListParams())) } doReturn Mono.error(RuntimeException("err")) doReturn Mono.just(ChainResponse("\"0x1\"".toByteArray(), null))
on { callRpc(ChainRequest("net_version", ListParams())) } doReturn Mono.just(ChainResponse("\"1\"".toByteArray(), null))
}
val wsPool = mock<WsConnectionPool> {
on { getConnection() } doReturn connection
}
val wsClient = JsonRpcWsClient(wsPool)
val upstream = mock<DefaultUpstream> {
on { getId() } doReturn "id"
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions().buildOptions()
}
val wsHead = GenericWsHead(
AlwaysForkChoice(),
BlockValidator.ALWAYS_VALID,
reader,
wsSub,
Schedulers.boundedElastic(),
Schedulers.boundedElastic(),
upstream,
EthereumChainSpecific,
wsClient,
Duration.ofSeconds(60),
)
StepVerifier.create(wsHead.getFlux())
.then {
wsHead.start()
connectionInfoSink.tryEmitNext(WsConnection.ConnectionInfo("id", WsConnection.ConnectionState.CONNECTED))
}
.expectNoEvent(Duration.ofMillis(1500))
.then {
wsHead.onNoHeadUpdates()
}
.expectNext(BlockContainer.from(block))
.thenCancel()
.verify(Duration.ofSeconds(3))
verify(connection, times(2)).callRpc(ChainRequest("eth_chainId", ListParams()))
verify(connection, times(2)).callRpc(ChainRequest("net_version", ListParams()))
verify(wsSub).subscribe(ChainRequest("eth_subscribe", ListParams("newHeads")))
}
private fun block() =
BlockJson<TransactionRefJson>()
.apply {
number = 1500000L
uncles = emptyList()
totalDifficulty = BigInteger.ONE
parentHash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
}
}