better track ws capabiltity for upstream through head liveness check (#277)
This commit is contained in:
@@ -20,6 +20,8 @@ import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.FileResolver
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class ChainsConfigReaderSpec extends Specification {
|
||||
|
||||
ChainsConfigReader reader = new ChainsConfigReader(
|
||||
@@ -39,15 +41,19 @@ class ChainsConfigReaderSpec extends Specification {
|
||||
eth.laggingLagSize == 1
|
||||
eth.syncingLagSize == 6
|
||||
eth.callLimitContract == "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"
|
||||
eth.expectedBlockTime == Duration.ofSeconds(12)
|
||||
|
||||
pol.laggingLagSize == 10
|
||||
pol.syncingLagSize == 20
|
||||
pol.expectedBlockTime == Duration.ofMillis(2700)
|
||||
|
||||
opt.laggingLagSize == 3
|
||||
opt.syncingLagSize == 40
|
||||
opt.options.validatePeers == false
|
||||
opt.expectedBlockTime == Duration.ofMillis(400)
|
||||
|
||||
sep.laggingLagSize == 1
|
||||
sep.syncingLagSize == 10
|
||||
sep.expectedBlockTime == Duration.ofSeconds(12)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,7 +635,7 @@ class UpstreamsConfigReaderSpec extends Specification {
|
||||
def options = partialOptions.buildOptions()
|
||||
then:
|
||||
options == new UpstreamsConfig.Options(
|
||||
false, 30, Duration.ofSeconds(60), null, true, 1, true, true, true
|
||||
false, false, 30, Duration.ofSeconds(60), null, true, 1, true, true, true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,10 @@ import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
class ConnectorFactoryMock implements ConnectorFactory {
|
||||
Reader<JsonRpcRequest, JsonRpcResponse> api
|
||||
Head head
|
||||
ConnectorMode mode
|
||||
|
||||
ConnectorFactoryMock(Reader<JsonRpcRequest, JsonRpcResponse> api, Head head) {
|
||||
this(api, head, ConnectorMode.RPC_REQUESTS_WITH_WS_HEAD)
|
||||
}
|
||||
|
||||
ConnectorFactoryMock(Reader<JsonRpcRequest, JsonRpcResponse> api, Head head, ConnectorMode mode) {
|
||||
this.api = api
|
||||
this.head = head
|
||||
this.mode = mode
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
@@ -32,6 +26,6 @@ class ConnectorFactoryMock implements ConnectorFactory {
|
||||
}
|
||||
|
||||
EthereumConnector create(DefaultUpstream upstream, EthereumUpstreamValidator validator, Chain chain, boolean skipEnhance) {
|
||||
return new EthereumConnectorMock(api, head, this.mode)
|
||||
return new EthereumConnectorMock(api, head)
|
||||
}
|
||||
}
|
||||
@@ -9,21 +9,22 @@ import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFact
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory.ConnectorMode
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import reactor.core.publisher.Flux
|
||||
|
||||
class EthereumConnectorMock implements EthereumConnector {
|
||||
Reader<JsonRpcRequest, JsonRpcResponse> api
|
||||
Head head
|
||||
ConnectorMode mode
|
||||
Flux<Boolean> liveness
|
||||
|
||||
EthereumConnectorMock(Reader<JsonRpcRequest, JsonRpcResponse> api, Head head, ConnectorMode mode) {
|
||||
EthereumConnectorMock(Reader<JsonRpcRequest, JsonRpcResponse> api, Head head) {
|
||||
this.api = api
|
||||
this.mode = mode
|
||||
this.head = head
|
||||
this.liveness = Flux.just(false)
|
||||
}
|
||||
|
||||
@Override
|
||||
ConnectorMode getConnectorMode() {
|
||||
return this.mode
|
||||
Flux<Boolean> hasLiveSubscriptionHead() {
|
||||
return liveness
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,7 +27,6 @@ import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
import io.emeraldpay.dshackle.upstream.calls.*
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory.ConnectorMode
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import org.jetbrains.annotations.NotNull
|
||||
@@ -36,7 +35,6 @@ import org.reactivestreams.Publisher
|
||||
class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
||||
EthereumHeadMock ethereumHeadMock
|
||||
|
||||
|
||||
static CallMethods allMethods() {
|
||||
new AggregatedCallMethods([
|
||||
new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET),
|
||||
@@ -50,7 +48,7 @@ class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
||||
}
|
||||
|
||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, Map<String, String> labels) {
|
||||
this(id, chain, api, allMethods(), labels, ConnectorMode.RPC_REQUESTS_WITH_WS_HEAD)
|
||||
this(id, chain, api, allMethods(), labels)
|
||||
}
|
||||
|
||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||
@@ -62,18 +60,19 @@ class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
||||
}
|
||||
|
||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
||||
this(id, chain, api, methods, Collections.<String, String>emptyMap(), ConnectorMode.RPC_REQUESTS_WITH_WS_HEAD)
|
||||
this(id, chain, api, methods, Collections.<String, String>emptyMap())
|
||||
}
|
||||
|
||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods, Map<String, String> labels, ConnectorMode mode) {
|
||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods, Map<String, String> labels) {
|
||||
super(id, (byte)id.hashCode(), chain,
|
||||
getOpts(),
|
||||
UpstreamsConfig.UpstreamRole.PRIMARY,
|
||||
methods,
|
||||
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(labels)),
|
||||
new ConnectorFactoryMock(api, new EthereumHeadMock(), mode),
|
||||
new ConnectorFactoryMock(api, new EthereumHeadMock()),
|
||||
ChainConfig.default(),
|
||||
true
|
||||
true,
|
||||
null
|
||||
)
|
||||
this.ethereumHeadMock = this.getHead() as EthereumHeadMock
|
||||
setLag(0)
|
||||
@@ -84,6 +83,7 @@ class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
||||
static Options getOpts() {
|
||||
def opt = UpstreamsConfig.PartialOptions.getDefaults()
|
||||
opt.setDisableValidation(true)
|
||||
opt.setDisableUpstreamValidation(true)
|
||||
return opt.buildOptions()
|
||||
}
|
||||
|
||||
@@ -91,6 +91,10 @@ class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
||||
this.ethereumHeadMock.nextBlock(block)
|
||||
}
|
||||
|
||||
EthereumConnectorMock getConnectorMock() {
|
||||
return this.connector as EthereumConnectorMock
|
||||
}
|
||||
|
||||
void setBlocks(Publisher<BlockContainer> blocks) {
|
||||
this.ethereumHeadMock.predefined = blocks
|
||||
}
|
||||
|
||||
@@ -62,7 +62,8 @@ class EthereumRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
||||
new QuorumForLabels.QuorumItem(1, new UpstreamsConfig.Labels()),
|
||||
new ConnectorFactoryMock(api, new EthereumHeadMock()),
|
||||
ChainsConfig.ChainConfig.default(),
|
||||
false
|
||||
false,
|
||||
null
|
||||
)
|
||||
this.ethereumHeadMock = this.getHead() as EthereumHeadMock
|
||||
setLag(0)
|
||||
|
||||
@@ -30,7 +30,6 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory.ConnectorMode
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
@@ -63,10 +62,6 @@ class TestingCommons {
|
||||
return new EthereumPosRpcUpstreamMock(id, Chain.ETHEREUM__MAINNET, api())
|
||||
}
|
||||
|
||||
static EthereumPosRpcUpstreamMock upstream(String id, ConnectorMode mode) {
|
||||
return new EthereumPosRpcUpstreamMock(id, Chain.ETHEREUM__MAINNET, api(), EthereumPosRpcUpstreamMock.allMethods(), Collections.<String, String>emptyMap(), mode)
|
||||
}
|
||||
|
||||
static EthereumPosRpcUpstreamMock upstream(String id, String provider) {
|
||||
return new EthereumPosRpcUpstreamMock(id, Chain.ETHEREUM__MAINNET, api(), Collections.singletonMap("provider", provider))
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ class FilteredApisSpec extends Specification {
|
||||
new MostWorkForkChoice(),
|
||||
BlockValidator.ALWAYS_VALID,
|
||||
Schedulers.boundedElastic(),
|
||||
Schedulers.boundedElastic()
|
||||
Schedulers.boundedElastic(),
|
||||
Duration.ofSeconds(12)
|
||||
)
|
||||
new EthereumLikeRpcUpstream(
|
||||
"test",
|
||||
@@ -71,7 +72,8 @@ class FilteredApisSpec extends Specification {
|
||||
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(it)),
|
||||
connectorFactory,
|
||||
ChainsConfig.ChainConfig.default(),
|
||||
false
|
||||
false,
|
||||
null
|
||||
)
|
||||
}
|
||||
def matcher = new Selector.LabelMatcher("test", ["foo"])
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFact
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.hex.Hex32
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -178,18 +179,26 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
||||
|
||||
def "get available subscriptions"() {
|
||||
when:
|
||||
def up1 = TestingCommons.upstream("test", EthereumConnectorFactory.ConnectorMode.RPC_ONLY)
|
||||
def up1 = TestingCommons.upstream("test")
|
||||
up1.getConnectorMock().setLiveness(Flux.just(false))
|
||||
|
||||
def ethereumSubscribe1 = new EthereumEgressSubscription(TestingCommons.multistream(up1) as EthereumPosMultiStream, Schedulers.boundedElastic(), null)
|
||||
then:
|
||||
ethereumSubscribe1.getAvailableTopics() == []
|
||||
when:
|
||||
def up2 = TestingCommons.upstream("test")
|
||||
up2.getConnectorMock().setLiveness(Flux.just(true))
|
||||
up2.stop()
|
||||
up2.start()
|
||||
def ethereumSubscribe2 = new EthereumEgressSubscription(TestingCommons.multistream(up2) as EthereumPosMultiStream, Schedulers.boundedElastic(), null)
|
||||
then:
|
||||
ethereumSubscribe2.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS].toSet()
|
||||
when:
|
||||
def up3 = TestingCommons.upstream("test")
|
||||
def ethereumSubscribe3 = new EthereumEgressSubscription(TestingCommons.multistream(up2) as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||
up3.getConnectorMock().setLiveness(Flux.just(true))
|
||||
up3.stop()
|
||||
up3.start()
|
||||
def ethereumSubscribe3 = new EthereumEgressSubscription(TestingCommons.multistream(up3) as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||
then:
|
||||
ethereumSubscribe3.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS, EthereumEgressSubscription.METHOD_PENDING_TXES].toSet()
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.test.EthereumHeadMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class HeadLivenessValidatorSpec extends Specification{
|
||||
def "emits true"() {
|
||||
when:
|
||||
def head = new EthereumHeadMock()
|
||||
def checker = new HeadLivenessValidator(head, Duration.ofSeconds(10), Schedulers.boundedElastic())
|
||||
then:
|
||||
StepVerifier.create(checker.flux)
|
||||
.then {
|
||||
head.nextBlock(TestingCommons.blockForEthereum(1))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(2))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(3))
|
||||
}.expectNext(true).thenCancel().verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "starts accumulating trues but immediately emits after false"() {
|
||||
when:
|
||||
def head = new EthereumHeadMock()
|
||||
def checker = new HeadLivenessValidator(head, Duration.ofSeconds(100), Schedulers.boundedElastic())
|
||||
then:
|
||||
StepVerifier.create(checker.flux)
|
||||
.then {
|
||||
head.nextBlock(TestingCommons.blockForEthereum(1))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(2))
|
||||
}
|
||||
.expectNoEvent(Duration.ofMillis(100))
|
||||
.then {
|
||||
head.nextBlock(TestingCommons.blockForEthereum(5))
|
||||
}
|
||||
.expectNext(false)
|
||||
.thenCancel().verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "starts accumulating trues but timeouts because head staled"() {
|
||||
when:
|
||||
def head = new EthereumHeadMock()
|
||||
def checker = new HeadLivenessValidator(head, Duration.ofMillis(100), Schedulers.boundedElastic())
|
||||
then:
|
||||
StepVerifier.create(checker.flux)
|
||||
.then {
|
||||
head.nextBlock(TestingCommons.blockForEthereum(1))
|
||||
head.nextBlock(TestingCommons.blockForEthereum(2))
|
||||
}
|
||||
.thenAwait(Duration.ofSeconds(1))
|
||||
.expectNext(false)
|
||||
.thenCancel().verify(Duration.ofSeconds(2))
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,16 @@ chain-settings:
|
||||
lags:
|
||||
syncing: 6
|
||||
lagging: 1
|
||||
expected-block-time: 12s
|
||||
chains:
|
||||
- id: eth
|
||||
call-validate-contract: 0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96
|
||||
expected-block-time: 12s
|
||||
lags:
|
||||
syncing: 6
|
||||
lagging: 1
|
||||
- id: optimism
|
||||
expected-block-time: 400ms
|
||||
lags:
|
||||
lagging: 3
|
||||
- id: sepolia
|
||||
|
||||
Reference in New Issue
Block a user