Check chain settings if the next height is very different (#548)

This commit is contained in:
KirillPamPam
2024-08-15 18:42:07 +04:00
committed by GitHub
parent 96487b6c5e
commit 021add4fb0
12 changed files with 584 additions and 19 deletions

View File

@@ -36,7 +36,7 @@ class GenericWsHeadTest {
@Test
fun `validate chain settings and then head sub`() {
val block = block()
val block = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn Mono.empty()
}
@@ -133,14 +133,15 @@ class GenericWsHeadTest {
@Test
fun `no validate chain settings if it's disabled`() {
val block = block()
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(25000, BlockHash.from("0x2ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
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"))
WsSubscriptions.SubscribeData(Flux.just(Global.objectMapper.writeValueAsBytes(block1), Global.objectMapper.writeValueAsBytes(block2)), "id", AtomicReference("subId"))
}
val connection = mock<WsConnection>()
val wsPool = mock<WsConnectionPool> {
@@ -168,7 +169,8 @@ class GenericWsHeadTest {
StepVerifier.create(wsHead.getFlux())
.then { wsHead.start() }
.expectNext(BlockContainer.from(block))
.expectNext(BlockContainer.from(block1))
.expectNext(BlockContainer.from(block2))
.thenCancel()
.verify(Duration.ofSeconds(3))
@@ -179,7 +181,7 @@ class GenericWsHeadTest {
@Test
fun `validate chain settings, getting an error and then head sub`() {
val block = block()
val block = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn Mono.empty()
}
@@ -224,7 +226,7 @@ class GenericWsHeadTest {
}
.expectNoEvent(Duration.ofMillis(1500))
.then {
wsHead.onNoHeadUpdates()
wsHead.start()
}
.expectNext(BlockContainer.from(block))
.thenCancel()
@@ -235,14 +237,234 @@ class GenericWsHeadTest {
verify(wsSub).subscribe(ChainRequest("eth_subscribe", ListParams("newHeads")))
}
private fun block() =
@Test
fun `emit chain heads and no suspicious head`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(15000, BlockHash.from("0x2ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
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(block1), Global.objectMapper.writeValueAsBytes(block2)), "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(block1))
.expectNext(BlockContainer.from(block2))
.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 `emit chain heads and validate a suspicious one`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(25000, BlockHash.from("0x2ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
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(block1), Global.objectMapper.writeValueAsBytes(block2)), "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(block1))
.expectNext(BlockContainer.from(block2))
.thenCancel()
.verify(Duration.ofSeconds(1))
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")))
}
@Test
fun `emit chain heads, validate a suspicious one and skip it`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(25000, BlockHash.from("0x2ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
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(block1), Global.objectMapper.writeValueAsBytes(block2)), "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)) doReturn
Mono.just(ChainResponse("\"dsff\"".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(block1))
.expectNoEvent(Duration.ofMillis(500))
.thenCancel()
.verify(Duration.ofSeconds(1))
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")))
}
@Test
fun `emit chain heads, validate a suspicious one and then fatal error`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(25000, BlockHash.from("0x2ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
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(block1), Global.objectMapper.writeValueAsBytes(block2)), "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)) doReturn
Mono.just(ChainResponse("\"5\"".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(block1))
.then {
StepVerifier.create(wsHead.headLiveness())
.expectNext(HeadLivenessState.FATAL_ERROR)
.thenCancel()
.verify(Duration.ofSeconds(1))
}
.thenCancel()
.verify(Duration.ofSeconds(1))
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(
height: Long,
blockHash: BlockHash,
) =
BlockJson<TransactionRefJson>()
.apply {
number = 1500000L
number = height
uncles = emptyList()
totalDifficulty = BigInteger.ONE
parentHash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
parentHash = blockHash
timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
hash = blockHash
}
}

View File

@@ -0,0 +1,257 @@
package io.emeraldpay.dshackle.upstream.generic
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.EthereumChainSpecific
import io.emeraldpay.dshackle.upstream.ethereum.HeadLivenessState
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.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.Mono
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
class GenericRpcHeadTest {
@Test
fun `emit chain heads and no suspicious head`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(10001, BlockHash.from("0x3ec1ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val upstream = mock<DefaultUpstream> {
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions().buildOptions()
}
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block1), null)) doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block2), null))
}
val head = GenericRpcHead(
reader,
AlwaysForkChoice(),
"id",
BlockValidator.ALWAYS_VALID,
upstream,
Schedulers.boundedElastic(),
EthereumChainSpecific,
Duration.ofSeconds(5),
)
StepVerifier.withVirtualTime { head.getFlux() }
.expectSubscription()
.then { head.start() }
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block1))
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block2))
.thenCancel()
.verify(Duration.ofSeconds(3))
verify(reader, times(2)).read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false)))
verify(reader, never()).read(ChainRequest("eth_chainId", ListParams()))
verify(reader, never()).read(ChainRequest("net_version", ListParams()))
}
@Test
fun `emit chain heads and no validation if it's disabled`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(50000, BlockHash.from("0x3ec1ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val upstream = mock<DefaultUpstream> {
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions(disableUpstreamValidation = true).buildOptions()
}
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block1), null)) doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block2), null))
}
val head = GenericRpcHead(
reader,
AlwaysForkChoice(),
"id",
BlockValidator.ALWAYS_VALID,
upstream,
Schedulers.boundedElastic(),
EthereumChainSpecific,
Duration.ofSeconds(5),
)
StepVerifier.withVirtualTime { head.getFlux() }
.expectSubscription()
.then { head.start() }
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block1))
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block2))
.thenCancel()
.verify(Duration.ofSeconds(3))
verify(reader, times(2)).read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false)))
verify(reader, never()).read(ChainRequest("eth_chainId", ListParams()))
verify(reader, never()).read(ChainRequest("net_version", ListParams()))
}
@Test
fun `emit chain heads and validate chain settings`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(50000, BlockHash.from("0x3ec1ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_chainId", ListParams())) } doReturn Mono.just(ChainResponse("\"0x1\"".toByteArray(), null))
on { read(ChainRequest("net_version", ListParams())) } doReturn Mono.just(ChainResponse("\"1\"".toByteArray(), null))
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block1), null)) doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block2), null))
}
val upstream = mock<DefaultUpstream> {
on { getIngressReader() } doReturn reader
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions().buildOptions()
}
val head = GenericRpcHead(
reader,
AlwaysForkChoice(),
"id",
BlockValidator.ALWAYS_VALID,
upstream,
Schedulers.boundedElastic(),
EthereumChainSpecific,
Duration.ofSeconds(5),
)
StepVerifier.withVirtualTime { head.getFlux() }
.expectSubscription()
.then { head.start() }
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block1))
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block2))
.thenCancel()
.verify(Duration.ofSeconds(3))
verify(reader, times(2)).read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false)))
verify(reader).read(ChainRequest("eth_chainId", ListParams()))
verify(reader).read(ChainRequest("net_version", ListParams()))
}
@Test
fun `emit chain heads, validate chain settings and then fatal error`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(50000, BlockHash.from("0x3ec1ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_chainId", ListParams())) } doReturn Mono.just(ChainResponse("\"0x1\"".toByteArray(), null))
on { read(ChainRequest("net_version", ListParams())) } doReturn Mono.just(ChainResponse("\"5\"".toByteArray(), null))
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block1), null)) doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block2), null))
}
val upstream = mock<DefaultUpstream> {
on { getIngressReader() } doReturn reader
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions().buildOptions()
}
val head = GenericRpcHead(
reader,
AlwaysForkChoice(),
"id",
BlockValidator.ALWAYS_VALID,
upstream,
Schedulers.boundedElastic(),
EthereumChainSpecific,
Duration.ofSeconds(5),
)
StepVerifier.withVirtualTime { head.getFlux() }
.expectSubscription()
.then { head.start() }
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block1))
.expectNoEvent(Duration.ofSeconds(5))
.then {
StepVerifier.create(head.headLiveness())
.expectNext(HeadLivenessState.FATAL_ERROR)
.thenCancel()
.verify(Duration.ofSeconds(3))
}
.thenCancel()
.verify(Duration.ofSeconds(3))
verify(reader, times(2)).read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false)))
verify(reader).read(ChainRequest("eth_chainId", ListParams()))
verify(reader).read(ChainRequest("net_version", ListParams()))
}
@Test
fun `emit chain heads, validate chain settings and skip a received head due to error`() {
val block1 = block(10000, BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val block2 = block(50000, BlockHash.from("0x3ec1ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"))
val reader = mock<ChainReader> {
on { read(ChainRequest("eth_chainId", ListParams())) } doReturn Mono.just(ChainResponse("sds".toByteArray(), null))
on { read(ChainRequest("net_version", ListParams())) } doReturn Mono.just(ChainResponse("\"sad\"".toByteArray(), null))
on { read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false))) } doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block1), null)) doReturn
Mono.just(ChainResponse(Global.objectMapper.writeValueAsBytes(block2), null))
}
val upstream = mock<DefaultUpstream> {
on { getIngressReader() } doReturn reader
on { getChain() } doReturn Chain.ETHEREUM__MAINNET
on { getOptions() } doReturn ChainOptions.PartialOptions().buildOptions()
}
val head = GenericRpcHead(
reader,
AlwaysForkChoice(),
"id",
BlockValidator.ALWAYS_VALID,
upstream,
Schedulers.boundedElastic(),
EthereumChainSpecific,
Duration.ofSeconds(5),
)
StepVerifier.withVirtualTime { head.getFlux() }
.expectSubscription()
.then { head.start() }
.expectNoEvent(Duration.ofSeconds(5))
.expectNext(BlockContainer.from(block1))
.expectNoEvent(Duration.ofSeconds(5))
.thenCancel()
.verify(Duration.ofSeconds(3))
Thread.sleep(100)
verify(reader, times(2)).read(ChainRequest("eth_getBlockByNumber", ListParams("latest", false)))
verify(reader).read(ChainRequest("eth_chainId", ListParams()))
verify(reader).read(ChainRequest("net_version", ListParams()))
}
private fun block(
height: Long,
blockHash: BlockHash,
) =
BlockJson<TransactionRefJson>()
.apply {
number = height
uncles = emptyList()
totalDifficulty = BigInteger.ONE
parentHash = blockHash
timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
hash = blockHash
}
}

View File

@@ -53,7 +53,7 @@ class GenericConnectorFactoryCreatorTest {
config,
)?.create(mock<DefaultUpstream> { on { getId() } doReturn "id" }, Chain.ETHEREUM__MAINNET)
assertEquals(expectedBlockTime, args?.get(6))
assertEquals(expectedBlockTime, args?.get(7))
}
}