Update methods based on upstream availability (#216)

This commit is contained in:
KirillPamPam
2023-05-25 17:27:09 +04:00
committed by GitHub
parent aa5ada324b
commit 1e9e80a9a3
8 changed files with 211 additions and 24 deletions

View File

@@ -23,16 +23,17 @@ import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosUpstream
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
import io.emeraldpay.dshackle.upstream.grpc.EthereumPosGrpcUpstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
import org.jetbrains.annotations.NotNull
import reactor.core.publisher.Flux
@@ -252,6 +253,63 @@ class MultistreamSpec extends Specification {
!act
}
def "Change ms methods based on upstream availability"() {
setup:
def up1 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2", "eth_test3"]))
def up2 = new EthereumPosRpcUpstreamMock("test2", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
def ms = new EthereumPosMultiStream(Chain.ETHEREUM, new ArrayList<EthereumPosUpstream>(), Caches.default(), Schedulers.parallel(), TestingCommons.tracerMock())
when:
ms.onUpstreamChange(
new UpstreamChangeEvent(Chain.ETHEREUM, up1, UpstreamChangeEvent.ChangeType.ADDED)
)
ms.onUpstreamChange(
new UpstreamChangeEvent(Chain.ETHEREUM, up2, UpstreamChangeEvent.ChangeType.ADDED)
)
def states = ms.subscribeStateChanges()
then:
StepVerifier.create(states)
.then {
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
up2.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
}
.expectNext(new Multistream.UpstreamChangeState(up1.getId(), UpstreamAvailability.OK))
.expectNext(new Multistream.UpstreamChangeState(up2.getId(), UpstreamAvailability.OK))
.then {
assert ms.getMethods().supportedMethods == Set.of("eth_test1", "eth_test2", "eth_test3")
}
.then {
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_SYNCING))
}
.expectNext(new Multistream.UpstreamChangeState(up1.getId(), UpstreamAvailability.SYNCING))
.then {
assert ms.getMethods().supportedMethods == Set.of("eth_test1", "eth_test2")
}
.then {
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
}
.expectNext(new Multistream.UpstreamChangeState(up1.getId(), UpstreamAvailability.OK))
.then {
assert ms.getMethods().supportedMethods == Set.of("eth_test1", "eth_test2", "eth_test3")
}
.then {
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
}
.expectNextCount(0)
.then {
up2.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
}
.expectNextCount(0)
.thenCancel()
.verify(Duration.ofSeconds(3))
}
private BlockchainOuterClass.ChainStatus status(BlockchainOuterClass.AvailabilityEnum status) {
return BlockchainOuterClass.ChainStatus.newBuilder()
.setAvailability(status)
.build()
}
class TestEthereumPosMultistream extends EthereumPosMultiStream {
TestEthereumPosMultistream(@NotNull Chain chain, @NotNull List<EthereumPosUpstream> upstreams, @NotNull Caches caches) {

View File

@@ -30,14 +30,15 @@ import io.emeraldpay.dshackle.test.MockGrpcServer
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.BuildInfo
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics
import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
import io.grpc.stub.StreamObserver
import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.Timer
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier
import spock.lang.Specification
import java.time.Duration
@@ -254,4 +255,61 @@ class EthereumGrpcUpstreamSpec extends Specification {
h.hash == BlockId.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
h.height == 650247
}
def "Send update status if methods were changed"() {
setup:
def chain = Chain.ETHEREUM
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
}
@Override
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
}
})
def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null, ChainsConfig.ChainConfig.default(), Schedulers.parallel())
upstream.setLag(0)
upstream.setStatus(UpstreamAvailability.OK)
when:
def statuses = upstream.observeStatus()
then:
StepVerifier.create(statuses)
.then {
upstream.update(
describe(["eth_getBlockByHash"]),
BlockchainOuterClass.BuildInfo.newBuilder()
.setVersion(buildInfo.version)
.build(),
)
}
.expectNext(UpstreamAvailability.OK)
.then {
upstream.update(
describe(["eth_getBlockByHash"]),
BlockchainOuterClass.BuildInfo.newBuilder()
.setVersion(buildInfo.version)
.build(),
)
}
.expectNextCount(0)
.then {
upstream.update(
describe(["eth_getBlockByHash", "eth_getBlockByHash1"]),
BlockchainOuterClass.BuildInfo.newBuilder()
.setVersion(buildInfo.version)
.build(),
)
}
.expectNext(UpstreamAvailability.OK)
.thenCancel()
.verify(Duration.ofSeconds(3))
}
private BlockchainOuterClass.DescribeChain describe(List<String> methods) {
return BlockchainOuterClass.DescribeChain.newBuilder()
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
.addAllSupportedMethods(methods)
.build()
}
}