Check chain settings via ws (#544)
This commit is contained in:
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.test
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.HeadLivenessState
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.reactivestreams.Publisher
|
||||
import reactor.core.publisher.Flux
|
||||
@@ -89,7 +90,7 @@ class EthereumHeadMock implements Head {
|
||||
}
|
||||
|
||||
@Override
|
||||
Flux<Boolean> headLiveness() {
|
||||
Flux<HeadLivenessState> headLiveness() {
|
||||
return Flux.empty()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.emeraldpay.dshackle.test
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.IngressSubscription
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.HeadLivenessState
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.NoEthereumIngressSubscription
|
||||
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
|
||||
import io.emeraldpay.dshackle.upstream.ChainRequest
|
||||
@@ -12,16 +13,16 @@ import reactor.core.publisher.Flux
|
||||
class GenericConnectorMock implements GenericConnector {
|
||||
Reader<ChainRequest, ChainResponse> api
|
||||
Head head
|
||||
Flux<Boolean> liveness
|
||||
Flux<HeadLivenessState> liveness
|
||||
|
||||
GenericConnectorMock(Reader<ChainRequest, ChainResponse> api, Head head) {
|
||||
this.api = api
|
||||
this.head = head
|
||||
this.liveness = Flux.just(false)
|
||||
this.liveness = Flux.just(HeadLivenessState.NON_CONSECUTIVE)
|
||||
}
|
||||
|
||||
@Override
|
||||
Flux<Boolean> hasLiveSubscriptionHead() {
|
||||
Flux<HeadLivenessState> headLivenessEvents() {
|
||||
return liveness
|
||||
}
|
||||
|
||||
|
||||
@@ -179,14 +179,14 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
||||
def "get available subscriptions"() {
|
||||
when:
|
||||
def up1 = TestingCommons.upstream("test")
|
||||
up1.getConnectorMock().setLiveness(Flux.just(false))
|
||||
up1.getConnectorMock().setLiveness(Flux.just(HeadLivenessState.NON_CONSECUTIVE))
|
||||
|
||||
def ethereumSubscribe1 = new EthereumEgressSubscription(TestingCommons.multistream(up1) as GenericMultistream, Schedulers.boundedElastic(), null)
|
||||
then:
|
||||
ethereumSubscribe1.getAvailableTopics() == []
|
||||
when:
|
||||
def up2 = TestingCommons.upstream("test")
|
||||
up2.getConnectorMock().setLiveness(Flux.just(true))
|
||||
up2.getConnectorMock().setLiveness(Flux.just(HeadLivenessState.OK))
|
||||
up2.stop()
|
||||
up2.start()
|
||||
def ethereumSubscribe2 = new EthereumEgressSubscription(TestingCommons.multistream(up2) as GenericMultistream, Schedulers.boundedElastic(), null)
|
||||
@@ -194,7 +194,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
||||
ethereumSubscribe2.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS].toSet()
|
||||
when:
|
||||
def up3 = TestingCommons.upstream("test")
|
||||
up3.getConnectorMock().setLiveness(Flux.just(true))
|
||||
up3.getConnectorMock().setLiveness(Flux.just(HeadLivenessState.OK))
|
||||
up3.stop()
|
||||
up3.start()
|
||||
def ethereumSubscribe3 = new EthereumEgressSubscription(TestingCommons.multistream(up3) as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||
@@ -202,7 +202,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
||||
ethereumSubscribe3.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS, EthereumEgressSubscription.METHOD_PENDING_TXES].toSet()
|
||||
when:
|
||||
def up4 = TestingCommons.upstream(TestingCommons.api(), "eth_getBlockByNumber")
|
||||
up4.getConnectorMock().setLiveness(Flux.just(true))
|
||||
up4.getConnectorMock().setLiveness(Flux.just(HeadLivenessState.OK))
|
||||
up4.stop()
|
||||
up4.start()
|
||||
def ethereumSubscribe4 = new EthereumEgressSubscription(TestingCommons.multistream(up4) as GenericMultistream, Schedulers.boundedElastic(), null)
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
||||
import io.emeraldpay.dshackle.upstream.forkchoice.AlwaysForkChoice
|
||||
import io.emeraldpay.dshackle.upstream.ChainRequest
|
||||
import io.emeraldpay.dshackle.upstream.ChainResponse
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsClient
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionRefJson
|
||||
@@ -69,6 +70,11 @@ class GenericWsHeadSpec extends Specification {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
1 * ws.subscribe(_) >> new WsSubscriptions.SubscribeData(
|
||||
Flux.fromIterable([headBlock]), "id", new AtomicReference<String>("")
|
||||
)
|
||||
@@ -82,6 +88,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -113,6 +120,11 @@ class GenericWsHeadSpec extends Specification {
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
def connectionInfoSink = Sinks.many().multicast().directBestEffort()
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> connectionInfoSink.asFlux()
|
||||
@@ -120,6 +132,8 @@ class GenericWsHeadSpec extends Specification {
|
||||
new WsSubscriptions.SubscribeData(Flux.error(new RuntimeException()), "id", new AtomicReference<String>("")),
|
||||
new WsSubscriptions.SubscribeData(Flux.fromIterable([secondHeadBlock]), "id", new AtomicReference<String>(""))
|
||||
]
|
||||
1 * it.unsubscribe(new ChainRequest("eth_unsubscribe", new ListParams(""), 2, null, null, false)) >>
|
||||
Mono.just(new ChainResponse("".bytes, null))
|
||||
}
|
||||
|
||||
def head = new GenericWsHead(
|
||||
@@ -131,6 +145,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -171,6 +186,11 @@ class GenericWsHeadSpec extends Specification {
|
||||
Global.objectMapper.writeValueAsBytes(it)
|
||||
}
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def connectionInfoSink = Sinks.many().multicast().directBestEffort()
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], null)
|
||||
@@ -195,6 +215,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -229,6 +250,11 @@ class GenericWsHeadSpec extends Specification {
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], null)
|
||||
apiMock.answerOnce("eth_blockNumber", [], Mono.empty())
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> connectionInfoSink.asFlux()
|
||||
1 * subscribe(_) >>> [
|
||||
@@ -245,6 +271,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -278,6 +305,11 @@ class GenericWsHeadSpec extends Specification {
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], null)
|
||||
apiMock.answerOnce("eth_blockNumber", [], Mono.empty())
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> connectionInfoSink.asFlux()
|
||||
1 * subscribe(_) >>> [
|
||||
@@ -294,6 +326,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -340,6 +373,11 @@ class GenericWsHeadSpec extends Specification {
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x29229361dc5aa1ec66c323dc7a299e2b61a8c8dd2a3522d41255ec10eca25dd8", false], null)
|
||||
apiMock.answerOnce("eth_blockNumber", [], Mono.empty())
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> connectionInfoSink.asFlux()
|
||||
2 * subscribe(_) >>> [
|
||||
@@ -357,6 +395,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -401,6 +440,12 @@ class GenericWsHeadSpec extends Specification {
|
||||
def reader = Mock(Reader) {
|
||||
1 * it.read(new ChainRequest("eth_getBlockByNumber", new ListParams("latest", false))) >> Mono.empty()
|
||||
}
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
def subId = "subId"
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
@@ -420,6 +465,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -450,6 +496,11 @@ class GenericWsHeadSpec extends Specification {
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> Mock(WsConnection)
|
||||
}
|
||||
def client = new JsonRpcWsClient(pool)
|
||||
|
||||
def connectionInfoSink = Sinks.many().multicast().directBestEffort()
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> connectionInfoSink.asFlux()
|
||||
@@ -467,6 +518,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
Schedulers.boundedElastic(),
|
||||
upstream,
|
||||
EthereumChainSpecific.INSTANCE,
|
||||
client,
|
||||
Duration.ofSeconds(60),
|
||||
)
|
||||
|
||||
@@ -479,7 +531,7 @@ class GenericWsHeadSpec extends Specification {
|
||||
.then {
|
||||
connectionInfoSink.tryEmitNext(new WsConnection.ConnectionInfo("id", WsConnection.ConnectionState.DISCONNECTED))
|
||||
}
|
||||
.expectNext(false)
|
||||
.expectNext(HeadLivenessState.DISCONNECTED)
|
||||
.thenCancel()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
@@ -21,19 +21,19 @@ class HeadLivenessValidatorSpec extends Specification{
|
||||
head.nextBlock(TestingCommons.blockForEthereum(1))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(2))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(3))
|
||||
}.expectNext(true).thenCancel().verify(Duration.ofSeconds(1))
|
||||
}.expectNext(HeadLivenessState.OK).thenCancel().verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "emits false if head liveness emits false"() {
|
||||
when:
|
||||
def head = Mock(Head) {
|
||||
1 * it.headLiveness() >> Flux.just(false)
|
||||
1 * it.headLiveness() >> Flux.just(HeadLivenessState.NON_CONSECUTIVE)
|
||||
1 * it.getFlux() >> Flux.just(TestingCommons.blockForEthereum(1))
|
||||
}
|
||||
def checker = new HeadLivenessValidatorImpl(head, Duration.ofSeconds(10), Schedulers.boundedElastic(), "test")
|
||||
then:
|
||||
StepVerifier.create(checker.flux)
|
||||
.expectNext(false)
|
||||
.expectNext(HeadLivenessState.NON_CONSECUTIVE)
|
||||
.thenCancel()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class HeadLivenessValidatorSpec extends Specification{
|
||||
.then {
|
||||
head.nextBlock(TestingCommons.blockForEthereum(5))
|
||||
}
|
||||
.expectNext(false)
|
||||
.expectNext(HeadLivenessState.NON_CONSECUTIVE)
|
||||
.thenCancel().verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class HeadLivenessValidatorSpec extends Specification{
|
||||
head.nextBlock(TestingCommons.blockForEthereum(2))
|
||||
}
|
||||
.thenAwait(Duration.ofSeconds(1))
|
||||
.expectNext(false)
|
||||
.expectNext(HeadLivenessState.NON_CONSECUTIVE)
|
||||
.thenCancel().verify(Duration.ofSeconds(2))
|
||||
}
|
||||
|
||||
@@ -82,13 +82,13 @@ class HeadLivenessValidatorSpec extends Specification{
|
||||
head.nextBlock(TestingCommons.blockForEthereum(2))
|
||||
}
|
||||
.thenAwait(Duration.ofSeconds(1))
|
||||
.expectNext(false)
|
||||
.expectNext(HeadLivenessState.NON_CONSECUTIVE)
|
||||
.then {
|
||||
head.nextBlock(TestingCommons.blockForEthereum(3))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(4))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(5))
|
||||
}
|
||||
.expectNext(true)
|
||||
.expectNext(HeadLivenessState.OK)
|
||||
.thenCancel().verify(Duration.ofSeconds(3))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user