From d44b72ec6920eb2f9596bc4ceef6ef3b5b4b45f4 Mon Sep 17 00:00:00 2001 From: KirillPamPam Date: Fri, 15 Mar 2024 17:46:29 +0400 Subject: [PATCH] Support beacon chain (#436) --- .../kotlin/chainsconfig.codegen.gradle.kts | 9 +- emerald-grpc | 2 +- .../io/emeraldpay/dshackle/BlockchainType.kt | 17 +- foundation/src/main/resources/chains.yaml | 33 +++ .../kotlin/io/emeraldpay/dshackle/Global.kt | 11 +- .../emeraldpay/dshackle/proxy/HttpHandler.kt | 8 +- .../emeraldpay/dshackle/proxy/ReadRpcJson.kt | 10 +- .../emeraldpay/dshackle/proxy/WriteRpcJson.kt | 12 +- .../dshackle/quorum/AlwaysQuorum.kt | 18 +- .../dshackle/quorum/BroadcastQuorum.kt | 8 +- .../emeraldpay/dshackle/quorum/CallQuorum.kt | 14 +- .../dshackle/quorum/MaximumValueQuorum.kt | 8 +- .../dshackle/quorum/NotLaggingQuorum.kt | 18 +- .../dshackle/quorum/NotNullQuorum.kt | 20 +- ...rumRpcReader.kt => QuorumRequestReader.kt} | 36 +-- .../dshackle/quorum/ValueAwareQuorum.kt | 16 +- .../dshackle/reader/BroadcastReader.kt | 18 +- .../emeraldpay/dshackle/reader/ChainReader.kt | 6 + .../dshackle/reader/JsonRpcReader.kt | 10 - ...aderFactory.kt => RequestReaderFactory.kt} | 53 ++--- .../dshackle/reader/SpannedReader.kt | 4 +- .../io/emeraldpay/dshackle/rpc/NativeCall.kt | 79 +++++-- .../configure/ConnectorFactoryCreator.kt | 21 +- .../configure/EthereumUpstreamCreator.kt | 4 +- .../GenericConnectorFactoryCreator.kt | 13 +- .../configure/GenericUpstreamCreator.kt | 4 +- .../configure/RestConnectorFactoryCreator.kt | 55 +++++ .../upstream/BasicEthUpstreamValidator.kt | 94 ++++++++ ...{HttpRpcFactory.kt => BasicHttpFactory.kt} | 23 +- .../dshackle/upstream/CallTargetsHolder.kt | 3 + .../JsonRpcError.kt => ChainCallError.kt} | 16 +- .../upstream/ChainCallUpstreamException.kt | 6 + .../JsonRpcException.kt => ChainException.kt} | 24 +- .../JsonRpcRequest.kt => ChainRequest.kt} | 24 +- .../JsonRpcResponse.kt => ChainResponse.kt} | 54 +++-- .../dshackle/upstream/HttpFactory.kt | 3 +- .../dshackle/upstream/HttpReader.kt | 114 +++++++++ .../dshackle/upstream/LabelsDetector.kt | 74 +++++- .../dshackle/upstream/Multistream.kt | 6 +- .../RpcMetrics.kt => RequestMetrics.kt} | 4 +- .../emeraldpay/dshackle/upstream/Upstream.kt | 4 +- .../dshackle/upstream/UpstreamValidator.kt | 16 +- .../beaconchain/BeaconChainLabelsDetector.kt | 27 +++ .../BeaconChainLowerBoundBlockDetector.kt | 21 ++ .../beaconchain/BeaconChainSpecific.kt | 95 ++++++++ .../beaconchain/BeaconChainValidator.kt | 88 +++++++ .../upstream/bitcoin/BitcoinMultistream.kt | 6 +- .../upstream/bitcoin/BitcoinReader.kt | 14 +- .../upstream/bitcoin/BitcoinRpcHead.kt | 16 +- .../upstream/bitcoin/BitcoinRpcUpstream.kt | 8 +- .../bitcoin/BitcoinUpstreamValidator.kt | 12 +- .../upstream/bitcoin/BitcoinZMQHead.kt | 12 +- .../upstream/bitcoin/CachingMempoolData.kt | 8 +- .../upstream/bitcoin/LocalCallRouter.kt | 16 +- .../upstream/bitcoin/RpcUnspentReader.kt | 8 +- .../calls/DefaultBeaconChainMethods.kt | 143 +++++++++++ .../EthereumArchiveBlockNumberReader.kt | 12 +- .../ethereum/EthereumChainSpecific.kt | 18 +- .../upstream/ethereum/EthereumDirectReader.kt | 35 ++- .../upstream/ethereum/EthereumLocalReader.kt | 16 +- .../EthereumLowerBoundBlockDetector.kt | 8 +- .../ethereum/EthereumUpstreamValidator.kt | 111 +++------ .../upstream/ethereum/GenericWsHead.kt | 4 +- .../upstream/ethereum/WsConnection.kt | 6 +- .../upstream/ethereum/WsConnectionFactory.kt | 6 +- .../upstream/ethereum/WsConnectionImpl.kt | 52 ++-- .../upstream/ethereum/WsSubscriptions.kt | 8 +- .../upstream/ethereum/WsSubscriptionsImpl.kt | 12 +- .../subscribe/EthereumLabelsDetector.kt | 71 +----- .../subscribe/WebsocketPendingTxes.kt | 4 +- .../upstream/generic/AbstractChainSpecific.kt | 12 +- .../upstream/generic/ChainSpecific.kt | 19 +- .../dshackle/upstream/generic/GenericHead.kt | 4 +- .../generic/GenericIngressSubscription.kt | 4 +- .../upstream/generic/GenericMultistream.kt | 4 +- .../upstream/generic/GenericRpcHead.kt | 4 +- .../upstream/generic/GenericUpstream.kt | 4 +- .../generic/GenericUpstreamValidator.kt | 8 +- .../dshackle/upstream/generic/LocalReader.kt | 12 +- .../generic/connectors/GenericConnector.kt | 4 +- .../generic/connectors/GenericRpcConnector.kt | 8 +- .../generic/connectors/GenericWsConnector.kt | 6 +- .../connectors/RestConnectorFactory.kt | 47 ++++ .../upstream/grpc/BitcoinGrpcUpstream.kt | 14 +- .../upstream/grpc/GenericGrpcUpstream.kt | 6 +- .../dshackle/upstream/grpc/GrpcUpstreams.kt | 8 +- .../upstream/near/NearChainSpecific.kt | 12 +- .../near/NearLowerBoundBlockDetector.kt | 4 +- .../polkadot/PolkadotChainSpecific.kt | 20 +- .../PolkadotLowerBoundBlockDetector.kt | 12 +- .../upstream/restclient/RestHttpReader.kt | 112 +++++++++ .../upstream/restclient/RestRequestParser.kt | 39 +++ .../dshackle/upstream/rpcclient/CallParams.kt | 71 +++++- .../upstream/rpcclient/JsonRpcGrpcClient.kt | 47 ++-- .../upstream/rpcclient/JsonRpcHttpClient.kt | 223 ------------------ .../upstream/rpcclient/JsonRpcHttpReader.kt | 133 +++++++++++ .../rpcclient/JsonRpcUpstreamException.kt | 6 - .../upstream/rpcclient/JsonRpcWsClient.kt | 16 +- .../upstream/rpcclient/JsonRpcWsMessage.kt | 4 +- .../upstream/rpcclient/ResponseParser.kt | 26 +- .../upstream/rpcclient/ResponseRpcParser.kt | 11 +- .../upstream/rpcclient/ResponseWSParser.kt | 8 +- .../rpcclient/stream/JsonRpcStreamParser.kt | 9 +- .../upstream/solana/SolanaChainSpecific.kt | 24 +- .../solana/SolanaLowerBoundBlockDetector.kt | 12 +- .../starknet/StarknetChainSpecific.kt | 12 +- .../{rpcclient => }/stream/Responses.kt | 6 +- .../dshackle/proxy/HttpHandlerSpec.groovy | 4 +- .../dshackle/proxy/ReadRpcJsonSpec.groovy | 10 +- .../dshackle/quorum/AlwaysQuorumSpec.groovy | 8 +- .../quorum/BroadcastQuorumSpec.groovy | 22 +- .../quorum/MaximumValueQuorumSpec.groovy | 24 +- .../quorum/NotLaggingQuorumSpec.groovy | 14 +- .../dshackle/quorum/NotNullQuorumSpec.groovy | 24 +- ....groovy => QuorumRequestReaderSpec.groovy} | 84 +++---- .../quorum/ValueAwareQuorumSpec.groovy | 8 +- .../reader/BroadcastReaderSpec.groovy | 60 ++--- .../dshackle/rpc/NativeCallSpec.groovy | 63 ++--- .../dshackle/test/ApiReaderMock.groovy | 38 +-- .../dshackle/test/ConnectorFactoryMock.groovy | 9 +- .../dshackle/test/EthereumApiStub.groovy | 8 +- .../dshackle/test/GenericConnectorMock.groovy | 10 +- .../dshackle/test/GenericUpstreamMock.groovy | 16 +- .../dshackle/test/TestingCommons.groovy | 18 +- .../dshackle/upstream/FilteredApisSpec.groovy | 4 +- .../dshackle/upstream/MultistreamSpec.groovy | 4 +- .../bitcoin/BitcoinRpcHeadSpec.groovy | 16 +- .../bitcoin/RpcUnspentReaderSpec.groovy | 10 +- .../ethereum/EthereumCachingReaderSpec.groovy | 115 +++++---- .../EthereumLabelsDetectorSpec.groovy | 18 +- .../ethereum/EthereumLocalReaderSpec.groovy | 6 +- .../EthereumUpstreamValidatorSpec.groovy | 84 +++---- .../ethereum/GenericWsHeadSpec.groovy | 12 +- .../ethereum/WsConnectionImplRealSpec.groovy | 12 +- .../ethereum/WsConnectionImplSpec.groovy | 8 +- .../ethereum/WsSubscriptionsImplSpec.groovy | 16 +- .../subscribe/WebsocketPendingTxesSpec.groovy | 4 +- .../rpcclient/JsonRpcErrorSpec.groovy | 7 +- .../rpcclient/JsonRpcGrpcClientSpec.groovy | 6 +- ...ec.groovy => JsonRpcHttpReaderSpec.groovy} | 28 ++- .../rpcclient/JsonRpcRequestSpec.groovy | 13 +- .../rpcclient/JsonRpcResponseSpec.groovy | 34 +-- .../rpcclient/JsonRpcWsClientSpec.groovy | 7 +- ...oryTest.kt => RequestReaderFactoryTest.kt} | 14 +- .../emeraldpay/dshackle/rpc/NativeCallTest.kt | 2 +- .../RecursiveLowerBoundBlockDetectorTest.kt | 40 ++-- .../beaconchain/BeaconChainSpecificTest.kt | 50 ++++ .../beaconchain/BeaconChainValidatorTest.kt | 98 ++++++++ .../GenericConnectorFactoryCreatorTest.kt | 2 +- .../restclient/RestRequestParserTest.kt | 50 ++++ .../stream/JsonRpcStreamParserTest.kt | 4 + .../SolanaLowerBoundBlockDetectorTest.kt | 16 +- 152 files changed, 2498 insertions(+), 1382 deletions(-) rename src/main/kotlin/io/emeraldpay/dshackle/quorum/{QuorumRpcReader.kt => QuorumRequestReader.kt} (85%) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/reader/ChainReader.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/reader/JsonRpcReader.kt rename src/main/kotlin/io/emeraldpay/dshackle/reader/{RpcReaderFactory.kt => RequestReaderFactory.kt} (51%) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/startup/configure/RestConnectorFactoryCreator.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/BasicEthUpstreamValidator.kt rename src/main/kotlin/io/emeraldpay/dshackle/upstream/{HttpRpcFactory.kt => BasicHttpFactory.kt} (70%) rename src/main/kotlin/io/emeraldpay/dshackle/upstream/{rpcclient/JsonRpcError.kt => ChainCallError.kt} (64%) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainCallUpstreamException.kt rename src/main/kotlin/io/emeraldpay/dshackle/upstream/{rpcclient/JsonRpcException.kt => ChainException.kt} (65%) rename src/main/kotlin/io/emeraldpay/dshackle/upstream/{rpcclient/JsonRpcRequest.kt => ChainRequest.kt} (77%) rename src/main/kotlin/io/emeraldpay/dshackle/upstream/{rpcclient/JsonRpcResponse.kt => ChainResponse.kt} (81%) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpReader.kt rename src/main/kotlin/io/emeraldpay/dshackle/upstream/{rpcclient/RpcMetrics.kt => RequestMetrics.kt} (91%) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLabelsDetector.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLowerBoundBlockDetector.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecific.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidator.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultBeaconChainMethods.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/RestConnectorFactory.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestHttpReader.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParser.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpClient.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpReader.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcUpstreamException.kt rename src/main/kotlin/io/emeraldpay/dshackle/upstream/{rpcclient => }/stream/Responses.kt (92%) rename src/test/groovy/io/emeraldpay/dshackle/quorum/{QuorumRpcReaderSpec.groovy => QuorumRequestReaderSpec.groovy} (71%) rename src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/{JsonRpcHttpClientSpec.groovy => JsonRpcHttpReaderSpec.groovy} (80%) rename src/test/kotlin/io/emeraldpay/dshackle/reader/{RpcReaderFactoryTest.kt => RequestReaderFactoryTest.kt} (79%) create mode 100644 src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecificTest.kt create mode 100644 src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidatorTest.kt create mode 100644 src/test/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParserTest.kt diff --git a/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts b/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts index 8a2c63ac..6a9072ac 100644 --- a/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts +++ b/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts @@ -1,5 +1,11 @@ -import com.squareup.kotlinpoet.* + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asClassName import io.emeraldpay.dshackle.BlockchainType import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.ChainsConfigReader @@ -119,6 +125,7 @@ open class CodeGen(private val config: ChainsConfig) { "polkadot" -> "BlockchainType.POLKADOT" "solana" -> "BlockchainType.SOLANA" "near" -> "BlockchainType.NEAR" + "eth-beacon-chain" -> "BlockchainType.ETHEREUM_BEACON_CHAIN" else -> throw IllegalArgumentException("unknown blockchain type $type") } } diff --git a/emerald-grpc b/emerald-grpc index 7a83a728..b29eb2e0 160000 --- a/emerald-grpc +++ b/emerald-grpc @@ -1 +1 @@ -Subproject commit 7a83a728ca70ffed4cd44a664c9228db367b160c +Subproject commit b29eb2e04fa255732907360d81f2f525b9873d05 diff --git a/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt b/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt index e2ef746a..acfe2f9d 100644 --- a/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt +++ b/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt @@ -1,5 +1,18 @@ package io.emeraldpay.dshackle -enum class BlockchainType { - UNKNOWN, BITCOIN, ETHEREUM, STARKNET, POLKADOT, SOLANA, NEAR; +enum class BlockchainType( + val apiType: ApiType, +) { + UNKNOWN(ApiType.JSON_RPC), + BITCOIN(ApiType.JSON_RPC), + ETHEREUM(ApiType.JSON_RPC), + STARKNET(ApiType.JSON_RPC), + POLKADOT(ApiType.JSON_RPC), + SOLANA(ApiType.JSON_RPC), + NEAR(ApiType.JSON_RPC), + ETHEREUM_BEACON_CHAIN(ApiType.REST); +} + +enum class ApiType { + JSON_RPC, REST; } \ No newline at end of file diff --git a/foundation/src/main/resources/chains.yaml b/foundation/src/main/resources/chains.yaml index 5e38b161..c93706ab 100644 --- a/foundation/src/main/resources/chains.yaml +++ b/foundation/src/main/resources/chains.yaml @@ -1361,3 +1361,36 @@ chain-settings: grpcId: 1052 short-names: [dymension] chain-id: 0x44c + - id: eth-beacon-chain + label: Ethereum Beacon Chain + type: eth-beacon-chain + settings: + expected-block-time: 12s + lags: + syncing: 6 + lagging: 1 + chains: + - id: Mainnet + chain-id: 0x0 + short-names: [eth-beacon-chain] + code: ETH_BEACON_CHAIN + grpcId: 1053 + priority: 100 + - id: Goerli + chain-id: 0x0 + code: GOERLI_BEACON_CHAIN + grpcId: 10069 + priority: 1 + short-names: [eth-beacon-chain-goerli] + - id: Sepolia + code: SEPOLIA_BEACON_CHAIN + grpcId: 10070 + priority: 10 + chain-id: 0x0 + short-names: [eth-beacon-chain-sepolia] + - id: Holesky + code: HOLESKY_BEACON_CHAIN + grpcId: 10071 + priority: 5 + chain-id: 0x0 + short-names: [eth-beacon-chain-holesky] diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt index 8c4c64d4..5a732797 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt @@ -21,14 +21,16 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.datatype.jdk8.Jdk8Module import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse +import io.emeraldpay.dshackle.upstream.beaconchain.BeaconChainBlockHeader +import io.emeraldpay.dshackle.upstream.beaconchain.BeaconChainBlockHeaderDeserializer import io.emeraldpay.dshackle.upstream.bitcoin.data.EsploraUnspent import io.emeraldpay.dshackle.upstream.bitcoin.data.EsploraUnspentDeserializer import io.emeraldpay.dshackle.upstream.bitcoin.data.RpcUnspent import io.emeraldpay.dshackle.upstream.bitcoin.data.RpcUnspentDeserializer import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.TransactionIdSerializer -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone @@ -67,12 +69,13 @@ class Global { private fun createObjectMapper(): ObjectMapper { val module = SimpleModule("EmeraldDshackle", Version(1, 0, 0, null, null, null)) - module.addSerializer(JsonRpcResponse::class.java, JsonRpcResponse.ResponseJsonSerializer()) + module.addSerializer(ChainResponse::class.java, ChainResponse.ResponseJsonSerializer()) module.addSerializer(TransactionId::class.java, TransactionIdSerializer()) module.addDeserializer(EsploraUnspent::class.java, EsploraUnspentDeserializer()) module.addDeserializer(RpcUnspent::class.java, RpcUnspentDeserializer()) - module.addDeserializer(JsonRpcRequest::class.java, JsonRpcRequest.Deserializer()) + module.addDeserializer(ChainRequest::class.java, ChainRequest.Deserializer()) + module.addDeserializer(BeaconChainBlockHeader::class.java, BeaconChainBlockHeaderDeserializer()) val objectMapper = ObjectMapper() objectMapper.registerModule(module) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/proxy/HttpHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/proxy/HttpHandler.kt index 5b8e2dbe..def791dd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/proxy/HttpHandler.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/proxy/HttpHandler.kt @@ -20,8 +20,8 @@ import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.ProxyConfig import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerHttp import io.emeraldpay.dshackle.rpc.NativeCall +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.netty.buffer.ByteBuf import io.netty.buffer.Unpooled import org.reactivestreams.Publisher @@ -97,10 +97,10 @@ class HttpHandler( } .onErrorResume(RpcException::class.java) { err -> val id = err.details?.let { - if (it is JsonRpcResponse.Id) it else JsonRpcResponse.NumberId(-1) - } ?: JsonRpcResponse.NumberId(-1) + if (it is ChainResponse.Id) it else ChainResponse.NumberId(-1) + } ?: ChainResponse.NumberId(-1) - val json = JsonRpcResponse.error(err.code, err.rpcMessage, id) + val json = ChainResponse.error(err.code, err.rpcMessage, id) Mono.just(Global.objectMapper.writeValueAsString(json)) } .map { Unpooled.wrappedBuffer(it.toByteArray()) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/proxy/ReadRpcJson.kt b/src/main/kotlin/io/emeraldpay/dshackle/proxy/ReadRpcJson.kt index b98af23b..36d6cda2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/proxy/ReadRpcJson.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/proxy/ReadRpcJson.kt @@ -20,10 +20,10 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.json.RequestJson import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.io.IOException @@ -54,27 +54,27 @@ open class ReadRpcJson : Function { throw RpcException( RpcResponseError.CODE_INVALID_REQUEST, "jsonrpc version is not set", - id?.let { JsonRpcResponse.Id.from(it) }, + id?.let { ChainResponse.Id.from(it) }, ) } throw RpcException( RpcResponseError.CODE_INVALID_REQUEST, "Unsupported JSON RPC version: " + json["jsonrpc"].toString(), - id?.let { JsonRpcResponse.Id.from(it) }, + id?.let { ChainResponse.Id.from(it) }, ) } if (!(json["method"] != null && json["method"] is String)) { throw RpcException( RpcResponseError.CODE_INVALID_REQUEST, "Method is not set", - id?.let { JsonRpcResponse.Id.from(it) }, + id?.let { ChainResponse.Id.from(it) }, ) } if (json.containsKey("params") && json["params"] !is List<*>) { throw RpcException( RpcResponseError.CODE_INVALID_REQUEST, "Params must be an array", - id?.let { JsonRpcResponse.Id.from(it) }, + id?.let { ChainResponse.Id.from(it) }, ) } RequestJson( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/proxy/WriteRpcJson.kt b/src/main/kotlin/io/emeraldpay/dshackle/proxy/WriteRpcJson.kt index 84bea0eb..2e1326b1 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/proxy/WriteRpcJson.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/proxy/WriteRpcJson.kt @@ -19,7 +19,7 @@ package io.emeraldpay.dshackle.proxy import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.rpc.NativeCall -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainResponse import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import reactor.core.publisher.Flux @@ -68,22 +68,22 @@ open class WriteRpcJson { open fun toJson(call: ProxyCall, response: NativeCall.CallResult): String? { val id = call.ids[response.id]?.let { - JsonRpcResponse.Id.from(it) + ChainResponse.Id.from(it) } ?: return null val json = if (response.isError()) { val error = response.error!! error.upstreamError?.let { upstreamError -> - JsonRpcResponse.error(upstreamError, id) - } ?: JsonRpcResponse.error(-32002, error.message, id) + ChainResponse.error(upstreamError, id) + } ?: ChainResponse.error(-32002, error.message, id) } else { - JsonRpcResponse.ok(response.result!!, id) + ChainResponse.ok(response.result!!, id) } return objectMapper.writeValueAsString(json) } fun toJson(call: ProxyCall, error: NativeCall.CallFailure): String? { val id = call.ids[error.id] ?: return null - val json = JsonRpcResponse.error(-32003, error.reason.message ?: "", JsonRpcResponse.Id.from(id)) + val json = ChainResponse.error(-32003, error.reason.message ?: "", ChainResponse.Id.from(id)) return objectMapper.writeValueAsString(json) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt index a3294604..e9d8ca1c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt @@ -16,17 +16,17 @@ */ package io.emeraldpay.dshackle.quorum +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner open class AlwaysQuorum : CallQuorum { private var resolved = false - private var result: JsonRpcResponse? = null - private var rpcError: JsonRpcError? = null + private var result: ChainResponse? = null + private var rpcError: ChainCallError? = null private var sig: ResponseSigner.Signature? = null private val resolvers = ArrayList() @@ -43,7 +43,7 @@ open class AlwaysQuorum : CallQuorum { } override fun record( - response: JsonRpcResponse, + response: ChainResponse, signature: ResponseSigner.Signature?, upstream: Upstream, ): Boolean { @@ -55,7 +55,7 @@ open class AlwaysQuorum : CallQuorum { } override fun record( - error: JsonRpcException, + error: ChainException, signature: ResponseSigner.Signature?, upstream: Upstream, ) { @@ -64,11 +64,11 @@ open class AlwaysQuorum : CallQuorum { resolvers.add(upstream) } - override fun getResponse(): JsonRpcResponse? { + override fun getResponse(): ChainResponse? { return result } - override fun getError(): JsonRpcError? { + override fun getError(): ChainCallError? { return rpcError } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/BroadcastQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/BroadcastQuorum.kt index 215cff06..ae68f4c6 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/BroadcastQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/BroadcastQuorum.kt @@ -16,13 +16,13 @@ */ package io.emeraldpay.dshackle.quorum +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner open class BroadcastQuorum() : CallQuorum, ValueAwareQuorum(String::class.java) { - private var result: JsonRpcResponse? = null + private var result: ChainResponse? = null private var txid: String? = null private var sig: ResponseSigner.Signature? = null @@ -34,7 +34,7 @@ open class BroadcastQuorum() : CallQuorum, ValueAwareQuorum(String::clas return result == null } - override fun getResponse(): JsonRpcResponse? { + override fun getResponse(): ChainResponse? { return result } @@ -43,7 +43,7 @@ open class BroadcastQuorum() : CallQuorum, ValueAwareQuorum(String::clas } override fun recordValue( - response: JsonRpcResponse, + response: ChainResponse, responseValue: String?, signature: ResponseSigner.Signature?, upstream: Upstream, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt index 548d47d1..62038bcd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt @@ -16,10 +16,10 @@ */ package io.emeraldpay.dshackle.quorum +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner interface CallQuorum { @@ -27,19 +27,19 @@ interface CallQuorum { fun isFailed(): Boolean fun record( - response: JsonRpcResponse, + response: ChainResponse, signature: ResponseSigner.Signature?, upstream: Upstream, ): Boolean fun record( - error: JsonRpcException, + error: ChainException, signature: ResponseSigner.Signature?, upstream: Upstream, ) fun getSignature(): ResponseSigner.Signature? - fun getResponse(): JsonRpcResponse? - fun getError(): JsonRpcError? + fun getResponse(): ChainResponse? + fun getError(): ChainCallError? fun getResolvedBy(): Collection } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/MaximumValueQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/MaximumValueQuorum.kt index fb203b42..7a8ea761 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/MaximumValueQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/MaximumValueQuorum.kt @@ -1,13 +1,13 @@ package io.emeraldpay.dshackle.quorum +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.ethereum.hex.HexQuantity -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner class MaximumValueQuorum : CallQuorum, ValueAwareQuorum(String::class.java) { private var max: Long? = null - private var result: JsonRpcResponse? = null + private var result: ChainResponse? = null private var sig: ResponseSigner.Signature? = null override fun isResolved(): Boolean { @@ -18,7 +18,7 @@ class MaximumValueQuorum : CallQuorum, ValueAwareQuorum(String::class.ja return result == null } - override fun getResponse(): JsonRpcResponse? { + override fun getResponse(): ChainResponse? { return result } @@ -26,7 +26,7 @@ class MaximumValueQuorum : CallQuorum, ValueAwareQuorum(String::class.ja return sig } override fun recordValue( - response: JsonRpcResponse, + response: ChainResponse, responseValue: String?, signature: ResponseSigner.Signature?, upstream: Upstream, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt index 68063e62..03a6315f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt @@ -16,10 +16,10 @@ */ package io.emeraldpay.dshackle.quorum +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import java.util.concurrent.atomic.AtomicReference @@ -30,9 +30,9 @@ import java.util.concurrent.atomic.AtomicReference */ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum { - private val result: AtomicReference = AtomicReference() + private val result: AtomicReference = AtomicReference() private val failed = AtomicReference(false) - private var rpcError: JsonRpcError? = null + private var rpcError: ChainCallError? = null private var sig: ResponseSigner.Signature? = null private val resolvers = ArrayList() @@ -45,7 +45,7 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum { } override fun record( - response: JsonRpcResponse, + response: ChainResponse, signature: ResponseSigner.Signature?, upstream: Upstream, ): Boolean { @@ -60,7 +60,7 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum { } override fun record( - error: JsonRpcException, + error: ChainException, signature: ResponseSigner.Signature?, upstream: Upstream, ) { @@ -76,11 +76,11 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum { return sig } - override fun getResponse(): JsonRpcResponse { + override fun getResponse(): ChainResponse { return result.get() } - override fun getError(): JsonRpcError? { + override fun getError(): ChainCallError? { return rpcError } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotNullQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotNullQuorum.kt index 62c98f55..2f5d0591 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotNullQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotNullQuorum.kt @@ -1,16 +1,16 @@ package io.emeraldpay.dshackle.quorum import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner class NotNullQuorum : CallQuorum { private var sig: ResponseSigner.Signature? = null - private var result: JsonRpcResponse? = null - private var rpcError: JsonRpcError? = null + private var result: ChainResponse? = null + private var rpcError: ChainCallError? = null private val resolvers = ArrayList() private var allFailed = true private val seenUpstreams = HashSet() // just to prevent calling retry upstreams in FilteredApis @@ -20,7 +20,7 @@ class NotNullQuorum : CallQuorum { override fun isFailed(): Boolean = rpcError != null override fun record( - response: JsonRpcResponse, + response: ChainResponse, signature: ResponseSigner.Signature?, upstream: Upstream, ): Boolean { @@ -37,13 +37,13 @@ class NotNullQuorum : CallQuorum { return false } - override fun record(error: JsonRpcException, signature: ResponseSigner.Signature?, upstream: Upstream) { + override fun record(error: ChainException, signature: ResponseSigner.Signature?, upstream: Upstream) { val upId = upstream.getId() if (seenUpstreams.contains(upId)) { if (allFailed) { rpcError = error.error } else { - result = JsonRpcResponse(Global.nullValue, null) + result = ChainResponse(Global.nullValue, null) } sig = signature } @@ -53,9 +53,9 @@ class NotNullQuorum : CallQuorum { override fun getSignature(): ResponseSigner.Signature? = sig - override fun getResponse(): JsonRpcResponse? = result + override fun getResponse(): ChainResponse? = result - override fun getError(): JsonRpcError? = rpcError + override fun getError(): ChainCallError? = rpcError override fun getResolvedBy(): Collection = resolvers diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRequestReader.kt similarity index 85% rename from src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt rename to src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRequestReader.kt index 8092e3cc..2d079aa9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRequestReader.kt @@ -20,15 +20,15 @@ import io.emeraldpay.dshackle.commons.API_READER import io.emeraldpay.dshackle.commons.SPAN_NO_RESPONSE_MESSAGE import io.emeraldpay.dshackle.commons.SPAN_REQUEST_API_TYPE import io.emeraldpay.dshackle.commons.SPAN_REQUEST_UPSTREAM_ID -import io.emeraldpay.dshackle.reader.RpcReader +import io.emeraldpay.dshackle.reader.RequestReader import io.emeraldpay.dshackle.reader.SpannedReader import io.emeraldpay.dshackle.upstream.ApiSource +import io.emeraldpay.dshackle.upstream.ChainCallUpstreamException +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcUpstreamException import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import org.slf4j.LoggerFactory import org.springframework.cloud.sleuth.Tracer @@ -45,22 +45,22 @@ import java.util.function.Function /** * Makes request with applying Quorum */ -class QuorumRpcReader( +class QuorumRequestReader( private val apiControl: ApiSource, private val quorum: CallQuorum, signer: ResponseSigner?, private val tracer: Tracer, -) : RpcReader(signer) { +) : RequestReader(signer) { companion object { - private val log = LoggerFactory.getLogger(QuorumRpcReader::class.java) + private val log = LoggerFactory.getLogger(QuorumRequestReader::class.java) } constructor(apiControl: ApiSource, quorum: CallQuorum, tracer: Tracer) : this(apiControl, quorum, null, tracer) override fun attempts(): AtomicInteger = apiControl.attempts() - override fun read(key: JsonRpcRequest): Mono { + override fun read(key: ChainRequest): Mono { // needs at least one response, so start a request apiControl.request(1) @@ -99,8 +99,8 @@ class QuorumRpcReader( .transform(processResult(defaultResult)) } - private fun execute(key: JsonRpcRequest, retrySpec: reactor.util.retry.Retry): Function, Mono> { - val quorumReduce = BiFunction, Upstream>, CallQuorum> { res, a -> + private fun execute(key: ChainRequest, retrySpec: reactor.util.retry.Retry): Function, Mono> { + val quorumReduce = BiFunction, Upstream>, CallQuorum> { res, a -> if (res.record(a.t1, a.t2.orElse(null), a.t3)) { log.trace("Quorum is resolved for method ${key.method}") apiControl.resolve() @@ -139,7 +139,7 @@ class QuorumRpcReader( } } - private fun callApi(api: Upstream, key: JsonRpcRequest): Mono, Upstream>> { + private fun callApi(api: Upstream, key: ChainRequest): Mono, Upstream>> { val apiReader = api.getIngressReader() val spanParams = mapOf( SPAN_REQUEST_API_TYPE to apiReader.javaClass.name, @@ -157,7 +157,7 @@ class QuorumRpcReader( .map { Tuples.of(it.t1, it.t2, api) } } - private fun withSignatureAndUpstream(api: Upstream, key: JsonRpcRequest, response: JsonRpcResponse): Function, Mono>>> { + private fun withSignatureAndUpstream(api: Upstream, key: ChainRequest, response: ChainResponse): Function, Mono>>> { return Function { src -> src.map { // TODO: do streaming signature @@ -171,11 +171,11 @@ class QuorumRpcReader( } } - private fun withErrorResume(api: Upstream, key: JsonRpcRequest): Function, Mono> { + private fun withErrorResume(api: Upstream, key: ChainRequest): Function, Mono> { return Function { src -> src.onErrorResume { err -> val msgError = "Error during call upstream ${api.getId()} with method ${key.method}" - if (err is JsonRpcUpstreamException) { + if (err is ChainCallUpstreamException) { log.debug(msgError, err) } else { log.warn(msgError, err) @@ -184,12 +184,12 @@ class QuorumRpcReader( // when the call failed with an error we want to notify the quorum because // it may use the error message or other details // - val cleanErr: JsonRpcException = getError(key, err) + val cleanErr: ChainException = getError(key, err) quorum.record(cleanErr, null, api) // if it's failed after that, then we don't need more calls, stop api source if (quorum.isFailed()) { val msgQuorumFailed = "Quorum is failed, stop api source. Upstream ${api.getId()}, method ${key.method}" - if (cleanErr is JsonRpcUpstreamException) { + if (cleanErr is ChainCallUpstreamException) { log.debug(msgQuorumFailed) } else { log.warn(msgQuorumFailed) @@ -205,7 +205,7 @@ class QuorumRpcReader( } } - private fun setupDefaultResult(key: JsonRpcRequest): Mono { + private fun setupDefaultResult(key: ChainRequest): Mono { return Mono.just(quorum).flatMap { q -> if (q.isFailed()) { val resolvedBy = resolvedBy()?.getId() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt index be614788..fe364c1f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt @@ -17,11 +17,11 @@ package io.emeraldpay.dshackle.quorum import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import org.slf4j.LoggerFactory @@ -30,7 +30,7 @@ abstract class ValueAwareQuorum( ) : CallQuorum { private val log = LoggerFactory.getLogger(ValueAwareQuorum::class.java) - private var rpcError: JsonRpcError? = null + private var rpcError: ChainCallError? = null protected val resolvers = ArrayList() fun extractValue(response: ByteArray, clazz: Class): T? { @@ -38,7 +38,7 @@ abstract class ValueAwareQuorum( } override fun record( - response: JsonRpcResponse, + response: ChainResponse, signature: ResponseSigner.Signature?, upstream: Upstream, ): Boolean { @@ -58,7 +58,7 @@ abstract class ValueAwareQuorum( } override fun record( - error: JsonRpcException, + error: ChainException, signature: ResponseSigner.Signature?, upstream: Upstream, ) { @@ -67,7 +67,7 @@ abstract class ValueAwareQuorum( } abstract fun recordValue( - response: JsonRpcResponse, + response: ChainResponse, responseValue: T?, signature: ResponseSigner.Signature?, upstream: Upstream, @@ -79,7 +79,7 @@ abstract class ValueAwareQuorum( upstream: Upstream, ) - override fun getError(): JsonRpcError? { + override fun getError(): ChainCallError? { return rpcError } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/reader/BroadcastReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/reader/BroadcastReader.kt index 00eb195d..8ed57cde 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/reader/BroadcastReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/reader/BroadcastReader.kt @@ -3,11 +3,11 @@ package io.emeraldpay.dshackle.reader import io.emeraldpay.dshackle.commons.BROADCAST_READER import io.emeraldpay.dshackle.commons.SPAN_REQUEST_UPSTREAM_ID import io.emeraldpay.dshackle.quorum.CallQuorum +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import org.slf4j.LoggerFactory import org.springframework.cloud.sleuth.Tracer @@ -21,7 +21,7 @@ class BroadcastReader( signer: ResponseSigner?, private val quorum: CallQuorum, private val tracer: Tracer, -) : RpcReader(signer) { +) : RequestReader(signer) { private val internalMatcher = Selector.MultiMatcher( listOf(Selector.AvailabilityMatcher(), matcher), ) @@ -34,7 +34,7 @@ class BroadcastReader( return AtomicInteger(1) } - override fun read(key: JsonRpcRequest): Mono { + override fun read(key: ChainRequest): Mono { return Flux.fromIterable(upstreams) .filter { internalMatcher.matches(it) } .flatMap { up -> @@ -44,7 +44,7 @@ class BroadcastReader( val sig = getSignature(key, it.jsonRpcResponse, it.upstream.getId()) quorum.record(it.jsonRpcResponse, sig, it.upstream) } else { - val err = JsonRpcException(JsonRpcResponse.NumberId(key.id), it.jsonRpcResponse.error!!, it.upstream.getId()) + val err = ChainException(ChainResponse.NumberId(key.id), it.jsonRpcResponse.error!!, it.upstream.getId()) quorum.record(err, null, it.upstream) } quorum @@ -69,7 +69,7 @@ class BroadcastReader( } private fun execute( - key: JsonRpcRequest, + key: ChainRequest, upstream: Upstream, ): Mono = SpannedReader( @@ -83,12 +83,12 @@ class BroadcastReader( .onErrorResume { log.warn("Error during execution ${key.method} from upstream ${upstream.getId()} with message - ${it.message}") Mono.just( - BroadcastResponse(JsonRpcResponse(null, getError(key, it).error), upstream), + BroadcastResponse(ChainResponse(null, getError(key, it).error), upstream), ) } private class BroadcastResponse( - val jsonRpcResponse: JsonRpcResponse, + val jsonRpcResponse: ChainResponse, val upstream: Upstream, ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/reader/ChainReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/reader/ChainReader.kt new file mode 100644 index 00000000..e3d36c6d --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/reader/ChainReader.kt @@ -0,0 +1,6 @@ +package io.emeraldpay.dshackle.reader + +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse + +typealias ChainReader = Reader diff --git a/src/main/kotlin/io/emeraldpay/dshackle/reader/JsonRpcReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/reader/JsonRpcReader.kt deleted file mode 100644 index 89333b6e..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/reader/JsonRpcReader.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.emeraldpay.dshackle.reader - -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse - -typealias JsonRpcReader = Reader - -interface JsonRpcHttpReader : JsonRpcReader { - fun onStop() -} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/reader/RpcReaderFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/reader/RequestReaderFactory.kt similarity index 51% rename from src/main/kotlin/io/emeraldpay/dshackle/reader/RpcReaderFactory.kt rename to src/main/kotlin/io/emeraldpay/dshackle/reader/RequestReaderFactory.kt index 6ead791a..978fe091 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/reader/RpcReaderFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/reader/RequestReaderFactory.kt @@ -3,42 +3,42 @@ package io.emeraldpay.dshackle.reader import io.emeraldpay.dshackle.quorum.BroadcastQuorum import io.emeraldpay.dshackle.quorum.CallQuorum import io.emeraldpay.dshackle.quorum.MaximumValueQuorum -import io.emeraldpay.dshackle.quorum.QuorumRpcReader -import io.emeraldpay.dshackle.reader.RpcReader.Result +import io.emeraldpay.dshackle.quorum.QuorumRequestReader +import io.emeraldpay.dshackle.reader.RequestReader.Result +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import io.emeraldpay.dshackle.upstream.rpcclient.stream.Chunk import io.emeraldpay.dshackle.upstream.signature.ResponseSigner +import io.emeraldpay.dshackle.upstream.stream.Chunk import org.springframework.cloud.sleuth.Tracer import reactor.core.publisher.Flux import java.util.concurrent.atomic.AtomicInteger -abstract class RpcReader( +abstract class RequestReader( private val signer: ResponseSigner?, -) : Reader { +) : Reader { abstract fun attempts(): AtomicInteger - protected fun getError(key: JsonRpcRequest, err: Throwable) = + protected fun getError(key: ChainRequest, err: Throwable) = when (err) { - is RpcException -> JsonRpcException.from(err) - is JsonRpcException -> err - else -> JsonRpcException( - JsonRpcResponse.NumberId(key.id), - JsonRpcError(-32603, "Unhandled internal error: ${err.javaClass}: ${err.message}"), + is RpcException -> ChainException.from(err) + is ChainException -> err + else -> ChainException( + ChainResponse.NumberId(key.id), + ChainCallError(-32603, "Unhandled internal error: ${err.javaClass}: ${err.message}"), ) } - protected fun handleError(error: JsonRpcError?, id: Int, resolvedBy: String?) = - error?.asException(JsonRpcResponse.NumberId(id), resolvedBy) - ?: JsonRpcException(JsonRpcResponse.NumberId(id), JsonRpcError(-32603, "Unhandled Upstream error"), resolvedBy) + protected fun handleError(error: ChainCallError?, id: Int, resolvedBy: String?) = + error?.asException(ChainResponse.NumberId(id), resolvedBy) + ?: ChainException(ChainResponse.NumberId(id), ChainCallError(-32603, "Unhandled Upstream error"), resolvedBy) - protected fun getSignature(key: JsonRpcRequest, response: JsonRpcResponse, upstreamId: String) = + protected fun getSignature(key: ChainRequest, response: ChainResponse, upstreamId: String) = response.providedSignature ?: if (key.nonce != null) { signer?.sign(key.nonce, response.getResult(), upstreamId) @@ -55,29 +55,28 @@ abstract class RpcReader( ) } -interface RpcReaderFactory { +interface RequestReaderFactory { companion object { - fun default(): RpcReaderFactory { + fun default(): RequestReaderFactory { return Default() } } - fun create(data: RpcReaderData): RpcReader + fun create(data: ReaderData): RequestReader - class Default : RpcReaderFactory { - override fun create(data: RpcReaderData): RpcReader { + class Default : RequestReaderFactory { + override fun create(data: ReaderData): RequestReader { if (data.quorum is MaximumValueQuorum || data.quorum is BroadcastQuorum) { return BroadcastReader(data.multistream.getAll(), data.matcher, data.signer, data.quorum, data.tracer) } val apis = data.multistream.getApiSource(data.matcher) - return QuorumRpcReader(apis, data.quorum, data.signer, data.tracer) + return QuorumRequestReader(apis, data.quorum, data.signer, data.tracer) } } - data class RpcReaderData( + data class ReaderData( val multistream: Multistream, - val method: String, val matcher: Selector.Matcher, val quorum: CallQuorum, val signer: ResponseSigner?, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/reader/SpannedReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/reader/SpannedReader.kt index 43d43fb9..b33896b9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/reader/SpannedReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/reader/SpannedReader.kt @@ -7,7 +7,7 @@ import io.emeraldpay.dshackle.commons.SPAN_REQUEST_CANCELLED import io.emeraldpay.dshackle.commons.SPAN_REQUEST_INFO import io.emeraldpay.dshackle.commons.SPAN_STATUS_MESSAGE import io.emeraldpay.dshackle.data.HashId -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.ChainRequest import org.springframework.cloud.sleuth.Tracer import org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth import reactor.core.publisher.Mono @@ -50,7 +50,7 @@ class SpannedReader( private fun extractInfoFromKey(key: K): String? { return when (key) { - is JsonRpcRequest -> "method: ${key.method}" + is ChainRequest -> "method: ${key.method}" is HashId, Long -> "params: $key" else -> null } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index af8890d8..91b7f9e3 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -32,11 +32,14 @@ import io.emeraldpay.dshackle.commons.SPAN_STATUS_MESSAGE import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.quorum.CallQuorum import io.emeraldpay.dshackle.quorum.NotLaggingQuorum -import io.emeraldpay.dshackle.reader.RpcReader -import io.emeraldpay.dshackle.reader.RpcReaderFactory -import io.emeraldpay.dshackle.reader.RpcReaderFactory.RpcReaderData +import io.emeraldpay.dshackle.reader.RequestReader +import io.emeraldpay.dshackle.reader.RequestReaderFactory +import io.emeraldpay.dshackle.reader.RequestReaderFactory.ReaderData import io.emeraldpay.dshackle.reader.SpannedReader import io.emeraldpay.dshackle.upstream.ApiSource +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.Selector @@ -44,13 +47,11 @@ import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError import io.emeraldpay.dshackle.upstream.rpcclient.CallParams -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.rpcclient.ObjectParams -import io.emeraldpay.dshackle.upstream.rpcclient.stream.Chunk +import io.emeraldpay.dshackle.upstream.rpcclient.RestParams import io.emeraldpay.dshackle.upstream.signature.ResponseSigner +import io.emeraldpay.dshackle.upstream.stream.Chunk import io.micrometer.core.instrument.Metrics import org.apache.commons.lang3.StringUtils import org.reactivestreams.Publisher @@ -77,7 +78,7 @@ open class NativeCall( private val passthrough = config.passthrough - var rpcReaderFactory: RpcReaderFactory = RpcReaderFactory.default() + var requestReaderFactory: RequestReaderFactory = RequestReaderFactory.default() open fun nativeCall(requestMono: Mono): Flux { return nativeCallResult(requestMono) @@ -199,9 +200,8 @@ open class NativeCall( } } - fun parseParams(it: ValidCallContext): ValidCallContext { - val rawParams = extractParams(it.payload.params) - val params = it.requestDecorator.processRequest(rawParams) + fun parseParams(it: ValidCallContext): ValidCallContext { + val params = it.requestDecorator.processRequest(it.payload.params) return it.withPayload(ParsedCallDetails(it.payload.method, params)) } @@ -304,7 +304,11 @@ open class NativeCall( val requestId = requestItem.requestId val requestCount = request.itemsCount val method = requestItem.method - val params = requestItem.payload.toStringUtf8() + val params = if (requestItem.hasPayload()) { + requestItem.payload.toStringUtf8() + } else { + "" + } val availableMethods = upstream.getMethods() if (!availableMethods.isAvailable(method)) { @@ -314,7 +318,7 @@ open class NativeCall( CallError( requestItem.id, errorMessage, - JsonRpcError(RpcResponseError.CODE_METHOD_NOT_EXIST, errorMessage), + ChainCallError(RpcResponseError.CODE_METHOD_NOT_EXIST, errorMessage), null, ), requestId, @@ -356,7 +360,7 @@ open class NativeCall( upstream, matcher.build(), callQuorum, - RawCallDetails(method, params), + parsedCallDetails(requestItem), requestDecorator, resultDecorator, selector, @@ -367,6 +371,24 @@ open class NativeCall( } } + private fun parsedCallDetails(item: BlockchainOuterClass.NativeCallItem): ParsedCallDetails { + return if (item.hasPayload()) { + ParsedCallDetails(item.method, extractParams(item.payload.toStringUtf8())) + } else if (item.hasRestData()) { + ParsedCallDetails( + item.method, + RestParams( + item.restData.headersList.map { it.key to it.value }.toMap(), + item.restData.queryParamsList.map { it.key to it.value }.toMap(), + item.restData.pathParamsList, + item.restData.payload.toByteArray(), + ), + ) + } else { + throw IllegalStateException("Wrong payload type") + } + } + private fun getRequestDecorator(method: String): RequestDecorator = if (method in DefaultEthereumMethods.withFilterIdMethods) { WithFilterIdDecorator() @@ -381,7 +403,7 @@ open class NativeCall( return ctx.upstream.getLocalReader() .flatMap { api -> SpannedReader(api, tracer, LOCAL_READER) - .read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce, ctx.forwardedSelector)) + .read(ctx.payload.toChainRequest(ctx.nonce, ctx.forwardedSelector, false)) .map { val result = it.getResult() val upstreamId = it.providedUpstreamId ?: ctx.upstream.getId() @@ -405,13 +427,13 @@ open class NativeCall( if (!ctx.upstream.getMethods().isCallable(ctx.payload.method)) { return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method")) } - val reader = rpcReaderFactory.create( - RpcReaderData(ctx.upstream, ctx.payload.method, ctx.matcher, ctx.callQuorum, signer, tracer), + val reader = requestReaderFactory.create( + ReaderData(ctx.upstream, ctx.matcher, ctx.callQuorum, signer, tracer), ) val counter = reader.attempts() return SpannedReader(reader, tracer, RPC_READER) - .read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce, ctx.forwardedSelector, ctx.streamRequest)) + .read(ctx.payload.toChainRequest(ctx.nonce, ctx.forwardedSelector, ctx.streamRequest)) .map { val upId = it.resolvedBy?.getId() ?: ctx.upstream.getId() if (it.stream == null) { @@ -497,11 +519,11 @@ open class NativeCall( } interface ResultDecorator { - fun processResult(result: RpcReader.Result): ByteArray + fun processResult(result: RequestReader.Result): ByteArray } open class NoneResultDecorator : ResultDecorator { - override fun processResult(result: RpcReader.Result): ByteArray = result.value + override fun processResult(result: RequestReader.Result): ByteArray = result.value } open class CreateFilterDecorator : ResultDecorator { @@ -509,7 +531,7 @@ open class NativeCall( companion object { const val quoteCode = '"'.code.toByte() } - override fun processResult(result: RpcReader.Result): ByteArray { + override fun processResult(result: RequestReader.Result): ByteArray { val bytes = result.value if (bytes.last() == quoteCode && result.resolvedBy != null) { val suffix = result.resolvedBy.nodeId() @@ -624,7 +646,7 @@ open class NativeCall( data class CallError( val id: Int, val message: String, - val upstreamError: JsonRpcError?, + val upstreamError: ChainCallError?, val data: String?, val upstreamId: String? = null, ) { @@ -644,7 +666,7 @@ open class NativeCall( } fun from(t: Throwable): CallError { return when (t) { - is JsonRpcException -> CallError(t.error.code, t.error.message, t.error, getDataAsSting(t.error.details), t.upstreamId) + is ChainException -> CallError(t.error.code, t.error.message, t.error, getDataAsSting(t.error.details), t.upstreamId) is RpcException -> CallError(t.code, t.rpcMessage, null, getDataAsSting(t.details)) is CallFailure -> CallError(t.id, t.reason.message ?: "Upstream Error", null, null) else -> { @@ -704,6 +726,13 @@ open class NativeCall( } } - class RawCallDetails(val method: String, val params: String) - class ParsedCallDetails(val method: String, val params: CallParams) + class ParsedCallDetails(val method: String, val params: CallParams) { + fun toChainRequest( + nonce: Long?, + selector: BlockchainOuterClass.Selector?, + streamRequest: Boolean, + ): ChainRequest { + return ChainRequest(method, params, nonce, selector, streamRequest) + } + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/ConnectorFactoryCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/ConnectorFactoryCreator.kt index 873e10dd..d71cc297 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/ConnectorFactoryCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/ConnectorFactoryCreator.kt @@ -1,16 +1,18 @@ package io.emeraldpay.dshackle.startup.configure +import io.emeraldpay.dshackle.ApiType import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.upstream.BlockValidator -import io.emeraldpay.dshackle.upstream.HttpRpcFactory +import io.emeraldpay.dshackle.upstream.HttpFactory import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.generic.connectors.ConnectorFactory +import org.springframework.stereotype.Component import java.net.URI interface ConnectorFactoryCreator { - fun createConnectorFactoryCreator( + fun createConnectorFactory( id: String, conn: UpstreamsConfig.RpcConnection, chain: Chain, @@ -19,5 +21,18 @@ interface ConnectorFactoryCreator { chainsConf: ChainsConfig.ChainConfig, ): ConnectorFactory? - fun buildHttpFactory(conn: UpstreamsConfig.HttpEndpoint?, urls: ArrayList? = null): HttpRpcFactory? + fun buildHttpFactory(conn: UpstreamsConfig.HttpEndpoint?, urls: ArrayList? = null): HttpFactory? +} + +@Component +class ConnectorFactoryCreatorResolver( + private val genericConnectorFactoryCreator: GenericConnectorFactoryCreator, + private val restConnectorFactoryCreator: RestConnectorFactoryCreator, +) { + fun resolve(chain: Chain): ConnectorFactoryCreator { + if (chain.type.apiType == ApiType.REST) { + return restConnectorFactoryCreator + } + return genericConnectorFactoryCreator + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt index e5c9de67..fd7dabdb 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/EthereumUpstreamCreator.kt @@ -13,8 +13,8 @@ class EthereumUpstreamCreator( chainsConfig: ChainsConfig, indexConfig: IndexConfig, callTargets: CallTargetsHolder, - genericConnectorFactoryCreator: ConnectorFactoryCreator, -) : GenericUpstreamCreator(chainsConfig, indexConfig, callTargets, genericConnectorFactoryCreator) { + connectorFactoryCreatorResolver: ConnectorFactoryCreatorResolver, +) : GenericUpstreamCreator(chainsConfig, indexConfig, callTargets, connectorFactoryCreatorResolver) { override fun createUpstream( upstreamsConfig: UpstreamsConfig.Upstream<*>, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericConnectorFactoryCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericConnectorFactoryCreator.kt index 2b68dcee..661f1979 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericConnectorFactoryCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericConnectorFactoryCreator.kt @@ -4,8 +4,9 @@ import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.upstream.BasicHttpFactory import io.emeraldpay.dshackle.upstream.BlockValidator -import io.emeraldpay.dshackle.upstream.HttpRpcFactory +import io.emeraldpay.dshackle.upstream.HttpFactory import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionFactory import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionPoolFactory import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice @@ -17,16 +18,16 @@ import reactor.core.scheduler.Scheduler import java.net.URI @Component -class GenericConnectorFactoryCreator( +open class GenericConnectorFactoryCreator( private val fileResolver: FileResolver, private val wsConnectionResubscribeScheduler: Scheduler, private val headScheduler: Scheduler, private val wsScheduler: Scheduler, private val headLivenessScheduler: Scheduler, ) : ConnectorFactoryCreator { - private val log = LoggerFactory.getLogger(this::class.java) + protected val log = LoggerFactory.getLogger(this::class.java) - override fun createConnectorFactoryCreator( + override fun createConnectorFactory( id: String, conn: UpstreamsConfig.RpcConnection, chain: Chain, @@ -57,7 +58,7 @@ class GenericConnectorFactoryCreator( return connectorFactory } - override fun buildHttpFactory(conn: UpstreamsConfig.HttpEndpoint?, urls: ArrayList?): HttpRpcFactory? { + override fun buildHttpFactory(conn: UpstreamsConfig.HttpEndpoint?, urls: ArrayList?): HttpFactory? { return conn?.let { endpoint -> val tls = conn.tls?.let { tls -> tls.ca?.let { ca -> @@ -65,7 +66,7 @@ class GenericConnectorFactoryCreator( } } urls?.add(endpoint.url) - HttpRpcFactory(endpoint.url.toString(), conn.basicAuth, tls) + BasicHttpFactory(endpoint.url.toString(), conn.basicAuth, tls) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt index c616a9fa..58e40a33 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/GenericUpstreamCreator.kt @@ -21,7 +21,7 @@ open class GenericUpstreamCreator( chainsConfig: ChainsConfig, indexConfig: IndexConfig, callTargets: CallTargetsHolder, - private val genericConnectorFactoryCreator: ConnectorFactoryCreator, + private val connectorFactoryCreatorResolver: ConnectorFactoryCreatorResolver, ) : UpstreamCreator(chainsConfig, indexConfig, callTargets) { private val hashes: MutableMap = HashMap() @@ -58,7 +58,7 @@ open class GenericUpstreamCreator( val cs = ChainSpecificRegistry.resolve(chain) - val connectorFactory = genericConnectorFactoryCreator.createConnectorFactoryCreator( + val connectorFactory = connectorFactoryCreatorResolver.resolve(chain).createConnectorFactory( config.id!!, connection, chain, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/RestConnectorFactoryCreator.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/RestConnectorFactoryCreator.kt new file mode 100644 index 00000000..a566bc6f --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/configure/RestConnectorFactoryCreator.kt @@ -0,0 +1,55 @@ +package io.emeraldpay.dshackle.startup.configure + +import io.emeraldpay.dshackle.Chain +import io.emeraldpay.dshackle.FileResolver +import io.emeraldpay.dshackle.config.ChainsConfig +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.upstream.BlockValidator +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice +import io.emeraldpay.dshackle.upstream.generic.connectors.ConnectorFactory +import io.emeraldpay.dshackle.upstream.generic.connectors.RestConnectorFactory +import org.springframework.stereotype.Component +import reactor.core.scheduler.Scheduler +import reactor.core.scheduler.Schedulers +import java.net.URI + +@Component +class RestConnectorFactoryCreator( + fileResolver: FileResolver, + private val headScheduler: Scheduler, + private val headLivenessScheduler: Scheduler, +) : GenericConnectorFactoryCreator( + fileResolver, + Schedulers.single(), + headScheduler, + Schedulers.single(), + headLivenessScheduler, +) { + override fun createConnectorFactory( + id: String, + conn: UpstreamsConfig.RpcConnection, + chain: Chain, + forkChoice: ForkChoice, + blockValidator: BlockValidator, + chainsConf: ChainsConfig.ChainConfig, + ): ConnectorFactory? { + val urls = ArrayList() + val httpFactory = buildHttpFactory(conn.rpc, urls) + log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}") + + val connectorFactory = + RestConnectorFactory( + httpFactory, + forkChoice, + blockValidator, + headScheduler, + headLivenessScheduler, + chainsConf.expectedBlockTime, + ) + if (!connectorFactory.isValid()) { + log.warn("Upstream configuration is invalid - no http endpoint") + return null + } + return connectorFactory + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/BasicEthUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BasicEthUpstreamValidator.kt new file mode 100644 index 00000000..602bfb2a --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BasicEthUpstreamValidator.kt @@ -0,0 +1,94 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.Defaults +import io.emeraldpay.dshackle.foundation.ChainOptions +import reactor.core.publisher.Mono +import java.util.concurrent.TimeoutException +import java.util.function.Supplier + +abstract class BasicEthUpstreamValidator( + upstream: Upstream, + options: ChainOptions.Options, +) : UpstreamValidator(upstream, options) { + + override fun validate(): Mono { + return Mono.zip( + validatorFunctions().map { it.get() }, + ) { a -> a.map { it as UpstreamAvailability } } + .map(::resolve) + .defaultIfEmpty(UpstreamAvailability.UNAVAILABLE) + .onErrorResume { + log.error("Error during upstream validation for ${upstream.getId()}", it) + Mono.just(UpstreamAvailability.UNAVAILABLE) + } + } + + protected fun validateSyncing(): Mono { + if (!options.validateSyncing) { + return Mono.just(UpstreamAvailability.OK) + } + val validateSyncingRequest = validateSyncingRequest() + return upstream.getIngressReader() + .read(validateSyncingRequest.request) + .flatMap(ChainResponse::requireResult) + .map { validateSyncingRequest.mapper(it) } + .timeout( + Defaults.timeoutInternal, + Mono.fromCallable { log.warn("No response for ${validateSyncingRequest.request.method} from ${upstream.getId()}") } + .then(Mono.error(TimeoutException("Validation timeout for Syncing"))), + ) + .map { + upstream.getHead().onSyncingNode(it) + if (it) { + UpstreamAvailability.SYNCING + } else { + UpstreamAvailability.OK + } + } + .doOnError { err -> log.error("Error during syncing validation for ${upstream.getId()}", err) } + .onErrorReturn(UpstreamAvailability.UNAVAILABLE) + } + + protected fun validatePeers(): Mono { + if (!options.validatePeers || options.minPeers == 0) { + return Mono.just(UpstreamAvailability.OK) + } + val validatePeersRequest = validatePeersRequest() + return upstream + .getIngressReader() + .read(validatePeersRequest.request) + .flatMap(ChainResponse::checkError) + .map { validatePeersRequest.mapper(it) } + .timeout( + Defaults.timeoutInternal, + Mono.fromCallable { log.warn("No response for ${validatePeersRequest.request.method} from ${upstream.getId()}") } + .then(Mono.error(TimeoutException("Validation timeout for Peers"))), + ) + .map { count -> + val minPeers = options.minPeers + if (count < minPeers) { + UpstreamAvailability.IMMATURE + } else { + UpstreamAvailability.OK + } + } + .doOnError { err -> log.error("Error during peer count validation for ${upstream.getId()}", err) } + .onErrorReturn(UpstreamAvailability.UNAVAILABLE) + } + + protected abstract fun validateSyncingRequest(): ValidateSyncingRequest + + protected abstract fun validatePeersRequest(): ValidatePeersRequest + + protected abstract fun validatorFunctions(): List>> + + data class ValidateSyncingRequest( + val request: ChainRequest, + val mapper: (ByteArray) -> Boolean, + ) + + data class ValidatePeersRequest( + val request: ChainRequest, + val mapper: (ChainResponse) -> Int, + ) +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BasicHttpFactory.kt similarity index 70% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/BasicHttpFactory.kt index c325b1a3..af1d749c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpRpcFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/BasicHttpFactory.kt @@ -1,28 +1,28 @@ package io.emeraldpay.dshackle.upstream +import io.emeraldpay.dshackle.ApiType import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.AuthConfig -import io.emeraldpay.dshackle.reader.JsonRpcHttpReader -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcHttpClient -import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics +import io.emeraldpay.dshackle.upstream.restclient.RestHttpReader +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcHttpReader import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.Metrics import io.micrometer.core.instrument.Tag import io.micrometer.core.instrument.Timer -open class HttpRpcFactory( +class BasicHttpFactory( private val url: String, private val basicAuth: AuthConfig.ClientBasicAuth?, private val tls: ByteArray?, ) : HttpFactory { - override fun create(id: String?, chain: Chain): JsonRpcHttpReader { + override fun create(id: String?, chain: Chain): HttpReader { val metricsTags = listOf( // "unknown" is not supposed to happen Tag.of("upstream", id ?: "unknown"), // UNSPECIFIED shouldn't happen too Tag.of("chain", chain.chainCode), ) - val metrics = RpcMetrics( + val metrics = RequestMetrics( Timer.builder("upstream.rpc.conn") .description("Request time through a HTTP JSON RPC connection") .tags(metricsTags) @@ -33,11 +33,10 @@ open class HttpRpcFactory( .tags(metricsTags) .register(Metrics.globalRegistry), ) - return JsonRpcHttpClient( - url, - metrics, - basicAuth, - tls, - ) + + if (chain.type.apiType == ApiType.REST) { + return RestHttpReader(url, metrics, basicAuth, tls) + } + return JsonRpcHttpReader(url, metrics, basicAuth, tls) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt index e3557614..b6713523 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt @@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.BlockchainType.BITCOIN import io.emeraldpay.dshackle.BlockchainType.ETHEREUM +import io.emeraldpay.dshackle.BlockchainType.ETHEREUM_BEACON_CHAIN import io.emeraldpay.dshackle.BlockchainType.NEAR import io.emeraldpay.dshackle.BlockchainType.POLKADOT import io.emeraldpay.dshackle.BlockchainType.SOLANA @@ -9,6 +10,7 @@ import io.emeraldpay.dshackle.BlockchainType.STARKNET import io.emeraldpay.dshackle.BlockchainType.UNKNOWN import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.emeraldpay.dshackle.upstream.calls.DefaultBeaconChainMethods import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.DefaultPolkadotMethods @@ -31,6 +33,7 @@ class CallTargetsHolder { POLKADOT -> DefaultPolkadotMethods() SOLANA -> DefaultSolanaMethods() NEAR -> DefaultNearMethods() + ETHEREUM_BEACON_CHAIN -> DefaultBeaconChainMethods() UNKNOWN -> throw IllegalArgumentException("unknown chain") } callTargets[chain] = created diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcError.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainCallError.kt similarity index 64% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcError.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainCallError.kt index 42a5886f..9677f519 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcError.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainCallError.kt @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream.rpcclient +package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException -data class JsonRpcError(val code: Int, val message: String, val details: Any?) { +data class ChainCallError(val code: Int, val message: String, val details: Any?) { constructor(code: Int, message: String) : this(code, message, null) companion object { @JvmStatic - fun from(err: RpcException): JsonRpcError { - return JsonRpcError( + fun from(err: RpcException): ChainCallError { + return ChainCallError( err.code, err.rpcMessage, err.details, @@ -32,11 +32,11 @@ data class JsonRpcError(val code: Int, val message: String, val details: Any?) { } } - fun asException(id: JsonRpcResponse.Id?): JsonRpcException { - return JsonRpcUpstreamException(id ?: JsonRpcResponse.NumberId(-1), this) + fun asException(id: ChainResponse.Id?): ChainException { + return ChainCallUpstreamException(id ?: ChainResponse.NumberId(-1), this) } - fun asException(id: JsonRpcResponse.Id?, upstreamId: String?): JsonRpcException { - return JsonRpcException(id ?: JsonRpcResponse.NumberId(-1), this, upstreamId, false) + fun asException(id: ChainResponse.Id?, upstreamId: String?): ChainException { + return ChainException(id ?: ChainResponse.NumberId(-1), this, upstreamId, false) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainCallUpstreamException.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainCallUpstreamException.kt new file mode 100644 index 00000000..b3035772 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainCallUpstreamException.kt @@ -0,0 +1,6 @@ +package io.emeraldpay.dshackle.upstream + +class ChainCallUpstreamException( + id: ChainResponse.Id, + error: ChainCallError, +) : ChainException(id, error, null, false) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcException.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainException.kt similarity index 65% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcException.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainException.kt index 8cd5c1e8..2559aa8a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcException.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainException.kt @@ -13,34 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream.rpcclient +package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException -open class JsonRpcException( - val id: JsonRpcResponse.Id, - val error: JsonRpcError, +open class ChainException( + val id: ChainResponse.Id, + val error: ChainCallError, val upstreamId: String? = null, writableStackTrace: Boolean = true, cause: Throwable? = null, ) : Exception(error.message, cause, true, writableStackTrace) { - constructor(id: Int, message: String) : this(JsonRpcResponse.NumberId(id), JsonRpcError(-32005, message)) + constructor(id: Int, message: String) : this(ChainResponse.NumberId(id), ChainCallError(-32005, message)) - constructor(id: Int, message: String, cause: Throwable) : this(JsonRpcResponse.NumberId(id), JsonRpcError(-32005, message), cause = cause) + constructor(id: Int, message: String, cause: Throwable) : this(ChainResponse.NumberId(id), ChainCallError(-32005, message), cause = cause) companion object { - fun from(err: RpcException): JsonRpcException { + fun from(err: RpcException): ChainException { val id = err.details?.let { - if (it is JsonRpcResponse.Id) { + if (it is ChainResponse.Id) { it } else { - JsonRpcResponse.NumberId(-3) + ChainResponse.NumberId(-3) } - } ?: JsonRpcResponse.NumberId(-4) - return JsonRpcException( + } ?: ChainResponse.NumberId(-4) + return ChainException( id, - JsonRpcError.from(err), + ChainCallError.from(err), ) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcRequest.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainRequest.kt similarity index 77% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcRequest.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainRequest.kt index 853f9ba7..be155b5a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcRequest.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainRequest.kt @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream.rpcclient +package io.emeraldpay.dshackle.upstream import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonNode import io.emeraldpay.api.proto.BlockchainOuterClass -import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.rpcclient.CallParams +import io.emeraldpay.dshackle.upstream.rpcclient.ListParams -data class JsonRpcRequest( +data class ChainRequest( val method: String, val params: CallParams, val id: Int, @@ -40,16 +41,7 @@ data class JsonRpcRequest( ) : this(method, params, 1, nonce, selectors, isStreamed) fun toJson(): ByteArray { - val json = mapOf( - "jsonrpc" to "2.0", - "id" to id, - "method" to method, - "params" to when (params) { - is ListParams -> params.list - is ObjectParams -> params.obj - }, - ) - return Global.objectMapper.writeValueAsBytes(json) + return params.toJson(id, method) } override fun toString(): String { @@ -57,9 +49,9 @@ data class JsonRpcRequest( } @Suppress("UNCHECKED_CAST") - class Deserializer : JsonDeserializer() { + class Deserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): JsonRpcRequest { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): ChainRequest { val node: JsonNode = p.readValueAsTree() val id = node.get("id").intValue() val method = node.get("method").textValue() @@ -76,7 +68,7 @@ data class JsonRpcRequest( throw IllegalStateException("Unsupported param type: ${it.asToken()}") } } - return JsonRpcRequest(method, ListParams(params as List), id, null, null) + return ChainRequest(method, ListParams(params as List), id, null, null) } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcResponse.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainResponse.kt similarity index 81% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcResponse.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainResponse.kt index 90ed322f..a0644765 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcResponse.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainResponse.kt @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream.rpcclient +package io.emeraldpay.dshackle.upstream import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.SerializerProvider -import io.emeraldpay.dshackle.upstream.rpcclient.stream.Chunk import io.emeraldpay.dshackle.upstream.signature.ResponseSigner +import io.emeraldpay.dshackle.upstream.stream.Chunk import reactor.core.publisher.Flux import reactor.core.publisher.Mono -class JsonRpcResponse( +class ChainResponse( private val result: ByteArray?, - val error: JsonRpcError?, + val error: ChainCallError?, val id: Id, val stream: Flux?, /** @@ -38,42 +38,42 @@ class JsonRpcResponse( constructor(stream: Flux, id: Int) : this(null, null, NumberId(id.toLong()), stream, null, null) - constructor(result: ByteArray?, error: JsonRpcError?) : this(result, error, NumberId(0), null) + constructor(result: ByteArray?, error: ChainCallError?) : this(result, error, NumberId(0), null) - constructor(result: ByteArray?, error: JsonRpcError?, resolvedBy: String?) : + constructor(result: ByteArray?, error: ChainCallError?, resolvedBy: String?) : this(result, error, NumberId(0), null, null, resolvedBy) companion object { private val NULL_VALUE = "null".toByteArray() @JvmStatic - fun ok(value: ByteArray): JsonRpcResponse { - return JsonRpcResponse(value, null) + fun ok(value: ByteArray): ChainResponse { + return ChainResponse(value, null) } @JvmStatic - fun ok(value: ByteArray, id: Id): JsonRpcResponse { - return JsonRpcResponse(value, null, id, null) + fun ok(value: ByteArray, id: Id): ChainResponse { + return ChainResponse(value, null, id, null) } @JvmStatic - fun ok(value: String): JsonRpcResponse { - return JsonRpcResponse(value.toByteArray(), null) + fun ok(value: String): ChainResponse { + return ChainResponse(value.toByteArray(), null) } @JvmStatic - fun error(code: Int, msg: String): JsonRpcResponse { - return JsonRpcResponse(null, JsonRpcError(code, msg)) + fun error(code: Int, msg: String): ChainResponse { + return ChainResponse(null, ChainCallError(code, msg)) } @JvmStatic - fun error(error: JsonRpcError, id: Id): JsonRpcResponse { - return JsonRpcResponse(null, error, id, null) + fun error(error: ChainCallError, id: Id): ChainResponse { + return ChainResponse(null, error, id, null) } @JvmStatic - fun error(code: Int, msg: String, id: Id): JsonRpcResponse { - return JsonRpcResponse(null, JsonRpcError(code, msg), id, null) + fun error(code: Int, msg: String, id: Id): ChainResponse { + return ChainResponse(null, ChainCallError(code, msg), id, null) } } @@ -126,13 +126,21 @@ class JsonRpcResponse( } } - fun copyWithId(id: Id): JsonRpcResponse { - return JsonRpcResponse(result, error, id, stream, providedSignature, providedUpstreamId) + fun checkError(): Mono { + return if (error != null) { + Mono.error(error.asException(id)) + } else { + Mono.just(this) + } + } + + fun copyWithId(id: Id): ChainResponse { + return ChainResponse(result, error, id, stream, providedSignature, providedUpstreamId) } override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is JsonRpcResponse) return false + if (other !is ChainResponse) return false if (result != null) { if (other.result == null) return false @@ -238,8 +246,8 @@ class JsonRpcResponse( } } - class ResponseJsonSerializer : JsonSerializer() { - override fun serialize(value: JsonRpcResponse, gen: JsonGenerator, serializers: SerializerProvider) { + class ResponseJsonSerializer : JsonSerializer() { + override fun serialize(value: ChainResponse, gen: JsonGenerator, serializers: SerializerProvider) { gen.writeStartObject() gen.writeStringField("jsonrpc", "2.0") if (value.id.isNumber()) { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt index 11289871..f1644934 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpFactory.kt @@ -1,8 +1,7 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.Chain -import io.emeraldpay.dshackle.reader.JsonRpcHttpReader interface HttpFactory { - fun create(id: String?, chain: Chain): JsonRpcHttpReader + fun create(id: String?, chain: Chain): HttpReader } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpReader.kt new file mode 100644 index 00000000..1ea63779 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/HttpReader.kt @@ -0,0 +1,114 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.config.AuthConfig +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException +import io.micrometer.core.instrument.Metrics +import io.netty.handler.codec.http.HttpHeaderNames +import io.netty.handler.codec.http.HttpHeaders +import io.netty.handler.ssl.SslContextBuilder +import io.netty.resolver.DefaultAddressResolverGroup +import reactor.core.publisher.Mono +import reactor.netty.http.client.HttpClient +import reactor.netty.resources.ConnectionProvider +import java.io.ByteArrayInputStream +import java.security.KeyStore +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.util.Base64 +import java.util.function.Consumer +import java.util.function.Function + +abstract class HttpReader( + protected val target: String, + protected val metrics: RequestMetrics, + basicAuth: AuthConfig.ClientBasicAuth? = null, + tlsCAAuth: ByteArray? = null, +) : ChainReader { + + protected val httpClient: HttpClient + + init { + val connectionProvider = ConnectionProvider.builder("dshackleConnectionPool") + .maxConnections(1500) + .pendingAcquireMaxCount(10000) + .build() + + var build = HttpClient.create(connectionProvider) + .compress(true) + .resolver(DefaultAddressResolverGroup.INSTANCE) + + build = build.headers { h -> + h.add(HttpHeaderNames.CONTENT_TYPE, "application/json") + } + + basicAuth?.let { auth -> + val authString: String = auth.username + ":" + auth.password + val authBase64 = Base64.getEncoder().encodeToString(authString.toByteArray()) + val encodedAuth = "Basic $authBase64" + val headers = Consumer { h: HttpHeaders -> h.add(HttpHeaderNames.AUTHORIZATION, encodedAuth) } + build = build.headers(headers) + } + + tlsCAAuth?.let { auth -> + val cf = CertificateFactory.getInstance("X.509") + val cert = cf.generateCertificate(ByteArrayInputStream(auth)) as X509Certificate + val ks = KeyStore.getInstance(KeyStore.getDefaultType()) + ks.load(null, "".toCharArray()) + ks.setCertificateEntry("server", cert) + val sslContext = SslContextBuilder.forClient().trustManager(cert).build() + + build.secure { spec -> + spec.sslContext(sslContext) + } + } + + this.httpClient = build + } + + override fun read(key: ChainRequest): Mono { + return internalRead(key) + .transform(convertErrors(key)) + .transform(throwIfError()) + } + + protected abstract fun internalRead(key: ChainRequest): Mono + + fun onStop() { + Metrics.globalRegistry.remove(metrics.timer) + Metrics.globalRegistry.remove(metrics.fails) + } + + /** + * The subscribers expect to catch an exception if the response contains JSON RPC Error. Convert it here to JsonRpcException + */ + private fun throwIfError(): Function, Mono> { + return Function { resp -> + resp.flatMap { + if (it.hasError()) { + Mono.error(ChainCallUpstreamException(it.id, it.error!!)) + } else { + Mono.just(it) + } + } + } + } + + /** + * Convert internal exceptions to standard JsonRpcException + */ + private fun convertErrors(key: ChainRequest): Function, Mono> { + return Function { resp -> + resp.onErrorResume { t -> + val err = when (t) { + is RpcException -> ChainException.from(t) + is ChainException -> t + else -> ChainException(key.id, t.message ?: t.javaClass.name, cause = t) + } + // here we're measure the internal errors, not upstream errors + metrics.fails.increment() + Mono.error(err) + } + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/LabelsDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/LabelsDetector.kt index d9884bd6..1b8d36c0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/LabelsDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/LabelsDetector.kt @@ -1,10 +1,80 @@ package io.emeraldpay.dshackle.upstream +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.module.kotlin.readValue import io.emeraldpay.dshackle.Chain -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.reader.ChainReader +import org.slf4j.LoggerFactory import reactor.core.publisher.Flux -typealias LabelsDetectorBuilder = (Chain, JsonRpcReader) -> LabelsDetector? +typealias LabelsDetectorBuilder = (Chain, ChainReader) -> LabelsDetector? interface LabelsDetector { fun detectLabels(): Flux> } + +abstract class BasicEthLabelsDetector( + private val reader: ChainReader, +) : LabelsDetector { + private val log = LoggerFactory.getLogger(this::class.java) + + protected abstract fun nodeTypeRequest(): NodeTypeRequest + + protected fun detectNodeType(): Flux?> { + val nodeTypeRequest = nodeTypeRequest() + return reader + .read(nodeTypeRequest.request) + .flatMap(ChainResponse::requireResult) + .map { Global.objectMapper.readValue(it) } + .flatMapMany { node -> + val mappedNode = nodeTypeRequest.mapper(node) + val labels = mutableListOf>() + if (mappedNode.isTextual) { + clientType(mappedNode.textValue())?.let { + labels.add("client_type" to it) + } + clientVersion(mappedNode.textValue())?.let { + labels.add("client_version" to it) + } + } + + Flux.fromIterable(labels) + } + .onErrorResume { + Flux.empty() + } + } + + private fun clientVersion(client: String): String? { + val firstSlash = client.indexOf("/") + val secondSlash = client.indexOf("/", firstSlash + 1) + if (firstSlash == -1 || secondSlash == -1 || secondSlash < firstSlash) { + return null + } + return client.substring(firstSlash + 1, secondSlash) + } + + private fun clientType(client: String): String? { + return if (client.contains("erigon", true)) { + "erigon" + } else if (client.contains("geth", true)) { + "geth" + } else if (client.contains("bor", true)) { + "bor" + } else if (client.contains("nethermind", true)) { + "nethermind" + } else if (client.contains("prysm", true)) { + "prysm" + } else if (client.contains("lighthouse", true)) { + "lighthouse" + } else { + log.debug("Unknown client type: {}", client) + null + } + } + + data class NodeTypeRequest( + val request: ChainRequest, + val mapper: (JsonNode) -> JsonNode, + ) +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt index 31fef318..14475d12 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt @@ -22,7 +22,7 @@ import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.startup.UpstreamChangeEvent import io.emeraldpay.dshackle.upstream.calls.AggregatedCallMethods @@ -216,9 +216,9 @@ abstract class Multistream( /** * Finds an API that leverages caches and other optimizations/transformations of the request. */ - abstract fun getLocalReader(): Mono + abstract fun getLocalReader(): Mono - override fun getIngressReader(): JsonRpcReader { + override fun getIngressReader(): ChainReader { throw NotImplementedError("Immediate direct API is not implemented for Aggregated Upstream") } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/RpcMetrics.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/RequestMetrics.kt similarity index 91% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/RpcMetrics.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/RequestMetrics.kt index 4ef46831..5344df9d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/RpcMetrics.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/RequestMetrics.kt @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream.rpcclient +package io.emeraldpay.dshackle.upstream import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.Timer -class RpcMetrics( +class RequestMetrics( val timer: Timer, val fails: Counter, ) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt index 50fabeb2..aac568f8 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt @@ -18,7 +18,7 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.startup.UpstreamChangeEvent import io.emeraldpay.dshackle.upstream.calls.CallMethods import reactor.core.publisher.Flux @@ -33,7 +33,7 @@ interface Upstream : Lifecycle { /** * Get an actual reader that access the current upstream */ - fun getIngressReader(): JsonRpcReader + fun getIngressReader(): ChainReader fun getOptions(): ChainOptions.Options fun getRole(): UpstreamsConfig.UpstreamRole diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt index 4a4ac1c3..207e324a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt @@ -4,7 +4,6 @@ import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.foundation.ChainOptions import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import org.slf4j.LoggerFactory import reactor.core.publisher.Flux import reactor.core.publisher.Mono @@ -16,9 +15,8 @@ abstract class UpstreamValidator( val upstream: Upstream, val options: ChainOptions.Options, ) { - companion object { - private val log = LoggerFactory.getLogger(UpstreamValidator::class.java) - } + protected val log = LoggerFactory.getLogger(this::class.java) + fun start(): Flux { return Flux.interval( Duration.ZERO, @@ -39,6 +37,14 @@ abstract class UpstreamValidator( fun validateUpstreamSettingsOnStartup(): ValidateUpstreamSettingsResult { return validateUpstreamSettings().block() ?: ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR } + + companion object { + @JvmStatic + fun resolve(results: Iterable): UpstreamAvailability { + val cp = Comparator { avail1: UpstreamAvailability, avail2: UpstreamAvailability -> if (avail1.isBetterTo(avail2)) -1 else 1 } + return results.sortedWith(cp).last() + } + } } enum class ValidateUpstreamSettingsResult { @@ -48,6 +54,6 @@ enum class ValidateUpstreamSettingsResult { } data class SingleCallValidator( - val method: JsonRpcRequest, + val method: ChainRequest, val check: (ByteArray) -> UpstreamAvailability, ) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLabelsDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLabelsDetector.kt new file mode 100644 index 00000000..0c6a8f8d --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLabelsDetector.kt @@ -0,0 +1,27 @@ +package io.emeraldpay.dshackle.upstream.beaconchain + +import com.fasterxml.jackson.databind.node.NullNode +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.BasicEthLabelsDetector +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.rpcclient.RestParams +import reactor.core.publisher.Flux + +class BeaconChainLabelsDetector( + reader: ChainReader, +) : BasicEthLabelsDetector(reader) { + + override fun nodeTypeRequest(): NodeTypeRequest { + return NodeTypeRequest( + ChainRequest("GET#/eth/v1/node/version", RestParams.emptyParams()), + ) { node -> + node.get("data")?.get("version") ?: NullNode.instance + } + } + + override fun detectLabels(): Flux> { + return Flux.merge( + detectNodeType(), + ) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLowerBoundBlockDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLowerBoundBlockDetector.kt new file mode 100644 index 00000000..12d62e76 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainLowerBoundBlockDetector.kt @@ -0,0 +1,21 @@ +package io.emeraldpay.dshackle.upstream.beaconchain + +import io.emeraldpay.dshackle.Chain +import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector +import io.emeraldpay.dshackle.upstream.Upstream +import reactor.core.publisher.Mono + +class BeaconChainLowerBoundBlockDetector( + chain: Chain, + upstream: Upstream, +) : LowerBoundBlockDetector(chain, upstream) { + + // TODO: consensus nodes could be launched either in full mode or in archive mode + override fun lowerBlockDetect(): Mono { + return Mono.just(LowerBlockData(1)) + } + + override fun periodRequest(): Long { + return 120 + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecific.kt new file mode 100644 index 00000000..4dec9430 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecific.kt @@ -0,0 +1,95 @@ +package io.emeraldpay.dshackle.upstream.beaconchain + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.module.kotlin.readValue +import io.emeraldpay.dshackle.Chain +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.config.ChainsConfig +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.foundation.ChainOptions +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.LabelsDetector +import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.UpstreamValidator +import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific +import io.emeraldpay.dshackle.upstream.rpcclient.RestParams +import java.math.BigInteger +import java.time.Instant + +object BeaconChainSpecific : AbstractPollChainSpecific() { + override fun parseHeader(data: ByteArray, upstreamId: String): BlockContainer { + throw NotImplementedError() + } + + override fun listenNewHeadsRequest(): ChainRequest { + throw NotImplementedError() + } + + override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest { + throw NotImplementedError() + } + + override fun latestBlockRequest(): ChainRequest { + return ChainRequest("GET#/eth/v1/beacon/headers/head", RestParams.emptyParams()) + } + + override fun parseBlock(data: ByteArray, upstreamId: String): BlockContainer { + val blockHeader = Global.objectMapper.readValue(data) + + return BlockContainer( + height = blockHeader.height, + hash = BlockId.from(blockHeader.hash), + difficulty = BigInteger.ZERO, + timestamp = Instant.EPOCH, + full = false, + json = data, + parsed = blockHeader, + transactions = emptyList(), + upstreamId = upstreamId, + parentHash = BlockId.from(blockHeader.parentHash), + ) + } + + override fun labelDetector(chain: Chain, reader: ChainReader): LabelsDetector { + return BeaconChainLabelsDetector(reader) + } + + override fun validator( + chain: Chain, + upstream: Upstream, + options: ChainOptions.Options, + config: ChainsConfig.ChainConfig, + ): UpstreamValidator { + return BeaconChainValidator(upstream, options) + } + + override fun lowerBoundBlockDetector(chain: Chain, upstream: Upstream): LowerBoundBlockDetector { + return BeaconChainLowerBoundBlockDetector(chain, upstream) + } +} + +data class BeaconChainBlockHeader( + val hash: String, + val parentHash: String, + val height: Long, +) + +class BeaconChainBlockHeaderDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BeaconChainBlockHeader { + val node = p.readValueAsTree() + val data = node["data"] + + val hash = data["root"].textValue() + val headerData = data["header"]["message"] + val height = headerData["slot"].textValue().toLong() + val parentHash = headerData["parent_root"].textValue() + + return BeaconChainBlockHeader(hash, parentHash, height) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidator.kt new file mode 100644 index 00000000..b35f6549 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidator.kt @@ -0,0 +1,88 @@ +package io.emeraldpay.dshackle.upstream.beaconchain + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import io.emeraldpay.dshackle.Defaults +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.foundation.ChainOptions +import io.emeraldpay.dshackle.upstream.BasicEthUpstreamValidator +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.UpstreamAvailability +import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult +import io.emeraldpay.dshackle.upstream.rpcclient.RestParams +import reactor.core.publisher.Mono +import java.util.concurrent.TimeoutException +import java.util.function.Supplier + +class BeaconChainValidator( + upstream: Upstream, + options: ChainOptions.Options, +) : BasicEthUpstreamValidator(upstream, options) { + + override fun validatorFunctions(): List>> { + return listOf( + Supplier { validateSyncing() }, + Supplier { validateHealth() }, + Supplier { validatePeers() }, + ) + } + + override fun validateUpstreamSettings(): Mono { + return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID) + } + + private fun validateHealth(): Mono { + return upstream.getIngressReader() + .read(ChainRequest("GET#/eth/v1/node/health", RestParams.emptyParams())) + .flatMap(ChainResponse::requireResult) + .map { UpstreamAvailability.OK } + .timeout( + Defaults.timeoutInternal, + Mono.fromCallable { log.warn("No response for /eth/v1/node/health from ${upstream.getId()}") } + .then(Mono.error(TimeoutException("Validation timeout for /eth/v1/node/health"))), + ) + .doOnError { err -> log.error("Error during /eth/v1/node/health validation for ${upstream.getId()}", err) } + .onErrorReturn(UpstreamAvailability.UNAVAILABLE) + } + + override fun validateSyncingRequest(): ValidateSyncingRequest { + return ValidateSyncingRequest( + ChainRequest("GET#/eth/v1/node/syncing", RestParams.emptyParams()), + ) { bytes -> Global.objectMapper.readValue(bytes, BeaconChainSyncing::class.java).data.isSyncing } + } + + override fun validatePeersRequest(): ValidatePeersRequest { + return ValidatePeersRequest( + ChainRequest("GET#/eth/v1/node/peer_count", RestParams.emptyParams()), + ) { resp -> + Global.objectMapper.readValue( + resp.getResult(), + BeaconChainPeers::class.java, + ).data.connected.toInt() + } + } + + private data class BeaconChainSyncing( + @JsonProperty("data") + val data: BeaconChainSyncingData, + ) + + @JsonIgnoreProperties(ignoreUnknown = true) + private data class BeaconChainSyncingData( + @JsonProperty("is_syncing") + val isSyncing: Boolean, + ) + + private data class BeaconChainPeers( + @JsonProperty("data") + val data: BeaconChainPeersData, + ) + + @JsonIgnoreProperties(ignoreUnknown = true) + private data class BeaconChainPeersData( + @JsonProperty("connected") + val connected: String, + ) +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt index 29556787..26ca1eb2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinMultistream.kt @@ -18,7 +18,7 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.CachingReader import io.emeraldpay.dshackle.upstream.DistanceExtractor import io.emeraldpay.dshackle.upstream.EgressSubscription @@ -91,7 +91,7 @@ open class BitcoinMultistream( /** * Finds an API that executed directly on a remote. */ - open fun getDirectApi(matcher: Selector.Matcher): Mono { + open fun getDirectApi(matcher: Selector.Matcher): Mono { val apis = getApiSource(matcher) apis.request(1) return Mono.from(apis) @@ -99,7 +99,7 @@ open class BitcoinMultistream( .switchIfEmpty(Mono.error(Exception("No API available for $chain"))) } - override fun getLocalReader(): Mono { + override fun getLocalReader(): Mono { return Mono.just(callRouter) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinReader.kt index f8bf12a4..be9150ef 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinReader.kt @@ -18,12 +18,12 @@ package io.emeraldpay.dshackle.upstream.bitcoin import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.upstream.Capability +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.bitcoin.data.SimpleUnspent -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.bitcoinj.core.Address import org.slf4j.LoggerFactory @@ -56,16 +56,16 @@ open class BitcoinReader( } open fun getBlock(hash: String): Mono> { - return castedRead(JsonRpcRequest("getblock", ListParams(hash)), Map::class.java).cast() + return castedRead(ChainRequest("getblock", ListParams(hash)), Map::class.java).cast() } open fun getBlock(height: Long): Mono> { - return castedRead(JsonRpcRequest("getblockhash", ListParams(height)), String::class.java) + return castedRead(ChainRequest("getblockhash", ListParams(height)), String::class.java) .flatMap(this@BitcoinReader::getBlock) } open fun getTx(txid: String): Mono> { - return castedRead(JsonRpcRequest("getrawtransaction", ListParams(txid, true)), Map::class.java).cast() + return castedRead(ChainRequest("getrawtransaction", ListParams(txid, true)), Map::class.java).cast() } open fun listUnspent(address: Address): Mono> { @@ -84,10 +84,10 @@ open class BitcoinReader( mempool.stop() } - fun castedRead(req: JsonRpcRequest, clazz: Class): Mono { + fun castedRead(req: ChainRequest, clazz: Class): Mono { return upstreams.getDirectApi(Selector.empty).flatMap { api -> api.read(req) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { objectMapper.readValue(it, clazz) as T } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt index fa502ce5..af762455 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt @@ -16,13 +16,13 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.Defaults -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.AbstractHead +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.springframework.scheduling.concurrent.CustomizableThreadFactory import reactor.core.Disposable @@ -34,7 +34,7 @@ import java.time.Duration import java.util.concurrent.Executors class BitcoinRpcHead( - private val api: JsonRpcReader, + private val api: ChainReader, private val extractBlock: ExtractBlock, private val interval: Duration = Duration.ofSeconds(15), headScheduler: Scheduler, @@ -60,14 +60,14 @@ class BitcoinRpcHead( val base = Flux.interval(interval) .publishOn(scheduler) .flatMap { - api.read(JsonRpcRequest("getbestblockhash", ListParams())) - .flatMap(JsonRpcResponse::requireStringResult) + api.read(ChainRequest("getbestblockhash", ListParams())) + .flatMap(ChainResponse::requireStringResult) .timeout(Defaults.timeout, Mono.error(Exception("Best block hash is not received"))) } .distinctUntilChanged() .flatMap { hash -> - api.read(JsonRpcRequest("getblock", ListParams(hash))) - .flatMap(JsonRpcResponse::requireResult) + api.read(ChainRequest("getblock", ListParams(hash))) + .flatMap(ChainResponse::requireResult) .map(extractBlock::extract) .timeout(Defaults.timeout, Mono.error(Exception("Block data is not received"))) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcUpstream.kt index 409ecb1c..097523a7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcUpstream.kt @@ -19,11 +19,11 @@ import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcHttpReader -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.Capability import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.HttpReader import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.Upstream @@ -34,7 +34,7 @@ import reactor.core.Disposable open class BitcoinRpcUpstream( id: String, chain: Chain, - private val directApi: JsonRpcHttpReader, + private val directApi: HttpReader, private val head: Head, options: ChainOptions.Options, role: UpstreamsConfig.UpstreamRole, @@ -56,7 +56,7 @@ open class BitcoinRpcUpstream( return head } - override fun getIngressReader(): JsonRpcReader { + override fun getIngressReader(): ChainReader { return directApi } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstreamValidator.kt index 84cdc120..cdab3bdd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstreamValidator.kt @@ -16,10 +16,10 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.UpstreamAvailability -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.slf4j.LoggerFactory import org.springframework.scheduling.concurrent.CustomizableThreadFactory @@ -30,7 +30,7 @@ import java.time.Duration import java.util.concurrent.Executors class BitcoinUpstreamValidator( - private val api: JsonRpcReader, + private val api: ChainReader, private val options: ChainOptions.Options, ) { @@ -41,8 +41,8 @@ class BitcoinUpstreamValidator( } fun validate(): Mono { - return api.read(JsonRpcRequest("getconnectioncount", ListParams())) - .flatMap(JsonRpcResponse::requireResult) + return api.read(ChainRequest("getconnectioncount", ListParams())) + .flatMap(ChainResponse::requireResult) .map { Integer.parseInt(String(it)) } .map { count -> val minPeers = options.minPeers ?: 1 diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt index 46843323..ef2c110d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinZMQHead.kt @@ -2,13 +2,13 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.AbstractHead +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.apache.commons.codec.binary.Hex import reactor.core.Disposable @@ -20,7 +20,7 @@ import java.time.Duration class BitcoinZMQHead( private val server: ZMQServer, - private val api: JsonRpcReader, + private val api: ChainReader, private val extractBlock: ExtractBlock, headScheduler: Scheduler, ) : Head, AbstractHead(MostWorkForkChoice(), headScheduler, awaitHeadTimeoutMs = 1200_000), Lifecycle { @@ -33,11 +33,11 @@ class BitcoinZMQHead( Hex.encodeHexString(it) } .flatMap { hash -> - api.read(JsonRpcRequest("getblock", ListParams(hash))) + api.read(ChainRequest("getblock", ListParams(hash))) .switchIfEmpty(Mono.error(IllegalStateException("Block $hash is not available on upstream"))) .retryWhen(Retry.backoff(5, Duration.ofMillis(100))) .switchIfEmpty(Mono.fromCallable { log.warn("Block $hash is not available on upstream") }.then(Mono.empty())) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map(extractBlock::extract) .timeout(Defaults.timeout, Mono.error(Exception("Block data is not received"))) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/CachingMempoolData.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/CachingMempoolData.kt index 0d709176..93242a8a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/CachingMempoolData.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/CachingMempoolData.kt @@ -17,11 +17,11 @@ package io.emeraldpay.dshackle.upstream.bitcoin import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.Selector -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.slf4j.LoggerFactory import reactor.core.Disposable @@ -66,8 +66,8 @@ open class CachingMempoolData( @Suppress("UNCHECKED_CAST") fun fetchFromUpstream(): Mono> { return upstreams.getDirectApi(Selector.empty).flatMap { api -> - api.read(JsonRpcRequest("getrawmempool", ListParams())) - .flatMap(JsonRpcResponse::requireResult) + api.read(ChainRequest("getrawmempool", ListParams())) + .flatMap(ChainResponse::requireResult) .map { objectMapper.readValue(it, List::class.java) as List } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/LocalCallRouter.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/LocalCallRouter.kt index 2f78f987..5617eacc 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/LocalCallRouter.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/LocalCallRouter.kt @@ -17,14 +17,14 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.SilentException -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.bitcoin.data.RpcUnspent import io.emeraldpay.dshackle.upstream.bitcoin.data.SimpleUnspent import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.bitcoinj.core.Address import org.slf4j.LoggerFactory @@ -40,16 +40,16 @@ import reactor.core.publisher.Mono class LocalCallRouter( private val methods: CallMethods, private val reader: BitcoinReader, -) : JsonRpcReader { +) : ChainReader { companion object { private val log = LoggerFactory.getLogger(LocalCallRouter::class.java) } - override fun read(key: JsonRpcRequest): Mono { + override fun read(key: ChainRequest): Mono { if (methods.isHardcoded(key.method)) { return Mono.just(methods.executeHardcoded(key.method)) - .map { JsonRpcResponse(it, null) } + .map { ChainResponse(it, null) } } if (!methods.isCallable(key.method)) { return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method")) @@ -63,7 +63,7 @@ class LocalCallRouter( /** * */ - fun processUnspentRequest(key: JsonRpcRequest): Mono { + fun processUnspentRequest(key: ChainRequest): Mono { if (key.params is ListParams) { if (key.params.list.size < 3) { return Mono.error(SilentException("Invalid call to unspent. Address is missing")) @@ -74,7 +74,7 @@ class LocalCallRouter( return reader.listUnspent(address).map { val rpc = it.map(convertUnspent(address)) val json = Global.objectMapper.writeValueAsBytes(rpc) - JsonRpcResponse.ok(json, JsonRpcResponse.NumberId(key.id)) + ChainResponse.ok(json, ChainResponse.NumberId(key.id)) } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReader.kt index fc57c2d9..ecc53cd8 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReader.kt @@ -18,11 +18,11 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.SilentException import io.emeraldpay.dshackle.upstream.Capability +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.bitcoin.data.RpcUnspent import io.emeraldpay.dshackle.upstream.bitcoin.data.SimpleUnspent -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.bitcoinj.core.Address import org.slf4j.LoggerFactory @@ -51,8 +51,8 @@ class RpcUnspentReader( // val address = key.toString() return upstreams.getDirectApi(selector).flatMap { api -> - api.read(JsonRpcRequest("listunspent", ListParams(1, 9999999, listOf(address)))) - .flatMap(JsonRpcResponse::requireResult) + api.read(ChainRequest("listunspent", ListParams(1, 9999999, listOf(address)))) + .flatMap(ChainResponse::requireResult) .map { Global.objectMapper.readerFor(RpcUnspent::class.java).readValues(it).readAll() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultBeaconChainMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultBeaconChainMethods.kt new file mode 100644 index 00000000..0ba8372c --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultBeaconChainMethods.kt @@ -0,0 +1,143 @@ +package io.emeraldpay.dshackle.upstream.calls + +import io.emeraldpay.dshackle.quorum.AlwaysQuorum +import io.emeraldpay.dshackle.quorum.CallQuorum +import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException + +class DefaultBeaconChainMethods : CallMethods { + + private val beaconMethods = setOf( + getMethod("/eth/v1/beacon/genesis"), + getMethod("/eth/v1/beacon/states/*/root"), + getMethod("/eth/v1/beacon/states/*/fork"), + getMethod("/eth/v1/beacon/states/*/finality_checkpoints"), + getMethod("/eth/v1/beacon/states/*/validators"), + postMethod("/eth/v1/beacon/states/*/validators"), + getMethod("/eth/v1/beacon/states/*/validators/*"), + getMethod("/eth/v1/beacon/states/*/validator_balances"), + postMethod("/eth/v1/beacon/states/*/validator_balances"), + getMethod("/eth/v1/beacon/states/*/committees"), + getMethod("/eth/v1/beacon/states/*/sync_committees"), + getMethod("/eth/v1/beacon/states/*/randao"), + getMethod("/eth/v1/beacon/headers"), + getMethod("/eth/v1/beacon/headers/*"), + postMethod("/eth/v1/beacon/blinded_blocks"), + postMethod("/eth/v2/beacon/blinded_blocks"), + postMethod("/eth/v1/beacon/blocks"), + postMethod("/eth/v2/beacon/blocks"), + getMethod("/eth/v2/beacon/blocks/*"), + getMethod("/eth/v1/beacon/blocks/*/root"), + getMethod("/eth/v1/beacon/blocks/*/attestations"), + getMethod("/eth/v1/beacon/blob_sidecars/*"), + postMethod("/eth/v1/beacon/rewards/sync_committee/*"), + getMethod("/eth/v1/beacon/deposit_snapshot"), + getMethod("/eth/v1/beacon/rewards/blocks/*"), + postMethod("/eth/v1/beacon/rewards/attestations/*"), + getMethod("/eth/v1/beacon/blinded_blocks/*"), + getMethod("/eth/v1/beacon/light_client/bootstrap/*"), + getMethod("/eth/v1/beacon/light_client/updates"), + getMethod("/eth/v1/beacon/light_client/finality_update"), + getMethod("/eth/v1/beacon/light_client/optimistic_update"), + getMethod("/eth/v1/beacon/pool/attestations"), + postMethod("/eth/v1/beacon/pool/attestations"), + getMethod("/eth/v1/beacon/pool/attester_slashings"), + postMethod("/eth/v1/beacon/pool/attester_slashings"), + getMethod("/eth/v1/beacon/pool/proposer_slashings"), + postMethod("/eth/v1/beacon/pool/proposer_slashings"), + postMethod("/eth/v1/beacon/pool/sync_committees"), + getMethod("/eth/v1/beacon/pool/voluntary_exits"), + postMethod("/eth/v1/beacon/pool/voluntary_exits"), + getMethod("/eth/v1/beacon/pool/bls_to_execution_changes"), + postMethod("/eth/v1/beacon/pool/bls_to_execution_changes"), + ) + + private val builderMethods = setOf( + getMethod("/eth/v1/builder/states/*/expected_withdrawals"), + ) + + private val configMethods = setOf( + getMethod("/eth/v1/config/fork_schedule"), + getMethod("/eth/v1/config/spec"), + getMethod("/eth/v1/config/deposit_contract"), + ) + + private val debugMethods = setOf( + getMethod("/eth/v2/debug/beacon/states/*"), + getMethod("/eth/v2/debug/beacon/heads"), + getMethod("/eth/v1/debug/fork_choice"), + ) + + private val eventMethods = setOf( + getMethod("/eth/v1/events"), + ) + + // need to think up what to do with these methods, probably hardcode them? + private val nodeMethods = setOf( + getMethod("/eth/v1/node/identity"), + getMethod("/eth/v1/node/peers"), + getMethod("/eth/v1/node/peers/*"), + getMethod("/eth/v1/node/peer_count"), + getMethod("/eth/v1/node/version"), + getMethod("/eth/v1/node/syncing"), + getMethod("/eth/v1/node/health"), + ) + + private val validatorMethods = setOf( + postMethod("/eth/v1/validator/duties/attester/*"), + getMethod("/eth/v1/validator/duties/proposer/*"), + postMethod("/eth/v1/validator/duties/sync/*"), + getMethod("/eth/v3/validator/blocks/*"), + getMethod("/eth/v1/validator/attestation_data"), + getMethod("/eth/v1/validator/aggregate_attestation"), + postMethod("/eth/v1/validator/aggregate_and_proofs"), + postMethod("/eth/v1/validator/beacon_committee_subscriptions"), + postMethod("/eth/v1/validator/sync_committee_subscriptions"), + postMethod("/eth/v1/validator/sync_committee_subscriptions"), + getMethod("/eth/v1/validator/sync_committee_contribution"), + postMethod("/eth/v1/validator/sync_committee_selections"), + postMethod("/eth/v1/validator/contribution_and_proofs"), + postMethod("/eth/v1/validator/prepare_beacon_proposer"), + postMethod("/eth/v1/validator/register_validator"), + postMethod("/eth/v1/validator/liveness/*"), + ) + + private val rewardMethods = setOf( + postMethod("/eth/v1/beacon/rewards/sync_committee/*"), + getMethod("/eth/v1/beacon/rewards/blocks/*"), + postMethod("/eth/v1/beacon/rewards/blocks/*"), + ) + + private val allowedMethods: Set = + beaconMethods + builderMethods + configMethods + debugMethods + eventMethods + nodeMethods + validatorMethods + + override fun createQuorumFor(method: String): CallQuorum { + return AlwaysQuorum() + } + + override fun isCallable(method: String): Boolean { + return allowedMethods.contains(method) + } + + override fun getSupportedMethods(): Set { + return allowedMethods.toSortedSet() + } + + override fun isHardcoded(method: String): Boolean { + return false + } + + override fun executeHardcoded(method: String): ByteArray { + throw RpcException(-32601, "Method not found") + } + + override fun getGroupMethods(groupName: String): Set { + return when (groupName) { + "default" -> getSupportedMethods() + else -> emptyList() + }.toSet() + } + + private fun getMethod(method: String) = "GET#$method" + + private fun postMethod(method: String) = "POST#$method" +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumArchiveBlockNumberReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumArchiveBlockNumberReader.kt index 59a09ed3..4756b781 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumArchiveBlockNumberReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumArchiveBlockNumberReader.kt @@ -1,10 +1,10 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Chain -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.hex.HexQuantity -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.publisher.Mono import kotlin.math.max @@ -14,12 +14,12 @@ private const val OPTIMISM_BEDROCK_BLOCK = "0x645C277" // 105235063 private const val EARLIEST_BLOCK = "0x2710" // 10000 class EthereumArchiveBlockNumberReader( - private val reader: JsonRpcReader, + private val reader: ChainReader, ) { fun readArchiveBlock(): Mono = - reader.read(JsonRpcRequest("eth_blockNumber", ListParams())) - .flatMap(JsonRpcResponse::requireResult) + reader.read(ChainRequest("eth_blockNumber", ListParams())) + .flatMap(ChainResponse::requireResult) .map { HexQuantity .from( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt index 4df8c83e..917de8d8 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainSpecific.kt @@ -5,8 +5,9 @@ import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.foundation.ChainOptions.Options -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.CachingReader +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription @@ -27,7 +28,6 @@ import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific import io.emeraldpay.dshackle.upstream.generic.CachingReaderBuilder import io.emeraldpay.dshackle.upstream.generic.GenericUpstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.springframework.cloud.sleuth.Tracer import reactor.core.publisher.Mono @@ -42,17 +42,19 @@ object EthereumChainSpecific : AbstractPollChainSpecific() { return parseBlock(data, upstreamId) } - override fun latestBlockRequest() = JsonRpcRequest("eth_getBlockByNumber", ListParams("latest", false)) - override fun listenNewHeadsRequest(): JsonRpcRequest = JsonRpcRequest("eth_subscribe", ListParams("newHeads")) - override fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest = - JsonRpcRequest("eth_unsubscribe", ListParams(subId)) + override fun latestBlockRequest() = + ChainRequest("eth_getBlockByNumber", ListParams("latest", false)) + override fun listenNewHeadsRequest(): ChainRequest = + ChainRequest("eth_subscribe", ListParams("newHeads")) + override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest = + ChainRequest("eth_unsubscribe", ListParams(subId)) override fun localReaderBuilder( cachingReader: CachingReader, methods: CallMethods, head: Head, logsOracle: LogsOracle?, - ): Mono { + ): Mono { return Mono.just(EthereumLocalReader(cachingReader as EthereumCachingReader, methods, head, logsOracle)) } @@ -94,7 +96,7 @@ object EthereumChainSpecific : AbstractPollChainSpecific() { return EthereumLowerBoundBlockDetector(chain, upstream) } - override fun labelDetector(chain: Chain, reader: JsonRpcReader): LabelsDetector { + override fun labelDetector(chain: Chain, reader: ChainReader): LabelsDetector { return EthereumLabelsDetector(reader, chain) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt index f5f186f2..4c0ce13f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt @@ -12,7 +12,9 @@ import io.emeraldpay.dshackle.data.DefaultContainer import io.emeraldpay.dshackle.data.TxContainer import io.emeraldpay.dshackle.data.TxId import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.reader.RpcReaderFactory +import io.emeraldpay.dshackle.reader.RequestReaderFactory +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.calls.CallMethods @@ -29,8 +31,6 @@ import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionReceiptJson import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionRefJson import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.apache.commons.collections4.Factory import org.apache.commons.lang3.exception.ExceptionUtils @@ -57,7 +57,7 @@ class EthereumDirectReader( } private val objectMapper: ObjectMapper = Global.objectMapper - var rpcReaderFactory: RpcReaderFactory = RpcReaderFactory.default() + var requestReaderFactory: RequestReaderFactory = RequestReaderFactory.default() val blockReader: Reader> val blockByHeightReader: Reader> @@ -69,20 +69,20 @@ class EthereumDirectReader( init { blockReader = object : Reader> { override fun read(key: BlockHash): Mono> { - val request = JsonRpcRequest("eth_getBlockByHash", ListParams(key.toHex(), false)) + val request = ChainRequest("eth_getBlockByHash", ListParams(key.toHex(), false)) return readBlock(request, key.toHex()) } } blockByHeightReader = object : Reader> { override fun read(key: Long): Mono> { val heightMatcher = Selector.HeightMatcher(key) - val request = JsonRpcRequest("eth_getBlockByNumber", ListParams(HexQuantity.from(key).toHex(), false)) + val request = ChainRequest("eth_getBlockByNumber", ListParams(HexQuantity.from(key).toHex(), false)) return readBlock(request, key.toString(), heightMatcher) } } txReader = object : Reader> { override fun read(key: TransactionId): Mono> { - val request = JsonRpcRequest("eth_getTransactionByHash", ListParams(key.toHex())) + val request = ChainRequest("eth_getTransactionByHash", ListParams(key.toHex())) return readWithQuorum(request) // retries were removed because we use NotNullQuorum which handle errors too .timeout(Duration.ofSeconds(5), Mono.error(TimeoutException("Tx not read $key"))) .flatMap { result -> @@ -100,14 +100,14 @@ class EthereumDirectReader( caches.cache(Caches.Tag.REQUESTED, tx.data) } }.onErrorResume { - Mono.error(JsonRpcException(request.id, ExceptionUtils.getRootCauseMessage(it))) + Mono.error(ChainException(request.id, ExceptionUtils.getRootCauseMessage(it))) } } } balanceReader = object : Reader> { override fun read(key: Address): Mono> { val height = up.getHead().getCurrentHeight()?.let { HexQuantity.from(it).toHex() } ?: "latest" - val request = JsonRpcRequest("eth_getBalance", ListParams(key.toHex(), height)) + val request = ChainRequest("eth_getBalance", ListParams(key.toHex(), height)) return readWithQuorum(request) .timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Balance not read $key"))) .map { @@ -131,7 +131,7 @@ class EthereumDirectReader( receiptReader = object : Reader> { override fun read(key: TransactionId): Mono> { - val request = JsonRpcRequest("eth_getTransactionReceipt", ListParams(key.toHex())) + val request = ChainRequest("eth_getTransactionReceipt", ListParams(key.toHex())) return readWithQuorum(request) .timeout(Duration.ofSeconds(5), Mono.error(TimeoutException("Receipt not read $key"))) .flatMap { result -> @@ -155,14 +155,14 @@ class EthereumDirectReader( ) } }.onErrorResume { - Mono.error(JsonRpcException(request.id, ExceptionUtils.getRootCauseMessage(it))) + Mono.error(ChainException(request.id, ExceptionUtils.getRootCauseMessage(it))) } } } logsByHashReader = object : Reader>> { override fun read(key: BlockId): Mono>> { - val request = JsonRpcRequest( + val request = ChainRequest( "eth_getLogs", ListParams( mapOf( @@ -190,7 +190,7 @@ class EthereumDirectReader( @Suppress("UNCHECKED_CAST") private fun readBlock( - request: JsonRpcRequest, + request: ChainRequest, id: String, matcher: Selector.Matcher = Selector.empty, ): Mono> { @@ -216,7 +216,7 @@ class EthereumDirectReader( .doOnNext { block -> caches.cache(Caches.Tag.REQUESTED, block.data) }.onErrorResume { - Mono.error(JsonRpcException(request.id, ExceptionUtils.getRootCauseMessage(it))) + Mono.error(ChainException(request.id, ExceptionUtils.getRootCauseMessage(it))) } } @@ -224,19 +224,18 @@ class EthereumDirectReader( * Read from an Upstream applying a Quorum specific for that request */ private fun readWithQuorum( - request: JsonRpcRequest, + request: ChainRequest, matcher: Selector.Matcher = Selector.empty, ): Mono> { - return Mono.just(rpcReaderFactory) + return Mono.just(requestReaderFactory) .map { val requestMatcher = Selector.Builder() .withMatcher(matcher) .forMethod(request.method) .build() it.create( - RpcReaderFactory.RpcReaderData( + RequestReaderFactory.ReaderData( up, - request.method, requestMatcher, callMethodsFactory.create().createQuorumFor(request.method), null, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt index dd965b35..9ba2b4e9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReader.kt @@ -18,15 +18,15 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Global.Companion.nullValue import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxId -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.LogsOracle import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.ethereum.hex.HexQuantity import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.switchIfEmpty @@ -44,12 +44,12 @@ class EthereumLocalReader( private val methods: CallMethods, private val head: Head, private val logsOracle: LogsOracle?, -) : JsonRpcReader { +) : ChainReader { - override fun read(key: JsonRpcRequest): Mono { + override fun read(key: ChainRequest): Mono { if (methods.isHardcoded(key.method)) { return Mono.just(methods.executeHardcoded(key.method)) - .map { JsonRpcResponse(it, null) } + .map { ChainResponse(it, null) } } if (!methods.isCallable(key.method)) { return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method")) @@ -61,7 +61,7 @@ class EthereumLocalReader( val common = commonRequests(key) ?.switchIfEmpty { Mono.just(nullValue to null) } if (common != null) { - return common.map { JsonRpcResponse(it.first, null, it.second) } + return common.map { ChainResponse(it.first, null, it.second) } } return Mono.empty() } @@ -71,7 +71,7 @@ class EthereumLocalReader( * parses JSON into Map. But the purpose of further processing and caching for some of the requests we want * to have actual data types. */ - fun commonRequests(key: JsonRpcRequest): Mono>? { + fun commonRequests(key: ChainRequest): Mono>? { val method = key.method val params = key.params if (params is ListParams) { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundBlockDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundBlockDetector.kt index fb938183..e6118e07 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundBlockDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumLowerBoundBlockDetector.kt @@ -1,10 +1,10 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Chain +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.RecursiveLowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.toHex import reactor.core.publisher.Mono @@ -45,13 +45,13 @@ class EthereumLowerBoundBlockDetector( return Mono.just(true) } return upstream.getIngressReader().read( - JsonRpcRequest( + ChainRequest( "eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", blockNumber.toHex()), ), ) .retryWhen(retrySpec(nonRetryableErrors)) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { true } .onErrorReturn(false) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt index 68310a80..945c1e26 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt @@ -22,109 +22,56 @@ import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.foundation.ChainOptions +import io.emeraldpay.dshackle.upstream.BasicEthUpstreamValidator +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability -import io.emeraldpay.dshackle.upstream.UpstreamValidator import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult import io.emeraldpay.dshackle.upstream.ethereum.domain.Address import io.emeraldpay.dshackle.upstream.ethereum.hex.HexData import io.emeraldpay.dshackle.upstream.ethereum.json.SyncingJson import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionCallJson -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams -import org.slf4j.LoggerFactory import org.springframework.scheduling.concurrent.CustomizableThreadFactory import reactor.core.publisher.Mono import reactor.core.scheduler.Schedulers import reactor.kotlin.extra.retry.retryRandomBackoff -import reactor.util.function.Tuple2 import java.time.Duration import java.util.concurrent.Executors import java.util.concurrent.TimeoutException +import java.util.function.Supplier open class EthereumUpstreamValidator @JvmOverloads constructor( private val chain: Chain, upstream: Upstream, options: ChainOptions.Options, private val config: ChainConfig, -) : UpstreamValidator(upstream, options) { +) : BasicEthUpstreamValidator(upstream, options) { companion object { - private val log = LoggerFactory.getLogger(EthereumUpstreamValidator::class.java) val scheduler = Schedulers.fromExecutor(Executors.newCachedThreadPool(CustomizableThreadFactory("ethereum-validator"))) } private val objectMapper: ObjectMapper = Global.objectMapper - override fun validate(): Mono { - return Mono.zip( - validateSyncing(), - validatePeers(), + override fun validateSyncingRequest(): ValidateSyncingRequest { + return ValidateSyncingRequest( + ChainRequest("eth_syncing", ListParams()), + ) { bytes -> objectMapper.readValue(bytes, SyncingJson::class.java).isSyncing } + } + + override fun validatePeersRequest(): ValidatePeersRequest { + return ValidatePeersRequest( + ChainRequest("net_peerCount", ListParams()), + ) { resp -> Integer.decode(resp.getResultAsProcessedString()) } + } + + override fun validatorFunctions(): List>> { + return listOf( + Supplier { validateSyncing() }, + Supplier { validatePeers() }, ) - .map(::resolve) - .defaultIfEmpty(UpstreamAvailability.UNAVAILABLE) - .onErrorResume { - log.error("Error during upstream validation for ${upstream.getId()}", it) - Mono.just(UpstreamAvailability.UNAVAILABLE) - } - } - - fun resolve(results: Tuple2): UpstreamAvailability { - val cp = Comparator { avail1: UpstreamAvailability, avail2: UpstreamAvailability -> if (avail1.isBetterTo(avail2)) -1 else 1 } - return listOf(results.t1, results.t2).sortedWith(cp).last() - } - - fun validateSyncing(): Mono { - if (!options.validateSyncing) { - return Mono.just(UpstreamAvailability.OK) - } - return upstream.getIngressReader() - .read(JsonRpcRequest("eth_syncing", ListParams())) - .flatMap(JsonRpcResponse::requireResult) - .map { objectMapper.readValue(it, SyncingJson::class.java) } - .timeout( - Defaults.timeoutInternal, - Mono.fromCallable { log.warn("No response for eth_syncing from ${upstream.getId()}") } - .then(Mono.error(TimeoutException("Validation timeout for Syncing"))), - ) - .map { - val isSyncing = it.isSyncing - upstream.getHead().onSyncingNode(isSyncing) - if (isSyncing) { - UpstreamAvailability.SYNCING - } else { - UpstreamAvailability.OK - } - } - .doOnError { err -> log.error("Error during syncing validation for ${upstream.getId()}", err) } - .onErrorReturn(UpstreamAvailability.UNAVAILABLE) - } - - fun validatePeers(): Mono { - if (!options.validatePeers || options.minPeers == 0) { - return Mono.just(UpstreamAvailability.OK) - } - return upstream - .getIngressReader() - .read(JsonRpcRequest("net_peerCount", ListParams())) - .flatMap(JsonRpcResponse::requireStringResult) - .map(Integer::decode) - .timeout( - Defaults.timeoutInternal, - Mono.fromCallable { log.warn("No response for net_peerCount from ${upstream.getId()}") } - .then(Mono.error(TimeoutException("Validation timeout for Peers"))), - ) - .map { count -> - val minPeers = options.minPeers - if (count < minPeers) { - UpstreamAvailability.IMMATURE - } else { - UpstreamAvailability.OK - } - } - .doOnError { err -> log.error("Error during peer count validation for ${upstream.getId()}", err) } - .onErrorReturn(UpstreamAvailability.UNAVAILABLE) } override fun validateUpstreamSettings(): Mono { @@ -178,7 +125,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( } return upstream.getIngressReader() .read( - JsonRpcRequest( + ChainRequest( "eth_call", ListParams( TransactionCallJson( @@ -191,7 +138,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( ), ), ) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { ValidateUpstreamSettingsResult.UPSTREAM_VALID } .onErrorResume { if (it.message != null && it.message!!.contains("rpc.returndata.limit")) { @@ -224,8 +171,8 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( .readArchiveBlock() .flatMap { upstream.getIngressReader() - .read(JsonRpcRequest("eth_getBlockByNumber", ListParams(it, false))) - .flatMap(JsonRpcResponse::requireResult) + .read(ChainRequest("eth_getBlockByNumber", ListParams(it, false))) + .flatMap(ChainResponse::requireResult) } .retryRandomBackoff(3, Duration.ofMillis(100), Duration.ofMillis(500)) { ctx -> log.warn( @@ -250,7 +197,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( private fun chainId(): Mono { return upstream.getIngressReader() - .read(JsonRpcRequest("eth_chainId", ListParams())) + .read(ChainRequest("eth_chainId", ListParams())) .retryRandomBackoff(3, Duration.ofMillis(100), Duration.ofMillis(500)) { ctx -> log.warn( "error during chainId retrieving for ${upstream.getId()}, iteration ${ctx.iteration()}, " + @@ -258,12 +205,12 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( ) } .doOnError { log.error("Error during execution 'eth_chainId' - ${it.message} for ${upstream.getId()}") } - .flatMap(JsonRpcResponse::requireStringResult) + .flatMap(ChainResponse::requireStringResult) } private fun netVersion(): Mono { return upstream.getIngressReader() - .read(JsonRpcRequest("net_version", ListParams())) + .read(ChainRequest("net_version", ListParams())) .retryRandomBackoff(3, Duration.ofMillis(100), Duration.ofMillis(500)) { ctx -> log.warn( "error during netVersion retrieving for ${upstream.getId()}, iteration ${ctx.iteration()}, " + @@ -271,6 +218,6 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( ) } .doOnError { log.error("Error during execution 'net_version' - ${it.message} for ${upstream.getId()}") } - .flatMap(JsonRpcResponse::requireStringResult) + .flatMap(ChainResponse::requireStringResult) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHead.kt index e13f23b6..f68fb6d2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHead.kt @@ -17,7 +17,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.Lifecycle @@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicReference class GenericWsHead( forkChoice: ForkChoice, blockValidator: BlockValidator, - private val api: JsonRpcReader, + private val api: ChainReader, private val wsSubscriptions: WsSubscriptions, private val wsConnectionResubscribeScheduler: Scheduler, headScheduler: Scheduler, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt index abdf0029..bfa2a850 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt @@ -15,8 +15,8 @@ */ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsMessage import reactor.core.publisher.Flux import reactor.core.publisher.Mono @@ -27,7 +27,7 @@ interface WsConnection : AutoCloseable { fun connectionId(): String fun getSubscribeResponses(): Flux - fun callRpc(originalRequest: JsonRpcRequest): Mono + fun callRpc(originalRequest: ChainRequest): Mono fun connect() fun connectionInfoFlux(): Flux diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionFactory.kt index 41de1f15..0aeac374 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionFactory.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionFactory.kt @@ -3,7 +3,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.AuthConfig import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics +import io.emeraldpay.dshackle.upstream.RequestMetrics import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.Metrics import io.micrometer.core.instrument.Tag @@ -22,7 +22,7 @@ open class WsConnectionFactory( var basicAuth: AuthConfig.ClientBasicAuth? = null var config: UpstreamsConfig.WsEndpoint? = null - private fun metrics(connIndex: Int): RpcMetrics { + private fun metrics(connIndex: Int): RequestMetrics { val metricsTags = listOf( Tag.of("index", connIndex.toString()), Tag.of("upstream", id), @@ -30,7 +30,7 @@ open class WsConnectionFactory( Tag.of("chain", chain.chainCode), ) - return RpcMetrics( + return RequestMetrics( Timer.builder("upstream.ws.conn") .description("Request time through a WebSocket JSON RPC connection") .tags(metricsTags) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImpl.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImpl.kt index ce763f4a..29cdb67a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImpl.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImpl.kt @@ -18,16 +18,16 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.AuthConfig +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse +import io.emeraldpay.dshackle.upstream.RequestMetrics import io.emeraldpay.dshackle.upstream.ethereum.WsConnection.ConnectionState.CONNECTED import io.emeraldpay.dshackle.upstream.ethereum.WsConnection.ConnectionState.DISCONNECTED import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsMessage import io.emeraldpay.dshackle.upstream.rpcclient.ResponseWSParser -import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics import io.micrometer.core.instrument.Metrics import io.netty.buffer.ByteBufInputStream import io.netty.handler.codec.http.HttpHeaderNames @@ -64,7 +64,7 @@ open class WsConnectionImpl( private val uri: URI, private val origin: URI, private val basicAuth: AuthConfig.ClientBasicAuth?, - private val rpcMetrics: RpcMetrics?, + private val requestMetrics: RequestMetrics?, private val scheduler: Scheduler, ) : AutoCloseable, WsConnection, Cloneable { @@ -108,7 +108,7 @@ open class WsConnectionImpl( private var rpcSend = Sinks .many() .unicast() - .onBackpressureBuffer() + .onBackpressureBuffer() private val disconnects = Sinks .many() @@ -120,7 +120,7 @@ open class WsConnectionImpl( .multicast() .directBestEffort() - private val currentRequests = ConcurrentHashMap>() + private val currentRequests = ConcurrentHashMap>() private val connId = UUID.randomUUID().toString() private val sendIdSeq = AtomicInteger(IDS_START) @@ -168,7 +168,7 @@ open class WsConnectionImpl( rpcSend = Sinks .many() .unicast() - .onBackpressureBuffer() + .onBackpressureBuffer() resetBackoffTask?.cancel(false) val retryInterval = currentBackOff.nextBackOff() resetBackoffTask = resetBackoffExecutor.schedule({ @@ -305,7 +305,7 @@ open class WsConnectionImpl( } private fun onMessageRpc(msg: ResponseWSParser.WsResponse) { - val rpcResponse = JsonRpcResponse( + val rpcResponse = ChainResponse( msg.value, msg.error, msg.id, @@ -346,7 +346,7 @@ open class WsConnectionImpl( return Flux.from(subscriptionResponses.asFlux()) } - override fun callRpc(originalRequest: JsonRpcRequest): Mono { + override fun callRpc(originalRequest: ChainRequest): Mono { return Mono.fromCallable { val startTime = System.nanoTime() // use an internal id sequence, to avoid id conflicts with user calls @@ -358,7 +358,7 @@ open class WsConnectionImpl( } } - private fun sendRpc(request: JsonRpcRequest) { + private fun sendRpc(request: ChainRequest) { // submit to upstream in a separate thread, to free current thread (needs for subscription, etc) sendExecutor.execute { val result = rpcSend.tryEmitNext(request) @@ -368,13 +368,13 @@ open class WsConnectionImpl( } } - private fun waitForResponse(request: JsonRpcRequest, originalId: Int, startTime: Long): Mono { + private fun waitForResponse(request: ChainRequest, originalId: Int, startTime: Long): Mono { val internalId = request.id.toLong() - val onResponse = Sinks.one() + val onResponse = Sinks.one() currentRequests[internalId.toInt()] = onResponse - val noResponse = JsonRpcException( - JsonRpcResponse.Id.from(originalId), - JsonRpcError( + val noResponse = ChainException( + ChainResponse.Id.from(originalId), + ChainCallError( RpcResponseError.CODE_INTERNAL_ERROR, "Response not received from WebSocket", ), @@ -384,10 +384,10 @@ open class WsConnectionImpl( val failOnDisconnect = Mono.from(disconnects.asFlux()) .flatMap { - Mono.error( - JsonRpcException( - JsonRpcResponse.Id.from(originalId), - JsonRpcError( + Mono.error( + ChainException( + ChainResponse.Id.from(originalId), + ChainCallError( RpcResponseError.CODE_UPSTREAM_CONNECTION_ERROR, "Disconnected from WebSocket", ), @@ -398,9 +398,9 @@ open class WsConnectionImpl( return Mono.from(onResponse.asMono()).or(failOnDisconnect) .doOnSubscribe { sendRpc(request) } .take(Defaults.timeout) - .doOnNext { rpcMetrics?.timer?.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS) } - .doOnError { rpcMetrics?.fails?.increment() } - .map { it.copyWithId(JsonRpcResponse.Id.from(originalId)) } + .doOnNext { requestMetrics?.timer?.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS) } + .doOnError { requestMetrics?.fails?.increment() } + .map { it.copyWithId(ChainResponse.Id.from(originalId)) } .switchIfEmpty( Mono.fromCallable { log.warn("No response for ${request.method} ${request.params}") }.then(Mono.error(noResponse)), ) @@ -414,11 +414,11 @@ open class WsConnectionImpl( connection?.dispose() connection = null currentRequests.clear() - rpcMetrics?.fails?.let { + requestMetrics?.fails?.let { it.close() Metrics.globalRegistry.remove(it) } - rpcMetrics?.timer?.let { + requestMetrics?.timer?.let { it.close() Metrics.globalRegistry.remove(it) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptions.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptions.kt index 0442f135..74e72891 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptions.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptions.kt @@ -15,8 +15,8 @@ */ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.util.concurrent.atomic.AtomicReference @@ -41,11 +41,11 @@ interface WsSubscriptions { /** * Subscribe on remote */ - fun subscribe(request: JsonRpcRequest): SubscribeData + fun subscribe(request: ChainRequest): SubscribeData fun connectionInfoFlux(): Flux - fun unsubscribe(request: JsonRpcRequest): Mono + fun unsubscribe(request: ChainRequest): Mono data class SubscribeData( val data: Flux, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImpl.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImpl.kt index 0d0f50ee..69720281 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImpl.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImpl.kt @@ -15,9 +15,9 @@ */ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.slf4j.LoggerFactory import reactor.core.publisher.Flux @@ -32,7 +32,7 @@ class WsSubscriptionsImpl( private val log = LoggerFactory.getLogger(WsSubscriptionsImpl::class.java) } - override fun subscribe(request: JsonRpcRequest): WsSubscriptions.SubscribeData { + override fun subscribe(request: ChainRequest): WsSubscriptions.SubscribeData { val subscriptionId = AtomicReference("") val conn = wsPool.getConnection() val messages = conn.getSubscribeResponses() @@ -44,7 +44,7 @@ class WsSubscriptionsImpl( .flatMapMany { if (it.hasError()) { log.warn("Failed to establish subscription: ${it.error?.message}") - Mono.error(JsonRpcException(it.id, it.error!!)) + Mono.error(ChainException(it.id, it.error!!)) } else { subscriptionId.set(it.getResultAsProcessedString()) messages @@ -54,7 +54,7 @@ class WsSubscriptionsImpl( return WsSubscriptions.SubscribeData(messageFlux, conn.connectionId(), subscriptionId) } - override fun unsubscribe(request: JsonRpcRequest): Mono { + override fun unsubscribe(request: ChainRequest): Mono { if (request.params is ListParams && (request.params.list.isEmpty() || request.params.list.contains("")) ) { return Mono.empty() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/EthereumLabelsDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/EthereumLabelsDetector.kt index 7a97a5d0..2ee710ab 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/EthereumLabelsDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/EthereumLabelsDetector.kt @@ -1,29 +1,21 @@ package io.emeraldpay.dshackle.upstream.ethereum.subscribe -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.module.kotlin.readValue import io.emeraldpay.dshackle.Chain -import io.emeraldpay.dshackle.Global.Companion.objectMapper -import io.emeraldpay.dshackle.reader.JsonRpcReader -import io.emeraldpay.dshackle.upstream.LabelsDetector +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.BasicEthLabelsDetector +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.EthereumArchiveBlockNumberReader -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams -import org.slf4j.LoggerFactory import reactor.core.publisher.Flux import reactor.core.publisher.Mono class EthereumLabelsDetector( - private val reader: JsonRpcReader, + private val reader: ChainReader, private val chain: Chain, -) : LabelsDetector { +) : BasicEthLabelsDetector(reader) { private val blockNumberReader = EthereumArchiveBlockNumberReader(reader) - companion object { - private val log = LoggerFactory.getLogger(EthereumLabelsDetector::class.java) - } - override fun detectLabels(): Flux> { return Flux.merge( detectNodeType(), @@ -31,27 +23,6 @@ class EthereumLabelsDetector( ) } - private fun detectNodeType(): Flux?> { - return reader - .read(JsonRpcRequest("web3_clientVersion", ListParams())) - .flatMap(JsonRpcResponse::requireResult) - .map { objectMapper.readValue(it) } - .flatMapMany { node -> - val labels = mutableListOf>() - if (node.isTextual) { - clientType(node.textValue())?.let { - labels.add("client_type" to it) - } - clientVersion(node.textValue())?.let { - labels.add("client_version" to it) - } - } - - Flux.fromIterable(labels) - } - .onErrorResume { Flux.empty() } - } - private fun detectArchiveNode(): Mono> { return Mono.zip( blockNumberReader.readEarliestBlock(chain).flatMap { haveBalance(it) }, @@ -63,34 +34,16 @@ class EthereumLabelsDetector( private fun haveBalance(blockNumber: String): Mono { return reader.read( - JsonRpcRequest( + ChainRequest( "eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", blockNumber), ), - ).flatMap(JsonRpcResponse::requireResult) + ).flatMap(ChainResponse::requireResult) } - private fun clientVersion(client: String): String? { - val firstSlash = client.indexOf("/") - val secondSlash = client.indexOf("/", firstSlash + 1) - if (firstSlash == -1 || secondSlash == -1 || secondSlash < firstSlash) { - return null - } - return client.substring(firstSlash + 1, secondSlash) - } - - private fun clientType(client: String): String? { - return if (client.contains("erigon", true)) { - "erigon" - } else if (client.contains("geth", true)) { - "geth" - } else if (client.contains("bor", true)) { - "bor" - } else if (client.contains("nethermind", true)) { - "nethermind" - } else { - log.debug("Unknown client type: {}", client) - null - } + override fun nodeTypeRequest(): NodeTypeRequest { + return NodeTypeRequest( + ChainRequest("web3_clientVersion", ListParams()), + ) { node -> node } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxes.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxes.kt index 09ecda74..15f67d0b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxes.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxes.kt @@ -15,11 +15,11 @@ */ package io.emeraldpay.dshackle.upstream.ethereum.subscribe +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.SubscriptionConnect import io.emeraldpay.dshackle.upstream.ethereum.EthereumEgressSubscription import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.slf4j.LoggerFactory import reactor.core.publisher.Flux @@ -35,7 +35,7 @@ class WebsocketPendingTxes( } override fun createConnection(): Flux { - return wsSubscriptions.subscribe(JsonRpcRequest("eth_subscribe", ListParams(EthereumEgressSubscription.METHOD_PENDING_TXES))) + return wsSubscriptions.subscribe(ChainRequest("eth_subscribe", ListParams(EthereumEgressSubscription.METHOD_PENDING_TXES))) .data .timeout(Duration.ofSeconds(60), Mono.empty()) .map { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt index eae0dd04..6778a5c0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/AbstractChainSpecific.kt @@ -3,8 +3,9 @@ package io.emeraldpay.dshackle.upstream.generic import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.CachingReader +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.EmptyEgressSubscription import io.emeraldpay.dshackle.upstream.Head @@ -17,7 +18,6 @@ import io.emeraldpay.dshackle.upstream.NoopCachingReader import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.CallSelector import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import org.springframework.cloud.sleuth.Tracer import reactor.core.publisher.Mono import reactor.core.scheduler.Scheduler @@ -29,7 +29,7 @@ abstract class AbstractChainSpecific : ChainSpecific { methods: CallMethods, head: Head, logsOracle: LogsOracle?, - ): Mono { + ): Mono { return Mono.just(LocalReader(methods)) } @@ -37,7 +37,7 @@ abstract class AbstractChainSpecific : ChainSpecific { return { _, _, _ -> NoopCachingReader } } - override fun labelDetector(chain: Chain, reader: JsonRpcReader): LabelsDetector? { + override fun labelDetector(chain: Chain, reader: ChainReader): LabelsDetector? { return null } @@ -56,13 +56,13 @@ abstract class AbstractChainSpecific : ChainSpecific { abstract class AbstractPollChainSpecific : AbstractChainSpecific() { - override fun getLatestBlock(api: JsonRpcReader, upstreamId: String): Mono { + override fun getLatestBlock(api: ChainReader, upstreamId: String): Mono { return api.read(latestBlockRequest()).map { parseBlock(it.getResult(), upstreamId) } } - abstract fun latestBlockRequest(): JsonRpcRequest + abstract fun latestBlockRequest(): ChainRequest abstract fun parseBlock(data: ByteArray, upstreamId: String): BlockContainer } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt index 07632114..68571203 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt @@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.upstream.generic import io.emeraldpay.dshackle.BlockchainType.BITCOIN import io.emeraldpay.dshackle.BlockchainType.ETHEREUM +import io.emeraldpay.dshackle.BlockchainType.ETHEREUM_BEACON_CHAIN import io.emeraldpay.dshackle.BlockchainType.NEAR import io.emeraldpay.dshackle.BlockchainType.POLKADOT import io.emeraldpay.dshackle.BlockchainType.SOLANA @@ -12,8 +13,9 @@ import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.CachingReader +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription @@ -23,13 +25,13 @@ import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamValidator +import io.emeraldpay.dshackle.upstream.beaconchain.BeaconChainSpecific import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.CallSelector import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions import io.emeraldpay.dshackle.upstream.near.NearChainSpecific import io.emeraldpay.dshackle.upstream.polkadot.PolkadotChainSpecific -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.solana.SolanaChainSpecific import io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific import org.apache.commons.collections4.Factory @@ -38,19 +40,19 @@ import reactor.core.publisher.Mono import reactor.core.scheduler.Scheduler typealias SubscriptionBuilder = (Multistream) -> EgressSubscription -typealias LocalReaderBuilder = (CachingReader, CallMethods, Head, LogsOracle?) -> Mono +typealias LocalReaderBuilder = (CachingReader, CallMethods, Head, LogsOracle?) -> Mono typealias CachingReaderBuilder = (Multistream, Caches, Factory) -> CachingReader interface ChainSpecific { fun parseHeader(data: ByteArray, upstreamId: String): BlockContainer - fun getLatestBlock(api: JsonRpcReader, upstreamId: String): Mono + fun getLatestBlock(api: ChainReader, upstreamId: String): Mono - fun listenNewHeadsRequest(): JsonRpcRequest + fun listenNewHeadsRequest(): ChainRequest - fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest + fun unsubscribeNewHeadsRequest(subId: String): ChainRequest - fun localReaderBuilder(cachingReader: CachingReader, methods: CallMethods, head: Head, logsOracle: LogsOracle?): Mono + fun localReaderBuilder(cachingReader: CachingReader, methods: CallMethods, head: Head, logsOracle: LogsOracle?): Mono fun subscriptionBuilder(headScheduler: Scheduler): (Multistream) -> EgressSubscription @@ -58,7 +60,7 @@ interface ChainSpecific { fun validator(chain: Chain, upstream: Upstream, options: ChainOptions.Options, config: ChainConfig): UpstreamValidator - fun labelDetector(chain: Chain, reader: JsonRpcReader): LabelsDetector? + fun labelDetector(chain: Chain, reader: ChainReader): LabelsDetector? fun makeIngressSubscription(ws: WsSubscriptions): IngressSubscription @@ -77,6 +79,7 @@ object ChainSpecificRegistry { POLKADOT -> PolkadotChainSpecific SOLANA -> SolanaChainSpecific NEAR -> NearChainSpecific + ETHEREUM_BEACON_CHAIN -> BeaconChainSpecific BITCOIN -> throw IllegalArgumentException("bitcoin should use custom streams implementation") UNKNOWN -> throw IllegalArgumentException("unknown chain") } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericHead.kt index ecf4e33e..22b5f902 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericHead.kt @@ -17,7 +17,7 @@ package io.emeraldpay.dshackle.upstream.generic import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.AbstractHead import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.Head @@ -33,7 +33,7 @@ open class GenericHead( private val chainSpecific: ChainSpecific, ) : Head, AbstractHead(forkChoice, headScheduler, blockValidator, 60_000, upstreamId) { - fun getLatestBlock(api: JsonRpcReader): Mono { + fun getLatestBlock(api: ChainReader): Mono { return chainSpecific.getLatestBlock(api, upstreamId) .subscribeOn(headScheduler) .timeout(Defaults.timeout, Mono.error(Exception("Block data not received"))) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericIngressSubscription.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericIngressSubscription.kt index eb2f9109..c58c6eb6 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericIngressSubscription.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericIngressSubscription.kt @@ -1,10 +1,10 @@ package io.emeraldpay.dshackle.upstream.generic +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.SubscriptionConnect import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions import io.emeraldpay.dshackle.upstream.generic.subscribe.GenericPersistentConnect -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.publisher.Flux import reactor.core.publisher.Mono @@ -38,7 +38,7 @@ class GenericSubscriptionConnect( @Suppress("UNCHECKED_CAST") override fun createConnection(): Flux { - return conn.subscribe(JsonRpcRequest(topic, ListParams(getParams(params)))) + return conn.subscribe(ChainRequest(topic, ListParams(getParams(params)))) .data .timeout(Duration.ofSeconds(60), Mono.empty()) .onErrorResume { Mono.empty() } as Flux diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt index 3bf1742c..c6e5703f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericMultistream.kt @@ -21,7 +21,7 @@ import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.config.IndexConfig import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.CachingReader import io.emeraldpay.dshackle.upstream.DistanceExtractor import io.emeraldpay.dshackle.upstream.DynamicMergedHead @@ -166,7 +166,7 @@ open class GenericMultistream( return this as T } - override fun getLocalReader(): Mono { + override fun getLocalReader(): Mono { return localReaderBuilder(cachingReader, getMethods(), getHead(), logsOracle) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericRpcHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericRpcHead.kt index cb407b21..5bf8cc77 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericRpcHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericRpcHead.kt @@ -16,7 +16,7 @@ */ package io.emeraldpay.dshackle.upstream.generic -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice @@ -26,7 +26,7 @@ import reactor.core.scheduler.Scheduler import java.time.Duration class GenericRpcHead( - private val api: JsonRpcReader, + private val api: ChainReader, forkChoice: ForkChoice, upstreamId: String, blockValidator: BlockValidator, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstream.kt index 2846bbdd..71c62728 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstream.kt @@ -5,7 +5,7 @@ import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig.Labels import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.startup.UpstreamChangeEvent import io.emeraldpay.dshackle.startup.UpstreamChangeEvent.ChangeType.UPDATED @@ -65,7 +65,7 @@ open class GenericUpstream( return connector.getHead() } - override fun getIngressReader(): JsonRpcReader { + override fun getIngressReader(): ChainReader { return connector.getIngressReader() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt index b2c91b4e..f0bc359c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt @@ -2,14 +2,13 @@ package io.emeraldpay.dshackle.upstream.generic import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.foundation.ChainOptions +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.SingleCallValidator import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.UpstreamValidator import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult.UPSTREAM_VALID -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse -import org.slf4j.LoggerFactory import reactor.core.publisher.Mono import java.util.concurrent.TimeoutException @@ -19,13 +18,10 @@ class GenericUpstreamValidator( private val validator: SingleCallValidator, ) : UpstreamValidator(upstream, options) { - companion object { - private val log = LoggerFactory.getLogger(GenericUpstreamValidator::class.java) - } override fun validate(): Mono { return upstream.getIngressReader() .read(validator.method) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { validator.check(it) } .timeout( Defaults.timeoutInternal, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/LocalReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/LocalReader.kt index b92251e7..4b89563d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/LocalReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/LocalReader.kt @@ -1,18 +1,18 @@ package io.emeraldpay.dshackle.upstream.generic -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import reactor.core.publisher.Mono -class LocalReader(private val methods: CallMethods) : JsonRpcReader { - override fun read(key: JsonRpcRequest): Mono { +class LocalReader(private val methods: CallMethods) : ChainReader { + override fun read(key: ChainRequest): Mono { if (methods.isHardcoded(key.method)) { return Mono.just(methods.executeHardcoded(key.method)) - .map { JsonRpcResponse(it, null) } + .map { ChainResponse(it, null) } } if (!methods.isCallable(key.method)) { return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method")) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnector.kt index 0486bb4c..68501910 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnector.kt @@ -1,6 +1,6 @@ package io.emeraldpay.dshackle.upstream.generic.connectors -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.Lifecycle @@ -11,7 +11,7 @@ interface GenericConnector : Lifecycle { fun hasLiveSubscriptionHead(): Flux - fun getIngressReader(): JsonRpcReader + fun getIngressReader(): ChainReader fun getIngressSubscription(): IngressSubscription } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericRpcConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericRpcConnector.kt index d29c8085..ef5d53e5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericRpcConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericRpcConnector.kt @@ -2,11 +2,11 @@ package io.emeraldpay.dshackle.upstream.generic.connectors import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.CachesEnabled -import io.emeraldpay.dshackle.reader.JsonRpcHttpReader -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.HttpReader import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.MergedHead @@ -33,7 +33,7 @@ import java.time.Duration class GenericRpcConnector( connectorType: ConnectorMode, - private val directReader: JsonRpcHttpReader, + private val directReader: HttpReader, wsFactory: WsConnectionPoolFactory?, upstream: DefaultUpstream, forkChoice: ForkChoice, @@ -152,7 +152,7 @@ class GenericRpcConnector( directReader.onStop() } - override fun getIngressReader(): JsonRpcReader { + override fun getIngressReader(): ChainReader { return directReader } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericWsConnector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericWsConnector.kt index 6ee26980..8ff1caa5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericWsConnector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericWsConnector.kt @@ -1,6 +1,6 @@ package io.emeraldpay.dshackle.upstream.generic.connectors -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.Head @@ -29,7 +29,7 @@ class GenericWsConnector( chainSpecific: ChainSpecific, ) : GenericConnector { private val pool: WsConnectionPool - private val reader: JsonRpcReader + private val reader: ChainReader private val head: GenericWsHead private val subscriptions: IngressSubscription private val liveness: HeadLivenessValidator @@ -68,7 +68,7 @@ class GenericWsConnector( head.stop() } - override fun getIngressReader(): JsonRpcReader { + override fun getIngressReader(): ChainReader { return reader } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/RestConnectorFactory.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/RestConnectorFactory.kt new file mode 100644 index 00000000..34820635 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/RestConnectorFactory.kt @@ -0,0 +1,47 @@ +package io.emeraldpay.dshackle.upstream.generic.connectors + +import io.emeraldpay.dshackle.Chain +import io.emeraldpay.dshackle.upstream.BlockValidator +import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.HttpFactory +import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice +import io.emeraldpay.dshackle.upstream.generic.ChainSpecificRegistry +import reactor.core.scheduler.Scheduler +import reactor.core.scheduler.Schedulers +import java.time.Duration + +class RestConnectorFactory( + private val httpFactory: HttpFactory?, + private val forkChoice: ForkChoice, + private val blockValidator: BlockValidator, + private val headScheduler: Scheduler, + private val headLivenessScheduler: Scheduler, + private val expectedBlockTime: Duration, +) : ConnectorFactory { + + override fun create(upstream: DefaultUpstream, chain: Chain): GenericConnector { + val specific = ChainSpecificRegistry.resolve(chain) + + if (httpFactory == null) { + throw IllegalArgumentException("No http endpoint") + } + + return GenericRpcConnector( + GenericConnectorFactory.ConnectorMode.RPC_ONLY, + httpFactory.create(upstream.getId(), chain), + null, + upstream, + forkChoice, + blockValidator, + Schedulers.single(), + headScheduler, + headLivenessScheduler, + expectedBlockTime, + specific, + ) + } + + override fun isValid(): Boolean { + return httpFactory != null + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt index 8a0e7daf..a4f40448 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt @@ -24,9 +24,11 @@ import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.BuildInfo import io.emeraldpay.dshackle.upstream.Capability +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector @@ -37,8 +39,6 @@ import io.emeraldpay.dshackle.upstream.bitcoin.ExtractBlock import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.reactivestreams.Publisher import reactor.core.publisher.Flux @@ -70,7 +70,7 @@ class BitcoinGrpcUpstream( Lifecycle { private val extractBlock = ExtractBlock() - private val defaultReader: JsonRpcReader = client.getReader() + private val defaultReader: ChainReader = client.getReader() private val blockConverter: Function = Function { value -> val parentHash = if (value.parentBlockId.isBlank()) { @@ -94,8 +94,8 @@ class BitcoinGrpcUpstream( private val reloadBlock: Function> = Function { existingBlock -> // head comes without transaction data // need to download transactions for the block - defaultReader.read(JsonRpcRequest("getblock", ListParams(existingBlock.hash.toHex()))) - .flatMap(JsonRpcResponse::requireResult) + defaultReader.read(ChainRequest("getblock", ListParams(existingBlock.hash.toHex()))) + .flatMap(ChainResponse::requireResult) .map(extractBlock::extract) .timeout(timeout, Mono.error(TimeoutException("Timeout from upstream"))) .doOnError { t -> @@ -134,7 +134,7 @@ class BitcoinGrpcUpstream( return grpcHead } - override fun getIngressReader(): JsonRpcReader { + override fun getIngressReader(): ChainReader { return defaultReader } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GenericGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GenericGrpcUpstream.kt index d2fb5cbc..b3e72c21 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GenericGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GenericGrpcUpstream.kt @@ -24,7 +24,7 @@ import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.foundation.ChainOptions -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.BuildInfo import io.emeraldpay.dshackle.upstream.Capability @@ -101,7 +101,7 @@ open class GenericGrpcUpstream( private var capabilities: Set = emptySet() private val buildInfo: BuildInfo = BuildInfo() - private val defaultReader: JsonRpcReader = client.getReader() + private val defaultReader: ChainReader = client.getReader() override fun start() { } @@ -159,7 +159,7 @@ open class GenericGrpcUpstream( return grpcHead } - override fun getIngressReader(): JsonRpcReader { + override fun getIngressReader(): ChainReader { return defaultReader } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt index e1f4a5ae..447ee5c7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt @@ -34,13 +34,13 @@ import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.startup.UpstreamChangeEvent import io.emeraldpay.dshackle.upstream.DefaultUpstream +import io.emeraldpay.dshackle.upstream.RequestMetrics import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.grpc.auth.AuthException import io.emeraldpay.dshackle.upstream.grpc.auth.ClientAuthenticationInterceptor import io.emeraldpay.dshackle.upstream.grpc.auth.GrpcAuthContext import io.emeraldpay.dshackle.upstream.grpc.auth.GrpcUpstreamsAuth import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient -import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics import io.grpc.ClientInterceptor import io.grpc.Codec import io.grpc.Status @@ -279,13 +279,13 @@ class GrpcUpstreams( return getOrCreate(chain, metrics, creator) } - private fun makeMetrics(chain: Chain): RpcMetrics { + private fun makeMetrics(chain: Chain): RequestMetrics { val metricsTags = listOf( Tag.of("upstream", id), Tag.of("chain", chain.chainCode), ) - return RpcMetrics( + return RequestMetrics( Timer.builder("upstream.grpc.conn") .description("Request time through a Dshackle/gRPC connection") .tags(metricsTags) @@ -300,7 +300,7 @@ class GrpcUpstreams( private fun getOrCreate( chain: Chain, - metrics: RpcMetrics, + metrics: RequestMetrics, creator: (chain: Chain, client: JsonRpcGrpcClient) -> DefaultUpstream, ): UpstreamChangeEvent { lock.withLock { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt index b6fc82b9..a851b136 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt @@ -8,6 +8,7 @@ import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.foundation.ChainOptions.Options +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.SingleCallValidator import io.emeraldpay.dshackle.upstream.Upstream @@ -15,7 +16,6 @@ import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.UpstreamValidator import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific import io.emeraldpay.dshackle.upstream.generic.GenericUpstreamValidator -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.rpcclient.ObjectParams import java.math.BigInteger @@ -44,11 +44,11 @@ object NearChainSpecific : AbstractPollChainSpecific() { throw NotImplementedError() } - override fun listenNewHeadsRequest(): JsonRpcRequest { + override fun listenNewHeadsRequest(): ChainRequest { throw NotImplementedError() } - override fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest { + override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest { throw NotImplementedError() } @@ -62,7 +62,7 @@ object NearChainSpecific : AbstractPollChainSpecific() { upstream, options, SingleCallValidator( - JsonRpcRequest("status", ListParams()), + ChainRequest("status", ListParams()), ) { data -> validate(data) }, @@ -82,8 +82,8 @@ object NearChainSpecific : AbstractPollChainSpecific() { } } - override fun latestBlockRequest(): JsonRpcRequest = // {...} - JsonRpcRequest("block", ObjectParams("finality" to "optimistic")) + override fun latestBlockRequest(): ChainRequest = // {...} + ChainRequest("block", ObjectParams("finality" to "optimistic")) } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearLowerBoundBlockDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearLowerBoundBlockDetector.kt index 72b9e92f..1e06d3d5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearLowerBoundBlockDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearLowerBoundBlockDetector.kt @@ -2,9 +2,9 @@ package io.emeraldpay.dshackle.upstream.near import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.publisher.Mono @@ -14,7 +14,7 @@ class NearLowerBoundBlockDetector( ) : LowerBoundBlockDetector(chain, upstream) { override fun lowerBlockDetect(): Mono { - return upstream.getIngressReader().read(JsonRpcRequest("status", ListParams())).map { + return upstream.getIngressReader().read(ChainRequest("status", ListParams())).map { val resp = Global.objectMapper.readValue(it.getResult(), NearStatus::class.java) LowerBlockData(resp.syncInfo.earliestHeight, null, resp.syncInfo.earliestBlockTime.toEpochMilli()) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt index 63a407b6..5d8f3a46 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt @@ -8,8 +8,9 @@ import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.foundation.ChainOptions.Options -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.CachingReader +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription @@ -28,7 +29,6 @@ import io.emeraldpay.dshackle.upstream.generic.GenericEgressSubscription import io.emeraldpay.dshackle.upstream.generic.GenericIngressSubscription import io.emeraldpay.dshackle.upstream.generic.GenericUpstreamValidator import io.emeraldpay.dshackle.upstream.generic.LocalReader -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.slf4j.LoggerFactory import reactor.core.publisher.Mono @@ -66,21 +66,21 @@ object PolkadotChainSpecific : AbstractPollChainSpecific() { ) } - override fun latestBlockRequest(): JsonRpcRequest = - JsonRpcRequest("chain_getBlock", ListParams()) + override fun latestBlockRequest(): ChainRequest = + ChainRequest("chain_getBlock", ListParams()) - override fun listenNewHeadsRequest(): JsonRpcRequest = - JsonRpcRequest("chain_subscribeNewHeads", ListParams()) + override fun listenNewHeadsRequest(): ChainRequest = + ChainRequest("chain_subscribeNewHeads", ListParams()) - override fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest = - JsonRpcRequest("chain_unsubscribeNewHeads", ListParams(subId)) + override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest = + ChainRequest("chain_unsubscribeNewHeads", ListParams(subId)) override fun localReaderBuilder( cachingReader: CachingReader, methods: CallMethods, head: Head, logsOracle: LogsOracle?, - ): Mono { + ): Mono { return Mono.just(LocalReader(methods)) } @@ -98,7 +98,7 @@ object PolkadotChainSpecific : AbstractPollChainSpecific() { upstream, options, SingleCallValidator( - JsonRpcRequest("system_health", ListParams()), + ChainRequest("system_health", ListParams()), ) { data -> validate(data, options.minPeers, upstream.getId()) }, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotLowerBoundBlockDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotLowerBoundBlockDetector.kt index de919cac..731116fc 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotLowerBoundBlockDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotLowerBoundBlockDetector.kt @@ -1,10 +1,10 @@ package io.emeraldpay.dshackle.upstream.polkadot import io.emeraldpay.dshackle.Chain +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.RecursiveLowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.toHex import reactor.core.publisher.Mono @@ -22,25 +22,25 @@ class PolkadotLowerBoundBlockDetector( override fun hasState(blockNumber: Long): Mono { return upstream.getIngressReader().read( - JsonRpcRequest( + ChainRequest( "chain_getBlockHash", ListParams(blockNumber.toHex()), // in polkadot state methods work only with hash ), ) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { String(it, 1, it.size - 2) } .flatMap { upstream.getIngressReader().read( - JsonRpcRequest( + ChainRequest( "state_getMetadata", ListParams(it), ), ) } .retryWhen(retrySpec(nonRetryableErrors)) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { true } .onErrorReturn(false) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestHttpReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestHttpReader.kt new file mode 100644 index 00000000..9ff947ec --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestHttpReader.kt @@ -0,0 +1,112 @@ +package io.emeraldpay.dshackle.upstream.restclient + +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.config.AuthConfig +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse +import io.emeraldpay.dshackle.upstream.HttpReader +import io.emeraldpay.dshackle.upstream.RequestMetrics +import io.emeraldpay.dshackle.upstream.rpcclient.ResponseRpcParser +import io.emeraldpay.dshackle.upstream.rpcclient.RestParams +import io.emeraldpay.dshackle.upstream.stream.AggregateResponse +import io.emeraldpay.dshackle.upstream.stream.Chunk +import io.emeraldpay.dshackle.upstream.stream.Response +import io.emeraldpay.dshackle.upstream.stream.StreamResponse +import io.netty.buffer.Unpooled +import io.netty.handler.codec.http.HttpMethod +import org.apache.commons.lang3.time.StopWatch +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.kotlin.core.publisher.switchIfEmpty +import java.util.concurrent.TimeUnit + +class RestHttpReader( + target: String, + metrics: RequestMetrics, + basicAuth: AuthConfig.ClientBasicAuth? = null, + tlsCAAuth: ByteArray? = null, +) : HttpReader(target, metrics, basicAuth, tlsCAAuth) { + + private val parser = ResponseRpcParser() + private val requestParser = RestRequestParser + + override fun internalRead(key: ChainRequest): Mono { + val startTime = StopWatch() + return Mono.just(key) + .doOnNext { + if (!startTime.isStarted) { + startTime.start() + } + } + .flatMap(this::execute) + .doOnNext { + if (startTime.isStarted) { + metrics.timer.record(startTime.nanoTime, TimeUnit.NANOSECONDS) + } + } + .handle { it, sink -> + when (it) { + is StreamResponse -> sink.next(ChainResponse(it.stream, key.id)) + is AggregateResponse -> { + if (it.code != 200) { + val error = parser.readError(Global.objectMapper.createParser(it.response)) + sink.next(ChainResponse(null, error)) + } else { + sink.next(ChainResponse(it.response, null)) + } + } + else -> sink.error(IllegalStateException("Wrong response type")) + } + } + } + + private fun execute(key: ChainRequest): Mono { + val restParams = key.params as RestParams + + val methodParams = key.method.split("#") + val restMethod = methodParams[0] + val path = methodParams[1] + + val url = target + .plus(requestParser.transformPathParams(path, restParams.pathParams)) + .plus(requestParser.transformQueryParams(restParams.queryParams)) + + val response = httpClient.headers { headers -> + restParams.headers.forEach { + headers.add(it.key, it.value) + } + } + .request(HttpMethod.valueOf(restMethod)) + .uri(url) + .send(Mono.just(Unpooled.wrappedBuffer(key.toJson()))) + + return if (!key.isStreamed) { + response.response { header, bytes -> + val statusCode = header.status().code() + + bytes.aggregate().asByteArray().map { + AggregateResponse(it, statusCode) + }.switchIfEmpty { + Mono.just(AggregateResponse(ByteArray(0), statusCode)) + } + }.single() + } else { + response.responseConnection { t, u -> + if (t.status().code() != 200) { + u.inbound().receive().aggregate().asByteArray() + .map { AggregateResponse(it, t.status().code()) } + } else { + Mono.just( + StreamResponse( + Flux.concat( + u.inbound().receive().asByteArray() + .map { Chunk(it, false) }, + Mono.just(Chunk(ByteArray(0), true)), + ), + ), + ) + } + }.single() + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParser.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParser.kt new file mode 100644 index 00000000..a301e07b --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParser.kt @@ -0,0 +1,39 @@ +package io.emeraldpay.dshackle.upstream.restclient + +import java.util.ArrayDeque +import java.util.Queue + +object RestRequestParser { + + fun transformPathParams(path: String, pathParams: List): String { + if (pathParams.isEmpty()) { + return path + } + val params: Queue = ArrayDeque(pathParams) + val paramPlaceholder = '*' + var occurrenceIndex = path.indexOf(paramPlaceholder) + + if (occurrenceIndex < 0) { + return path + } + + val builder = StringBuilder() + + var i = 0 + while (occurrenceIndex >= 0 && params.isNotEmpty()) { + val param = params.poll() + builder.append(path, i, occurrenceIndex).append(param) + i = occurrenceIndex + 1 + occurrenceIndex = path.indexOf(paramPlaceholder, occurrenceIndex + 1) + } + + return builder.append(path, i, path.length).toString() + } + + fun transformQueryParams(queryParams: Map): String { + if (queryParams.isEmpty()) { + return "" + } + return "?".plus(queryParams.entries.joinToString("&") { "${it.key}=${it.value}" }) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/CallParams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/CallParams.kt index 3c533c60..e94e5d41 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/CallParams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/CallParams.kt @@ -1,11 +1,76 @@ package io.emeraldpay.dshackle.upstream.rpcclient -sealed interface CallParams +import io.emeraldpay.dshackle.Global -data class ListParams(val list: List) : CallParams { +sealed interface CallParams { + fun toJson(id: Int, method: String): ByteArray +} + +abstract class JsonRpcParams : CallParams { + + override fun toJson(id: Int, method: String): ByteArray { + val json = mapOf( + "jsonrpc" to "2.0", + "id" to id, + "method" to method, + "params" to params(), + ) + + return Global.objectMapper.writeValueAsBytes(json) + } + + protected abstract fun params(): Any +} + +data class ListParams(val list: List) : JsonRpcParams() { constructor(vararg elements: Any) : this(listOf(*elements)) constructor() : this(listOf()) + + override fun params(): Any { + return list + } } -data class ObjectParams(val obj: Map) : CallParams { + +data class ObjectParams(val obj: Map) : JsonRpcParams() { constructor(vararg pairs: Pair) : this(mapOf(*pairs)) + + override fun params(): Any { + return obj + } +} + +data class RestParams( + val headers: Map, + val queryParams: Map, + val pathParams: List, + val payload: ByteArray, +) : CallParams { + companion object { + fun emptyParams() = RestParams(emptyMap(), emptyMap(), emptyList(), ByteArray(0)) + } + + override fun toJson(id: Int, method: String): ByteArray { + return payload + } + + // we need equals and hashCode because of having ByteArray + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RestParams) return false + + if (headers != other.headers) return false + if (queryParams != other.queryParams) return false + if (pathParams != other.pathParams) return false + if (!payload.contentEquals(other.payload)) return false + + return true + } + + override fun hashCode(): Int { + var result = headers.hashCode() + result = 31 * result + queryParams.hashCode() + result = 31 * result + pathParams.hashCode() + result = 31 * result + payload.contentHashCode() + return result + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClient.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClient.kt index 42ad1350..fe6c4e96 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClient.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClient.kt @@ -21,37 +21,35 @@ import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallReplySignature import io.emeraldpay.api.proto.ReactorBlockchainGrpc import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Global -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse +import io.emeraldpay.dshackle.upstream.RequestMetrics import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import io.grpc.StatusRuntimeException import org.apache.commons.lang3.time.StopWatch -import org.slf4j.LoggerFactory import reactor.core.publisher.Mono import java.util.concurrent.TimeUnit class JsonRpcGrpcClient( private val stub: ReactorBlockchainGrpc.ReactorBlockchainStub, private val chain: Chain, - private val metrics: RpcMetrics?, + private val metrics: RequestMetrics?, ) { - companion object { - private val log = LoggerFactory.getLogger(JsonRpcGrpcClient::class.java) - } - - fun getReader(): JsonRpcReader { + fun getReader(): ChainReader { return Executor(stub, chain, metrics) } class Executor( private val stub: ReactorBlockchainGrpc.ReactorBlockchainStub, private val chain: Chain, - private val metrics: RpcMetrics?, - ) : JsonRpcReader { + private val metrics: RequestMetrics?, + ) : ChainReader { - override fun read(key: JsonRpcRequest): Mono { + override fun read(key: ChainRequest): Mono { val timer = StopWatch() val req = BlockchainOuterClass.NativeCallRequest.newBuilder() .setChainValue(chain.id) @@ -61,16 +59,28 @@ class JsonRpcGrpcClient( val reqItem = BlockchainOuterClass.NativeCallItem.newBuilder() .setId(1) .setMethod(key.method) - .setPayload( + if (key.params is RestParams) { + reqItem.setRestData( + BlockchainOuterClass.RestData.newBuilder() + .addAllHeaders(mapKeyValue(key.params.headers)) + .addAllQueryParams(mapKeyValue(key.params.queryParams)) + .setPayload(ByteString.copyFrom(Global.objectMapper.writeValueAsBytes(key.params.payload))) + .addAllPathParams(key.params.pathParams) + .build(), + ) + } else { + reqItem.setPayload( ByteString.copyFrom( Global.objectMapper.writeValueAsBytes( when (key.params) { is ListParams -> key.params.list is ObjectParams -> key.params.obj + else -> throw IllegalStateException("Wrong param types ${key.params.javaClass}") }, ), ), ) + } if (key.nonce != null) { reqItem.nonce = key.nonce } @@ -91,7 +101,7 @@ class JsonRpcGrpcClient( } } - fun handleResponse(resp: BlockchainOuterClass.NativeCallReplyItem): Mono = + fun handleResponse(resp: BlockchainOuterClass.NativeCallReplyItem): Mono = if (resp.succeed) { val bytes = resp.payload.toByteArray() val signature = if (resp.hasSignature()) { @@ -99,7 +109,7 @@ class JsonRpcGrpcClient( } else { null } - Mono.just(JsonRpcResponse(bytes, null, JsonRpcResponse.NumberId(0), null, signature, resp.upstreamId)) + Mono.just(ChainResponse(bytes, null, ChainResponse.NumberId(0), null, signature, resp.upstreamId)) } else { metrics?.fails?.increment() Mono.error( @@ -140,5 +150,14 @@ class JsonRpcGrpcClient( resp.keyId, ) } + + private fun mapKeyValue(entries: Map) = + entries.map { + BlockchainOuterClass.KeyValue + .newBuilder() + .setKey(it.key) + .setValue(it.value) + .build() + } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpClient.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpClient.kt deleted file mode 100644 index 40273c17..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpClient.kt +++ /dev/null @@ -1,223 +0,0 @@ -/** - * Copyright (c) 2020 EmeraldPay, Inc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.emeraldpay.dshackle.upstream.rpcclient - -import io.emeraldpay.dshackle.config.AuthConfig -import io.emeraldpay.dshackle.reader.JsonRpcHttpReader -import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException -import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.stream.AggregateResponse -import io.emeraldpay.dshackle.upstream.rpcclient.stream.JsonRpcStreamParser -import io.emeraldpay.dshackle.upstream.rpcclient.stream.Response -import io.emeraldpay.dshackle.upstream.rpcclient.stream.SingleResponse -import io.emeraldpay.dshackle.upstream.rpcclient.stream.StreamResponse -import io.micrometer.core.instrument.Metrics -import io.netty.buffer.Unpooled -import io.netty.handler.codec.http.HttpHeaderNames -import io.netty.handler.codec.http.HttpHeaders -import io.netty.handler.ssl.SslContextBuilder -import io.netty.resolver.DefaultAddressResolverGroup -import org.apache.commons.lang3.time.StopWatch -import reactor.core.publisher.Mono -import reactor.netty.http.client.HttpClient -import reactor.netty.resources.ConnectionProvider -import java.io.ByteArrayInputStream -import java.security.KeyStore -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import java.util.Base64 -import java.util.concurrent.TimeUnit -import java.util.function.Consumer -import java.util.function.Function - -/** - * JSON RPC client - */ -class JsonRpcHttpClient( - private val target: String, - private val metrics: RpcMetrics, - basicAuth: AuthConfig.ClientBasicAuth? = null, - tlsCAAuth: ByteArray? = null, -) : JsonRpcHttpReader { - - private val parser = ResponseRpcParser() - private val streamParser = JsonRpcStreamParser() - private val httpClient: HttpClient - - init { - val connectionProvider = ConnectionProvider.builder("dshackleConnectionPool") - .maxConnections(1500) - .pendingAcquireMaxCount(10000) - .build() - - var build = HttpClient.create(connectionProvider) - .compress(true) - .resolver(DefaultAddressResolverGroup.INSTANCE) - - build = build.headers { h -> - h.add(HttpHeaderNames.CONTENT_TYPE, "application/json") - } - - basicAuth?.let { auth -> - val authString: String = auth.username + ":" + auth.password - val authBase64 = Base64.getEncoder().encodeToString(authString.toByteArray()) - val encodedAuth = "Basic $authBase64" - val headers = Consumer { h: HttpHeaders -> h.add(HttpHeaderNames.AUTHORIZATION, encodedAuth) } - build = build.headers(headers) - } - - tlsCAAuth?.let { auth -> - val cf = CertificateFactory.getInstance("X.509") - val cert = cf.generateCertificate(ByteArrayInputStream(auth)) as X509Certificate - val ks = KeyStore.getInstance(KeyStore.getDefaultType()) - ks.load(null, "".toCharArray()) - ks.setCertificateEntry("server", cert) - val sslContext = SslContextBuilder.forClient().trustManager(cert).build() - - build.secure { spec -> - spec.sslContext(sslContext) - } - } - - this.httpClient = build - } - - private fun execute(request: JsonRpcRequest): Mono { - val bytesRequest = request.toJson() - - val response = httpClient - .post() - .uri(target) - .send(Mono.just(Unpooled.wrappedBuffer(bytesRequest))) - - return if (!request.isStreamed) { - response.response { header, bytes -> - val statusCode = header.status().code() - - bytes.aggregate().asByteArray().map { - AggregateResponse(it, statusCode) - } - }.single() - } else { - response.responseConnection { t, u -> - streamParser.streamParse( - t.status().code(), - u.inbound().receive().asByteArray(), - ) - }.single() - } - } - - override fun onStop() { - Metrics.globalRegistry.remove(metrics.timer) - Metrics.globalRegistry.remove(metrics.fails) - } - - override fun read(key: JsonRpcRequest): Mono { - val startTime = StopWatch() - return Mono.just(key) - .doOnNext { - if (!startTime.isStarted) { - startTime.start() - } - } - .flatMap(this@JsonRpcHttpClient::execute) - .doOnNext { - if (startTime.isStarted) { - metrics.timer.record(startTime.nanoTime, TimeUnit.NANOSECONDS) - } - } - .transform(asJsonRpcResponse(key)) - .transform(convertErrors(key)) - .transform(throwIfError()) - } - - /** - * The subscribers expect to catch an exception if the response contains JSON RPC Error. Convert it here to JsonRpcException - */ - private fun throwIfError(): Function, Mono> { - return Function { resp -> - resp.flatMap { - if (it.hasError()) { - Mono.error(JsonRpcUpstreamException(it.id, it.error!!)) - } else { - Mono.just(it) - } - } - } - } - - /** - * Convert internal exceptions to standard JsonRpcException - */ - private fun convertErrors(key: JsonRpcRequest): Function, Mono> { - return Function { resp -> - resp.onErrorResume { t -> - val err = when (t) { - is RpcException -> JsonRpcException.from(t) - is JsonRpcException -> t - else -> JsonRpcException(key.id, t.message ?: t.javaClass.name, cause = t) - } - // here we're measure the internal errors, not upstream errors - metrics.fails.increment() - Mono.error(err) - } - } - } - - /** - * Process response from the upstream and convert it to JsonRpcResponse. - * The input is a pair of (Http Status Code, Http Response Body) - */ - private fun asJsonRpcResponse(key: JsonRpcRequest): Function, Mono> { - return Function { resp -> - resp.map { - when (it) { - is AggregateResponse -> { - val parsed = parser.parse(it.response) - val statusCode = it.code - if (statusCode != 200) { - if (parsed.hasError() && parsed.error!!.code != RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE) { - // extracted the error details from the HTTP Body - parsed - } else { - // here we got a valid response with ERROR as HTTP Status Code. We assume that HTTP Status has - // a higher priority so return an error here anyway - JsonRpcResponse.error( - RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, - "HTTP Code: $statusCode", - JsonRpcResponse.NumberId(key.id), - ) - } - } else { - parsed - } - } - is StreamResponse -> { - JsonRpcResponse(it.stream, key.id) - } - is SingleResponse -> { - if (it.hasError()) { - JsonRpcResponse(null, it.error) - } else { - JsonRpcResponse(it.result, null) - } - } - } - } - } - } -} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpReader.kt new file mode 100644 index 00000000..a9646459 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpReader.kt @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2020 EmeraldPay, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.emeraldpay.dshackle.upstream.rpcclient + +import io.emeraldpay.dshackle.config.AuthConfig +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse +import io.emeraldpay.dshackle.upstream.HttpReader +import io.emeraldpay.dshackle.upstream.RequestMetrics +import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError +import io.emeraldpay.dshackle.upstream.rpcclient.stream.JsonRpcStreamParser +import io.emeraldpay.dshackle.upstream.stream.AggregateResponse +import io.emeraldpay.dshackle.upstream.stream.Response +import io.emeraldpay.dshackle.upstream.stream.SingleResponse +import io.emeraldpay.dshackle.upstream.stream.StreamResponse +import io.netty.buffer.Unpooled +import org.apache.commons.lang3.time.StopWatch +import reactor.core.publisher.Mono +import java.util.concurrent.TimeUnit +import java.util.function.Function + +/** + * JSON RPC client + */ +class JsonRpcHttpReader( + target: String, + metrics: RequestMetrics, + basicAuth: AuthConfig.ClientBasicAuth? = null, + tlsCAAuth: ByteArray? = null, +) : HttpReader(target, metrics, basicAuth, tlsCAAuth) { + + private val parser = ResponseRpcParser() + private val streamParser = JsonRpcStreamParser() + + private fun execute(request: ChainRequest): Mono { + val bytesRequest = request.toJson() + + val response = httpClient + .post() + .uri(target) + .send(Mono.just(Unpooled.wrappedBuffer(bytesRequest))) + + return if (!request.isStreamed) { + response.response { header, bytes -> + val statusCode = header.status().code() + + bytes.aggregate().asByteArray().map { + AggregateResponse(it, statusCode) + } + }.single() + } else { + response.responseConnection { t, u -> + streamParser.streamParse( + t.status().code(), + u.inbound().receive().asByteArray(), + ) + }.single() + } + } + + override fun internalRead(key: ChainRequest): Mono { + val startTime = StopWatch() + return Mono.just(key) + .doOnNext { + if (!startTime.isStarted) { + startTime.start() + } + } + .flatMap(this@JsonRpcHttpReader::execute) + .doOnNext { + if (startTime.isStarted) { + metrics.timer.record(startTime.nanoTime, TimeUnit.NANOSECONDS) + } + } + .transform(asJsonRpcResponse(key)) + } + + /** + * Process response from the upstream and convert it to JsonRpcResponse. + * The input is a pair of (Http Status Code, Http Response Body) + */ + private fun asJsonRpcResponse(key: ChainRequest): Function, Mono> { + return Function { resp -> + resp.map { + when (it) { + is AggregateResponse -> { + val parsed = parser.parse(it.response) + val statusCode = it.code + if (statusCode != 200) { + if (parsed.hasError() && parsed.error!!.code != RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE) { + // extracted the error details from the HTTP Body + parsed + } else { + // here we got a valid response with ERROR as HTTP Status Code. We assume that HTTP Status has + // a higher priority so return an error here anyway + ChainResponse.error( + RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, + "HTTP Code: $statusCode", + ChainResponse.NumberId(key.id), + ) + } + } else { + parsed + } + } + is StreamResponse -> { + ChainResponse(it.stream, key.id) + } + is SingleResponse -> { + if (it.hasError()) { + ChainResponse(null, it.error) + } else { + ChainResponse(it.result, null) + } + } + } + } + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcUpstreamException.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcUpstreamException.kt deleted file mode 100644 index 2a7f6c53..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcUpstreamException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.emeraldpay.dshackle.upstream.rpcclient - -class JsonRpcUpstreamException( - id: JsonRpcResponse.Id, - error: JsonRpcError, -) : JsonRpcException(id, error, null, false) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClient.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClient.kt index 7381afdb..671e6da5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClient.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClient.kt @@ -15,22 +15,26 @@ */ package io.emeraldpay.dshackle.upstream.rpcclient -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionPool import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError import reactor.core.publisher.Mono class JsonRpcWsClient( private val wsPool: WsConnectionPool, -) : JsonRpcReader { +) : ChainReader { - override fun read(key: JsonRpcRequest): Mono { + override fun read(key: ChainRequest): Mono { val conn = wsPool.getConnection() if (!conn.isConnected) { return Mono.error( - JsonRpcException( - JsonRpcResponse.NumberId(key.id), - JsonRpcError( + ChainException( + ChainResponse.NumberId(key.id), + ChainCallError( RpcResponseError.CODE_UPSTREAM_CONNECTION_ERROR, "WebSocket is not connected", ), diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsMessage.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsMessage.kt index 9c74d39a..6cbe772d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsMessage.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsMessage.kt @@ -15,8 +15,10 @@ */ package io.emeraldpay.dshackle.upstream.rpcclient +import io.emeraldpay.dshackle.upstream.ChainCallError + class JsonRpcWsMessage( val result: ByteArray?, - val error: JsonRpcError?, + val error: ChainCallError?, val subscriptionId: String, ) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt index ce8a4299..bdd9c429 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseParser.kt @@ -20,6 +20,8 @@ import com.fasterxml.jackson.core.JsonParseException import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonToken import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError import org.slf4j.LoggerFactory import java.io.IOException @@ -45,7 +47,7 @@ abstract class ResponseParser { parser.nextToken() if (parser.currentToken != JsonToken.START_OBJECT) { return Preparsed( - error = JsonRpcError( + error = ChainCallError( RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON: not an Object", ), @@ -59,13 +61,13 @@ abstract class ResponseParser { log.warn("Failed to parse JSON from upstream: ${e.message}") } if (state.error != null && state.id == null) { - state = state.copy(id = JsonRpcResponse.NumberId(0)) + state = state.copy(id = ChainResponse.NumberId(0)) } if (state.isReady) { return state } return Preparsed( - error = JsonRpcError( + error = ChainCallError( RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON structure: never finalized", ), @@ -76,7 +78,7 @@ abstract class ResponseParser { if (field == "jsonrpc") { if (!parser.nextToken().isScalarValue) { return state.copy( - error = JsonRpcError( + error = ChainCallError( RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON (jsonrpc value)", ), @@ -103,17 +105,17 @@ abstract class ResponseParser { return state } - private fun readId(parser: JsonParser): JsonRpcResponse.Id { + private fun readId(parser: JsonParser): ChainResponse.Id { if (parser.currentToken() == JsonToken.FIELD_NAME) { parser.nextToken() } return if (parser.currentToken() == JsonToken.VALUE_NUMBER_INT) { - JsonRpcResponse.NumberId(parser.intValue) + ChainResponse.NumberId(parser.intValue) } else if (parser.currentToken() == JsonToken.VALUE_STRING) { - JsonRpcResponse.StringId(parser.text) + ChainResponse.StringId(parser.text) } else { log.warn("Invalid id type: ${parser.currentToken()}") - return JsonRpcResponse.NumberId(0) + return ChainResponse.NumberId(0) } } @@ -151,7 +153,7 @@ abstract class ResponseParser { } } - fun readError(parser: JsonParser): JsonRpcError? { + fun readError(parser: JsonParser): ChainCallError? { var code = 0 var message = "" var details: Any? = null @@ -202,14 +204,14 @@ abstract class ResponseParser { } } } - return JsonRpcError(code, message, details) + return ChainCallError(code, message, details) } data class Preparsed( - val id: JsonRpcResponse.Id? = null, + val id: ChainResponse.Id? = null, val result: ByteArray? = null, val nullResult: Boolean = false, - val error: JsonRpcError? = null, + val error: ChainCallError? = null, val subMethod: String? = null, val subId: String? = null, ) { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParser.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParser.kt index 50e9f827..e266f2b4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParser.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseRpcParser.kt @@ -15,21 +15,22 @@ */ package io.emeraldpay.dshackle.upstream.rpcclient +import io.emeraldpay.dshackle.upstream.ChainResponse import org.slf4j.LoggerFactory -open class ResponseRpcParser : ResponseParser() { +open class ResponseRpcParser : ResponseParser() { companion object { private val log = LoggerFactory.getLogger(ResponseRpcParser::class.java) } - override fun build(state: Preparsed): JsonRpcResponse { + override fun build(state: Preparsed): ChainResponse { if (state.error != null) { - return JsonRpcResponse(null, state.error, state.id ?: JsonRpcResponse.Id.from(-1), null) + return ChainResponse(null, state.error, state.id ?: ChainResponse.Id.from(-1), null) } if (state.nullResult) { - return JsonRpcResponse("null".toByteArray(), null, state.id ?: JsonRpcResponse.Id.from(-1), null) + return ChainResponse("null".toByteArray(), null, state.id ?: ChainResponse.Id.from(-1), null) } - return JsonRpcResponse(state.result, null, state.id ?: JsonRpcResponse.Id.from(-1), null) + return ChainResponse(state.result, null, state.id ?: ChainResponse.Id.from(-1), null) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseWSParser.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseWSParser.kt index 14a2062a..ca465105 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseWSParser.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/ResponseWSParser.kt @@ -17,6 +17,8 @@ package io.emeraldpay.dshackle.upstream.rpcclient import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonToken +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainResponse import org.slf4j.LoggerFactory import java.io.IOException @@ -39,7 +41,7 @@ class ResponseWSParser : ResponseParser() { if (state.isSubReady) { return WsResponse( Type.SUBSCRIPTION, - JsonRpcResponse.Id.from(state.subId!!), + ChainResponse.Id.from(state.subId!!), if (state.nullResult) NULL_RESULT else state.result, state.error, ) @@ -101,8 +103,8 @@ class ResponseWSParser : ResponseParser() { data class WsResponse( val type: Type, - val id: JsonRpcResponse.Id, + val id: ChainResponse.Id, val value: ByteArray?, - val error: JsonRpcError?, + val error: ChainCallError?, ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParser.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParser.kt index 9e4673df..a9b6eff4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParser.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParser.kt @@ -3,9 +3,14 @@ package io.emeraldpay.dshackle.upstream.rpcclient.stream import com.fasterxml.jackson.core.JsonFactory import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonToken +import io.emeraldpay.dshackle.upstream.ChainCallError import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError import io.emeraldpay.dshackle.upstream.rpcclient.ResponseRpcParser +import io.emeraldpay.dshackle.upstream.stream.AggregateResponse +import io.emeraldpay.dshackle.upstream.stream.Chunk +import io.emeraldpay.dshackle.upstream.stream.Response +import io.emeraldpay.dshackle.upstream.stream.SingleResponse +import io.emeraldpay.dshackle.upstream.stream.StreamResponse import org.slf4j.LoggerFactory import reactor.core.publisher.Flux import reactor.core.publisher.Mono @@ -66,7 +71,7 @@ class JsonRpcStreamParser( Mono.just( SingleResponse( null, - JsonRpcError(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, it.message ?: "Internal error"), + ChainCallError(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, it.message ?: "Internal error"), ), ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt index 8ef50374..6979e96d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt @@ -8,7 +8,8 @@ import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.foundation.ChainOptions.Options -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.DefaultSolanaMethods import io.emeraldpay.dshackle.upstream.EgressSubscription import io.emeraldpay.dshackle.upstream.IngressSubscription @@ -24,7 +25,6 @@ import io.emeraldpay.dshackle.upstream.generic.AbstractChainSpecific import io.emeraldpay.dshackle.upstream.generic.GenericEgressSubscription import io.emeraldpay.dshackle.upstream.generic.GenericIngressSubscription import io.emeraldpay.dshackle.upstream.generic.GenericUpstreamValidator -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.slf4j.LoggerFactory import reactor.core.publisher.Mono @@ -36,11 +36,11 @@ object SolanaChainSpecific : AbstractChainSpecific() { private val log = LoggerFactory.getLogger(SolanaChainSpecific::class.java) - override fun getLatestBlock(api: JsonRpcReader, upstreamId: String): Mono { - return api.read(JsonRpcRequest("getSlot", ListParams())).flatMap { + override fun getLatestBlock(api: ChainReader, upstreamId: String): Mono { + return api.read(ChainRequest("getSlot", ListParams())).flatMap { val slot = it.getResultAsProcessedString().toLong() api.read( - JsonRpcRequest( + ChainRequest( "getBlocks", ListParams( slot - 10, @@ -53,7 +53,7 @@ object SolanaChainSpecific : AbstractChainSpecific() { Mono.empty() } else { api.read( - JsonRpcRequest( + ChainRequest( "getBlock", ListParams( response.max(), @@ -98,8 +98,8 @@ object SolanaChainSpecific : AbstractChainSpecific() { ) } - override fun listenNewHeadsRequest(): JsonRpcRequest { - return JsonRpcRequest( + override fun listenNewHeadsRequest(): ChainRequest { + return ChainRequest( "blockSubscribe", ListParams( "all", @@ -111,8 +111,8 @@ object SolanaChainSpecific : AbstractChainSpecific() { ) } - override fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest { - return JsonRpcRequest("blockUnsubscribe", ListParams(subId)) + override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest { + return ChainRequest("blockUnsubscribe", ListParams(subId)) } override fun validator( @@ -125,7 +125,7 @@ object SolanaChainSpecific : AbstractChainSpecific() { upstream, options, SingleCallValidator( - JsonRpcRequest("getHealth", ListParams()), + ChainRequest("getHealth", ListParams()), ) { data -> val resp = String(data) if (resp == "\"ok\"") { @@ -142,7 +142,7 @@ object SolanaChainSpecific : AbstractChainSpecific() { return SolanaLowerBoundBlockDetector(chain, upstream) } - override fun labelDetector(chain: Chain, reader: JsonRpcReader): LabelsDetector? { + override fun labelDetector(chain: Chain, reader: ChainReader): LabelsDetector? { return null } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetector.kt index 4aa77152..b876d657 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetector.kt @@ -2,10 +2,10 @@ package io.emeraldpay.dshackle.upstream.solana import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.publisher.Mono import reactor.util.retry.Retry @@ -22,10 +22,10 @@ class SolanaLowerBoundBlockDetector( return Mono.just(reader) .flatMap { it.read( - JsonRpcRequest("getFirstAvailableBlock", ListParams()), // in case of solana we talk about the slot of the lowest confirmed block + ChainRequest("getFirstAvailableBlock", ListParams()), // in case of solana we talk about the slot of the lowest confirmed block ) } - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { val slot = String(it).toLong() if (slot == 0L) { @@ -36,7 +36,7 @@ class SolanaLowerBoundBlockDetector( } .flatMap { reader.read( - JsonRpcRequest( + ChainRequest( "getBlock", // since getFirstAvailableBlock returns the slot of the lowest confirmed block we can directly call getBlock ListParams( it, @@ -48,7 +48,7 @@ class SolanaLowerBoundBlockDetector( ), ), ) - .flatMap(JsonRpcResponse::requireResult) + .flatMap(ChainResponse::requireResult) .map { blockData -> val block = Global.objectMapper.readValue(blockData, SolanaBlock::class.java) LowerBlockData(max(block.height, 1), it) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt index ea05fed3..0936ff1c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt @@ -8,6 +8,7 @@ import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.foundation.ChainOptions.Options +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.SingleCallValidator import io.emeraldpay.dshackle.upstream.Upstream @@ -15,7 +16,6 @@ import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.UpstreamValidator import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific import io.emeraldpay.dshackle.upstream.generic.GenericUpstreamValidator -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.slf4j.LoggerFactory import java.math.BigInteger @@ -46,11 +46,11 @@ object StarknetChainSpecific : AbstractPollChainSpecific() { throw NotImplementedError() } - override fun listenNewHeadsRequest(): JsonRpcRequest { + override fun listenNewHeadsRequest(): ChainRequest { throw NotImplementedError() } - override fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest { + override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest { throw NotImplementedError() } @@ -64,7 +64,7 @@ object StarknetChainSpecific : AbstractPollChainSpecific() { upstream, options, SingleCallValidator( - JsonRpcRequest("starknet_syncing", ListParams()), + ChainRequest("starknet_syncing", ListParams()), ) { data -> validate(data, config.laggingLagSize, upstream.getId()) }, @@ -93,8 +93,8 @@ object StarknetChainSpecific : AbstractPollChainSpecific() { } } - override fun latestBlockRequest(): JsonRpcRequest = - JsonRpcRequest("starknet_getBlockWithTxHashes", ListParams("latest")) + override fun latestBlockRequest(): ChainRequest = + ChainRequest("starknet_getBlockWithTxHashes", ListParams("latest")) } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/Responses.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/stream/Responses.kt similarity index 92% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/Responses.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/stream/Responses.kt index 1295c55d..1b2e7a4f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/Responses.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/stream/Responses.kt @@ -1,13 +1,13 @@ -package io.emeraldpay.dshackle.upstream.rpcclient.stream +package io.emeraldpay.dshackle.upstream.stream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError +import io.emeraldpay.dshackle.upstream.ChainCallError import reactor.core.publisher.Flux sealed class Response data class SingleResponse( val result: ByteArray?, - val error: JsonRpcError?, + val error: ChainCallError?, ) : Response() { fun hasError() = error != null diff --git a/src/test/groovy/io/emeraldpay/dshackle/proxy/HttpHandlerSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/proxy/HttpHandlerSpec.groovy index fe132e93..d35eb52b 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/proxy/HttpHandlerSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/proxy/HttpHandlerSpec.groovy @@ -22,7 +22,7 @@ import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.ProxyConfig import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerHttp import io.emeraldpay.dshackle.rpc.NativeCall -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.micrometer.core.instrument.Counter import reactor.core.publisher.Flux @@ -73,7 +73,7 @@ class HttpHandlerSpec extends Specification { def "Return error on invalid request"() { setup: ReadRpcJson read = Mock(ReadRpcJson) { - 1 * apply(_) >> { throw new RpcException(-32123, "test", new JsonRpcResponse.NumberId(4)) } + 1 * apply(_) >> { throw new RpcException(-32123, "test", new ChainResponse.NumberId(4)) } } Counter errorMetric = Mock(Counter) { 1 * increment() diff --git a/src/test/groovy/io/emeraldpay/dshackle/proxy/ReadRpcJsonSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/proxy/ReadRpcJsonSpec.groovy index 61f5312b..af2d1684 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/proxy/ReadRpcJsonSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/proxy/ReadRpcJsonSpec.groovy @@ -17,7 +17,7 @@ package io.emeraldpay.dshackle.proxy -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import spock.lang.Specification @@ -192,7 +192,7 @@ class ReadRpcJsonSpec extends Specification { def t = thrown(RpcException) t.code == -32600 t.rpcMessage.toLowerCase() == "jsonrpc version is not set" - t.details == new JsonRpcResponse.NumberId(2) + t.details == new ChainResponse.NumberId(2) } def "Error if jsonrpc version is invalid"() { @@ -202,7 +202,7 @@ class ReadRpcJsonSpec extends Specification { def t = thrown(RpcException) t.code == -32600 t.rpcMessage.toLowerCase() == "unsupported json rpc version: 3.0" - t.details == new JsonRpcResponse.NumberId(2) + t.details == new ChainResponse.NumberId(2) } def "Error if method is not set"() { @@ -212,7 +212,7 @@ class ReadRpcJsonSpec extends Specification { def t = thrown(RpcException) t.code == -32600 t.rpcMessage.toLowerCase() == "method is not set" - t.details == new JsonRpcResponse.NumberId(2) + t.details == new ChainResponse.NumberId(2) } def "Error if params is not array"() { @@ -222,7 +222,7 @@ class ReadRpcJsonSpec extends Specification { def t = thrown(RpcException) t.code == -32600 t.rpcMessage.toLowerCase() == "params must be an array" - t.details == new JsonRpcResponse.NumberId(2) + t.details == new ChainResponse.NumberId(2) } def "Error if json is broken"() { diff --git a/src/test/groovy/io/emeraldpay/dshackle/quorum/AlwaysQuorumSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/quorum/AlwaysQuorumSpec.groovy index 1239d6ae..06e54d0f 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/quorum/AlwaysQuorumSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/quorum/AlwaysQuorumSpec.groovy @@ -16,8 +16,8 @@ package io.emeraldpay.dshackle.quorum import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import spock.lang.Specification @@ -28,7 +28,7 @@ class AlwaysQuorumSpec extends Specification { def quorum = new AlwaysQuorum() def up = Stub(Upstream) when: - quorum.record(new JsonRpcException(1, "test"), null, up) + quorum.record(new ChainException(1, "test"), null, up) then: quorum.isFailed() !quorum.isResolved() @@ -43,7 +43,7 @@ class AlwaysQuorumSpec extends Specification { def quorum = new AlwaysQuorum() def up = Stub(Upstream) when: - quorum.record(new JsonRpcResponse("123".bytes, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) + quorum.record(new ChainResponse("123".bytes, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) then: quorum.isResolved() quorum.getResponse().getResult() == "123".bytes diff --git a/src/test/groovy/io/emeraldpay/dshackle/quorum/BroadcastQuorumSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/quorum/BroadcastQuorumSpec.groovy index 9cf88d6a..9625756e 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/quorum/BroadcastQuorumSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/quorum/BroadcastQuorumSpec.groovy @@ -19,8 +19,8 @@ package io.emeraldpay.dshackle.quorum import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import spock.lang.Specification class BroadcastQuorumSpec extends Specification { @@ -35,17 +35,17 @@ class BroadcastQuorumSpec extends Specification { def upstream3 = Stub(Upstream) when: - q.record(new JsonRpcResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream1) + q.record(new ChainResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream1) then: 1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _) when: - q.record(new JsonRpcResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream2) + q.record(new ChainResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream2) then: 1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _) when: - q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3) + q.record(new ChainException(1, "Nonce too low"), null, upstream3) then: 1 * q.recordError(_, _, _) objectMapper.readValue(q.response.getResult(), Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c" @@ -59,17 +59,17 @@ class BroadcastQuorumSpec extends Specification { def upstream3 = Stub(Upstream) when: - q.record(new JsonRpcException(1, "Internal error"), null, upstream1) + q.record(new ChainException(1, "Internal error"), null, upstream1) then: 1 * q.recordError(_, _, _) when: - q.record(new JsonRpcResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream2) + q.record(new ChainResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream2) then: 1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _) when: - q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3) + q.record(new ChainException(1, "Nonce too low"), null, upstream3) then: 1 * q.recordError(_, _, _) q.isResolved() @@ -81,9 +81,9 @@ class BroadcastQuorumSpec extends Specification { def quorum = new BroadcastQuorum() def up = Stub(Upstream) when: - quorum.record(new JsonRpcException(1, "test 1"), null, up) - quorum.record(new JsonRpcException(1, "test 2"), null, up) - quorum.record(new JsonRpcException(1, "test 3"), null, up) + quorum.record(new ChainException(1, "test 1"), null, up) + quorum.record(new ChainException(1, "test 2"), null, up) + quorum.record(new ChainException(1, "test 3"), null, up) then: quorum.isFailed() !quorum.isResolved() diff --git a/src/test/groovy/io/emeraldpay/dshackle/quorum/MaximumValueQuorumSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/quorum/MaximumValueQuorumSpec.groovy index dfaf0f5a..63f5f8bf 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/quorum/MaximumValueQuorumSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/quorum/MaximumValueQuorumSpec.groovy @@ -1,8 +1,8 @@ package io.emeraldpay.dshackle.quorum import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import spock.lang.Specification class MaximumValueQuorumSpec extends Specification { @@ -19,9 +19,9 @@ class MaximumValueQuorumSpec extends Specification { } when: def quorum = new MaximumValueQuorum() - quorum.record(new JsonRpcResponse('"0x137"'.bytes, null), null, up) - quorum.record(new JsonRpcResponse('"0x138"'.bytes, null), null, up1) - quorum.record(new JsonRpcResponse('"0x139"'.bytes, null), null, up2) + quorum.record(new ChainResponse('"0x137"'.bytes, null), null, up) + quorum.record(new ChainResponse('"0x138"'.bytes, null), null, up1) + quorum.record(new ChainResponse('"0x139"'.bytes, null), null, up2) then: quorum.response.result == '"0x139"'.bytes quorum.resolvedBy.size() == 1 @@ -42,9 +42,9 @@ class MaximumValueQuorumSpec extends Specification { } when: def quorum = new MaximumValueQuorum() - quorum.record(new JsonRpcResponse('"0x137"'.bytes, null), null, up) - quorum.record(new JsonRpcResponse('"0x138"'.bytes, null), null, up1) - quorum.record(new JsonRpcException(10, "error"), null, up2) + quorum.record(new ChainResponse('"0x137"'.bytes, null), null, up) + quorum.record(new ChainResponse('"0x138"'.bytes, null), null, up1) + quorum.record(new ChainException(10, "error"), null, up2) then: quorum.response.result == '"0x138"'.bytes quorum.isResolved() @@ -65,13 +65,13 @@ class MaximumValueQuorumSpec extends Specification { } when: def quorum = new MaximumValueQuorum() - quorum.record(new JsonRpcException(10, "error1"), null, up) - quorum.record(new JsonRpcException(10, "error2"), null, up1) - quorum.record(new JsonRpcException(10, "error3"), null, up2) + quorum.record(new ChainException(10, "error1"), null, up) + quorum.record(new ChainException(10, "error2"), null, up1) + quorum.record(new ChainException(10, "error3"), null, up2) then: !quorum.isResolved() quorum.isFailed() - quorum.error == new JsonRpcException(10, "error3").error + quorum.error == new ChainException(10, "error3").error quorum.resolvedBy.size() == 3 } diff --git a/src/test/groovy/io/emeraldpay/dshackle/quorum/NotLaggingQuorumSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/quorum/NotLaggingQuorumSpec.groovy index 7b39ec2d..0f25b3d0 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/quorum/NotLaggingQuorumSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/quorum/NotLaggingQuorumSpec.groovy @@ -17,8 +17,8 @@ package io.emeraldpay.dshackle.quorum import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import spock.lang.Specification @@ -31,7 +31,7 @@ class NotLaggingQuorumSpec extends Specification { def quorum = new NotLaggingQuorum(1) when: - quorum.record(new JsonRpcResponse(value, null), null, up) + quorum.record(new ChainResponse(value, null), null, up) then: 1 * up.getLag() >> 0 quorum.isResolved() @@ -46,7 +46,7 @@ class NotLaggingQuorumSpec extends Specification { def quorum = new NotLaggingQuorum(1) when: - quorum.record(new JsonRpcResponse(value, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) + quorum.record(new ChainResponse(value, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) then: 1 * up.getLag() >> 0 quorum.isResolved() @@ -62,7 +62,7 @@ class NotLaggingQuorumSpec extends Specification { def quorum = new NotLaggingQuorum(1) when: - quorum.record(new JsonRpcResponse(value, null), null, up) + quorum.record(new ChainResponse(value, null), null, up) then: 1 * up.getLag() >> 1 quorum.isResolved() @@ -77,7 +77,7 @@ class NotLaggingQuorumSpec extends Specification { def quorum = new NotLaggingQuorum(1) when: - quorum.record(new JsonRpcResponse(value, null), null, up) + quorum.record(new ChainResponse(value, null), null, up) then: 1 * up.getLag() >> 2 !quorum.isResolved() @@ -91,7 +91,7 @@ class NotLaggingQuorumSpec extends Specification { def quorum = new NotLaggingQuorum(1) when: - quorum.record(new JsonRpcException(-100, "test error"), null, up) + quorum.record(new ChainException(-100, "test error"), null, up) then: 1 * up.getLag() >> 1 !quorum.isResolved() diff --git a/src/test/groovy/io/emeraldpay/dshackle/quorum/NotNullQuorumSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/quorum/NotNullQuorumSpec.groovy index 69512696..616498e5 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/quorum/NotNullQuorumSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/quorum/NotNullQuorumSpec.groovy @@ -1,8 +1,8 @@ package io.emeraldpay.dshackle.quorum import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import spock.lang.Specification @@ -20,7 +20,7 @@ class NotNullQuorumSpec extends Specification { 1 * getId() >> "id2" } def value = "null".getBytes() - def response = new JsonRpcResponse(value, null) + def response = new ChainResponse(value, null) def quorum = new NotNullQuorum() when: @@ -53,15 +53,15 @@ class NotNullQuorumSpec extends Specification { def quorum = new NotNullQuorum() when: - quorum.record(new JsonRpcException(10, "error"), null, up) - quorum.record(new JsonRpcException(10, "error"), null, up1) - quorum.record(new JsonRpcException(10, "error"), null, up2) - quorum.record(new JsonRpcException(10, "error"), null, up) + quorum.record(new ChainException(10, "error"), null, up) + quorum.record(new ChainException(10, "error"), null, up1) + quorum.record(new ChainException(10, "error"), null, up2) + quorum.record(new ChainException(10, "error"), null, up) then: quorum.isFailed() !quorum.isResolved() - quorum.error == new JsonRpcException(10, "error").error + quorum.error == new ChainException(10, "error").error } def "Resolve if one of upstream responds with value"() { @@ -79,10 +79,10 @@ class NotNullQuorumSpec extends Specification { def quorum = new NotNullQuorum() when: - def res = quorum.record(new JsonRpcResponse(value, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) - quorum.record(new JsonRpcException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up1) - quorum.record(new JsonRpcException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up2) - quorum.record(new JsonRpcException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) + def res = quorum.record(new ChainResponse(value, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) + quorum.record(new ChainException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up1) + quorum.record(new ChainException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up2) + quorum.record(new ChainException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up) then: !res diff --git a/src/test/groovy/io/emeraldpay/dshackle/quorum/QuorumRpcReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/quorum/QuorumRequestReaderSpec.groovy similarity index 71% rename from src/test/groovy/io/emeraldpay/dshackle/quorum/QuorumRpcReaderSpec.groovy rename to src/test/groovy/io/emeraldpay/dshackle/quorum/QuorumRequestReaderSpec.groovy index 98cac6fa..83b55a0c 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/quorum/QuorumRpcReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/quorum/QuorumRequestReaderSpec.groovy @@ -21,9 +21,9 @@ import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.FilteredApis import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError @@ -34,7 +34,7 @@ import spock.lang.Specification import java.time.Duration -class QuorumRpcReaderSpec extends Specification { +class QuorumRequestReaderSpec extends Specification { def "always-quorum - get the result if ok"() { setup: @@ -43,17 +43,17 @@ class QuorumRpcReaderSpec extends Specification { _ * getId() >> "id" _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_test", new ListParams())) >> Mono.just(JsonRpcResponse.ok("1")) + 1 * read(new ChainRequest("eth_test", new ListParams())) >> Mono.just(ChainResponse.ok("1")) } } def apis = new FilteredApis( Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new AlwaysQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new AlwaysQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } @@ -68,8 +68,8 @@ class QuorumRpcReaderSpec extends Specification { def "always-quorum - return upstream error returned"() { setup: def api = Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_test", new ListParams())) >>> [ - Mono.just(JsonRpcResponse.error(1, "test")) + 1 * read(new ChainRequest("eth_test", new ListParams())) >>> [ + Mono.just(ChainResponse.error(1, "test")) ] } def up = Mock(Upstream) { @@ -82,10 +82,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new AlwaysQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new AlwaysQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } @@ -93,7 +93,7 @@ class QuorumRpcReaderSpec extends Specification { then: StepVerifier.create(act) .expectErrorMatches { - it instanceof JsonRpcException && ((JsonRpcException) it).error.message == "test" + it instanceof ChainException && ((ChainException) it).error.message == "test" } .verify(Duration.ofSeconds(1)) } @@ -101,7 +101,7 @@ class QuorumRpcReaderSpec extends Specification { def "always-quorum - return upstream error thrown"() { setup: def api = Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_test", new ListParams())) >>> [ + 1 * read(new ChainRequest("eth_test", new ListParams())) >>> [ Mono.error( new RpcException( RpcResponseError.CODE_UPSTREAM_CONNECTION_ERROR, @@ -120,10 +120,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new AlwaysQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new AlwaysQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } @@ -131,7 +131,7 @@ class QuorumRpcReaderSpec extends Specification { then: StepVerifier.create(act) .expectErrorMatches { - it instanceof JsonRpcException && ((JsonRpcException) it).error.message == "test-123" + it instanceof ChainException && ((ChainException) it).error.message == "test-123" } .verify(Duration.ofSeconds(1)) } @@ -143,9 +143,9 @@ class QuorumRpcReaderSpec extends Specification { _ * getId() >> "id" _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getIngressReader() >> Mock(Reader) { - 2 * read(new JsonRpcRequest("eth_test", new ListParams())) >>> [ - Mono.just(JsonRpcResponse.ok("null")), - Mono.just(JsonRpcResponse.ok("1")) + 2 * read(new ChainRequest("eth_test", new ListParams())) >>> [ + Mono.just(ChainResponse.ok("null")), + Mono.just(ChainResponse.ok("1")) ] } } @@ -153,10 +153,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new NotNullQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new NotNullQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } @@ -176,9 +176,9 @@ class QuorumRpcReaderSpec extends Specification { _ * getId() >> "id" _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getIngressReader() >> Mock(Reader) { - 2 * read(new JsonRpcRequest("eth_test", new ListParams())) >>> [ - Mono.just(JsonRpcResponse.error(1, "test")), - Mono.just(JsonRpcResponse.ok("1")) + 2 * read(new ChainRequest("eth_test", new ListParams())) >>> [ + Mono.just(ChainResponse.error(1, "test")), + Mono.just(ChainResponse.ok("1")) ] } } @@ -186,10 +186,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new NotNullQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new NotNullQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } @@ -204,9 +204,9 @@ class QuorumRpcReaderSpec extends Specification { def "non-empty-quorum - error if all failed"() { setup: def api = Mock(Reader) { - 2 * read(new JsonRpcRequest("eth_test", new ListParams())) >>> [ - Mono.just(JsonRpcResponse.error(1, "test")), - Mono.just(JsonRpcResponse.error(1, "test")), + 2 * read(new ChainRequest("eth_test", new ListParams())) >>> [ + Mono.just(ChainResponse.error(1, "test")), + Mono.just(ChainResponse.error(1, "test")), ] } def up = Mock(Upstream) { @@ -219,10 +219,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new NotNullQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new NotNullQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } @@ -236,8 +236,8 @@ class QuorumRpcReaderSpec extends Specification { def "always-quorum - error if failed"() { setup: def api = Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_test", new ListParams())) >>> [ - Mono.just(JsonRpcResponse.error(1, "test error")), + 1 * read(new ChainRequest("eth_test", new ListParams())) >>> [ + Mono.just(ChainResponse.error(1, "test error")), ] } def up = Mock(Upstream) { @@ -250,10 +250,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new AlwaysQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new AlwaysQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } @@ -262,7 +262,7 @@ class QuorumRpcReaderSpec extends Specification { StepVerifier.create(act) .expectErrorMatches { t -> println("Error: $t.class / $t.message") - t instanceof JsonRpcException && t.message == "test error" && t.error.code == 1 + t instanceof ChainException && t.message == "test error" && t.error.code == 1 } .verify(Duration.ofSeconds(2)) } @@ -275,8 +275,8 @@ class QuorumRpcReaderSpec extends Specification { _ * isAvailable() >> true _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getIngressReader() >> Mock(Reader) { - _ * read(new JsonRpcRequest("eth_test", new ListParams())) >>> [ - Mono.just(JsonRpcResponse.error(-3010, "test")), + _ * read(new ChainRequest("eth_test", new ListParams())) >>> [ + Mono.just(ChainResponse.error(-3010, "test")), ] } } @@ -284,10 +284,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new NotLaggingQuorum(1), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new NotLaggingQuorum(1), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) then: StepVerifier.create(act) @@ -310,10 +310,10 @@ class QuorumRpcReaderSpec extends Specification { Chain.ETHEREUM__MAINNET, [up], Selector.empty ) - def reader = new QuorumRpcReader(apis, new AlwaysQuorum(), Stub(Tracer)) + def reader = new QuorumRequestReader(apis, new AlwaysQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_test", new ListParams())) + def act = reader.read(new ChainRequest("eth_test", new ListParams())) .map { new String(it.value) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/quorum/ValueAwareQuorumSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/quorum/ValueAwareQuorumSpec.groovy index 54806fb8..046b24f8 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/quorum/ValueAwareQuorumSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/quorum/ValueAwareQuorumSpec.groovy @@ -17,7 +17,7 @@ package io.emeraldpay.dshackle.quorum import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.Nullable @@ -67,7 +67,7 @@ class ValueAwareQuorumSpec extends Specification { } @Override - void recordValue(@NotNull JsonRpcResponse response, @Nullable Object responseValue, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) { + void recordValue(@NotNull ChainResponse response, @Nullable Object responseValue, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) { } @@ -89,8 +89,8 @@ class ValueAwareQuorumSpec extends Specification { } @Override - JsonRpcResponse getResponse() { - return new JsonRpcResponse(null, null) + ChainResponse getResponse() { + return new ChainResponse(null, null) } @Override diff --git a/src/test/groovy/io/emeraldpay/dshackle/reader/BroadcastReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/reader/BroadcastReaderSpec.groovy index f426dbf0..5d2bcc07 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/reader/BroadcastReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/reader/BroadcastReaderSpec.groovy @@ -3,9 +3,9 @@ package io.emeraldpay.dshackle.reader import io.emeraldpay.dshackle.quorum.BroadcastQuorum import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.springframework.cloud.sleuth.Tracer import reactor.core.publisher.Mono @@ -23,29 +23,29 @@ class BroadcastReaderSpec extends Specification { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.just(new JsonRpcResponse(result, null)) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.just(new ChainResponse(result, null)) } } def up1 = Mock(Upstream) { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.just(new JsonRpcResponse(result, null)) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.just(new ChainResponse(result, null)) } } def up2 = Mock(Upstream) { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.just(new JsonRpcResponse(result, null)) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.just(new ChainResponse(result, null)) } } def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) + def act = reader.read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) then: StepVerifier.create(act) .expectNextMatches { @@ -62,28 +62,28 @@ class BroadcastReaderSpec extends Specification { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.just(new JsonRpcResponse(result, null)) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.just(new ChainResponse(result, null)) } } def up1 = Mock(Upstream) { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.error(new JsonRpcException(1, "too low")) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.error(new ChainException(1, "too low")) } } def up2 = Mock(Upstream) { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.error(new JsonRpcException(1, "too low")) } + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.error(new ChainException(1, "too low")) } } def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) + def act = reader.read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) then: StepVerifier.create(act) .expectNextMatches { @@ -100,8 +100,8 @@ class BroadcastReaderSpec extends Specification { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.just(new JsonRpcResponse(result, null)) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.just(new ChainResponse(result, null)) } } def up1 = Mock(Upstream) { @@ -116,7 +116,7 @@ class BroadcastReaderSpec extends Specification { } def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) + def act = reader.read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) then: StepVerifier.create(act) .expectNextMatches { @@ -132,32 +132,32 @@ class BroadcastReaderSpec extends Specification { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.error(new JsonRpcException(1, "too low")) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.error(new ChainException(1, "too low")) } } def up1 = Mock(Upstream) { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.error(new JsonRpcException(1, "too low")) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.error(new ChainException(1, "too low")) } } def up2 = Mock(Upstream) { 1 * isAvailable() >> true _ * getId() >> "id" 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> - Mono.error(new JsonRpcException(1, "too low")) + 1 * read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) >> + Mono.error(new ChainException(1, "too low")) } } def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer)) when: - def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) + def act = reader.read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) then: StepVerifier.create(act) - .expectError(JsonRpcException.class) + .expectError(ChainException.class) .verify(Duration.ofSeconds(3)) } @@ -181,8 +181,8 @@ class BroadcastReaderSpec extends Specification { def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer)) when: def act = reader - .read(new JsonRpcRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) - .switchIfEmpty(Mono.just(new RpcReader.Result(new byte[0], null, 0, null, null))) + .read(new ChainRequest("eth_sendRawTransaction", new ListParams(["0x1"]))) + .switchIfEmpty(Mono.just(new RequestReader.Result(new byte[0], null, 0, null, null))) then: StepVerifier.create(act) .expectErrorMessage("Unhandled Upstream error") diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 9a562a89..17794b20 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -26,8 +26,8 @@ import io.emeraldpay.dshackle.config.CacheConfig import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.quorum.AlwaysQuorum import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.reader.RpcReader -import io.emeraldpay.dshackle.reader.RpcReaderFactory +import io.emeraldpay.dshackle.reader.RequestReader +import io.emeraldpay.dshackle.reader.RequestReaderFactory import io.emeraldpay.dshackle.startup.UpstreamChangeEvent import io.emeraldpay.dshackle.test.MultistreamHolderMock import io.emeraldpay.dshackle.test.TestingCommons @@ -35,10 +35,6 @@ import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods import io.emeraldpay.dshackle.upstream.generic.GenericUpstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException @@ -76,7 +72,7 @@ class NativeCallSpec extends Specification { def "Tries router first"() { def routedApi = Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_test", new ListParams())) >> Mono.just(new JsonRpcResponse("1".bytes, null)) + 1 * read(new ChainRequest("eth_test", new ListParams())) >> Mono.just(new ChainResponse("1".bytes, null)) } def upstream = Mock(Multistream) { 1 * getLocalReader() >> Mono.just(routedApi) @@ -97,7 +93,7 @@ class NativeCallSpec extends Specification { def "Return error if router denied the requests"() { def routedApi = Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_test", new ListParams())) >> Mono.error(new RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Test message")) + 1 * read(new ChainRequest("eth_test", new ListParams())) >> Mono.error(new RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Test message")) } def upstream = Mock(Multistream) { 1 * getLocalReader() >> Mono.just(routedApi) @@ -129,9 +125,9 @@ class NativeCallSpec extends Specification { } def nativeCall = nativeCall() - nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(_) >> Mono.just(new RpcReader.Result("\"foo\"".bytes, null, 1, ups, null)) + nativeCall.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(_) >> Mono.just(new RequestReader.Result("\"foo\"".bytes, null, 1, ups, null)) } } def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, @@ -150,10 +146,10 @@ class NativeCallSpec extends Specification { def quorum = new AlwaysQuorum() def nativeCall = nativeCall() - nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { + nativeCall.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { 1 * attempts() >> new AtomicInteger(1) - 1 * read(new JsonRpcRequest("eth_test", new ListParams(), 10)) >> Mono.empty() + 1 * read(new ChainRequest("eth_test", new ListParams(), 10)) >> Mono.empty() } } def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, @@ -175,10 +171,10 @@ class NativeCallSpec extends Specification { def quorum = new AlwaysQuorum() def nativeCall = nativeCall() - nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_test", new ListParams(), 10)) >> Mono.error( - new JsonRpcException(JsonRpcResponse.Id.from(12), new JsonRpcError(-32123, "Foo Bar", "Foo Bar Baz"), null, true, null) + nativeCall.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_test", new ListParams(), 10)) >> Mono.error( + new ChainException(ChainResponse.Id.from(12), new ChainCallError(-32123, "Foo Bar", "Foo Bar Baz"), null, true, null) ) } } @@ -338,7 +334,7 @@ class NativeCallSpec extends Specification { id == 1 nonce == 10 payload.method == "eth_test" - payload.params == "[]" + payload.params == new ListParams() } } @@ -354,6 +350,7 @@ class NativeCallSpec extends Specification { .addItems( BlockchainOuterClass.NativeCallItem.newBuilder() .setId(1) + .setPayload(ByteString.copyFromUtf8("[]")) .setMethod("eth_test") ) .build() @@ -365,7 +362,7 @@ class NativeCallSpec extends Specification { with(act[0]) { id == 1 payload.method == "eth_test" - payload.params == "" + payload.params == new ListParams() } } @@ -419,6 +416,7 @@ class NativeCallSpec extends Specification { .addItems( BlockchainOuterClass.NativeCallItem.newBuilder() .setId(1) + .setPayload(ByteString.copyFromUtf8("[]")) .setMethod("foo_bar") ) .build() @@ -457,6 +455,7 @@ class NativeCallSpec extends Specification { .addItems( BlockchainOuterClass.NativeCallItem.newBuilder() .setId(1) + .setPayload(ByteString.copyFromUtf8("[]")) .setMethod("eth_newFilter") ) .build() @@ -487,6 +486,7 @@ class NativeCallSpec extends Specification { .addItems( BlockchainOuterClass.NativeCallItem.newBuilder() .setId(1) + .setPayload(ByteString.copyFromUtf8("[]")) .setMethod("eth_getFilterChanges") ) .build() @@ -517,6 +517,7 @@ class NativeCallSpec extends Specification { .addItems( BlockchainOuterClass.NativeCallItem.newBuilder() .setId(1) + .setPayload(ByteString.copyFromUtf8("[]")) .setMethod("eth_uninstallFilter") ) .build() @@ -532,7 +533,7 @@ class NativeCallSpec extends Specification { setup: def nativeCall = nativeCall() def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), Selector.empty, new AlwaysQuorum(), - new NativeCall.RawCallDetails("eth_test", "[]"), "reqId", 1) + new NativeCall.ParsedCallDetails("eth_test", new ListParams()), "reqId", 1) when: def act = nativeCall.parseParams(ctx) then: @@ -545,7 +546,7 @@ class NativeCallSpec extends Specification { setup: def nativeCall = nativeCall() def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), Selector.empty, new AlwaysQuorum(), - new NativeCall.RawCallDetails("eth_test", ""), "reqId", 1) + new NativeCall.ParsedCallDetails("eth_test", new ListParams()), "reqId", 1) when: def act = nativeCall.parseParams(ctx) then: @@ -558,7 +559,7 @@ class NativeCallSpec extends Specification { setup: def nativeCall = nativeCall() def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), Selector.empty, new AlwaysQuorum(), - new NativeCall.RawCallDetails("eth_test", "[false]"), "reqId", 1) + new NativeCall.ParsedCallDetails("eth_test", new ListParams(false)), "reqId", 1) when: def act = nativeCall.parseParams(ctx) then: @@ -571,7 +572,7 @@ class NativeCallSpec extends Specification { setup: def nativeCall = nativeCall() def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), Selector.empty, new AlwaysQuorum(), - new NativeCall.RawCallDetails("eth_test", "[false, 123]"), "reqId", 1) + new NativeCall.ParsedCallDetails("eth_test", new ListParams(false, 123)), "reqId", 1) when: def act = nativeCall.parseParams(ctx) then: @@ -584,7 +585,7 @@ class NativeCallSpec extends Specification { setup: def nativeCall = nativeCall() def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), Selector.empty, new AlwaysQuorum(), - new NativeCall.RawCallDetails("eth_getFilterUpdates", '["0xabcd"]'), + new NativeCall.ParsedCallDetails("eth_getFilterUpdates", new ListParams("0xabcd")), new NativeCall.WithFilterIdDecorator(), new NativeCall.NoneResultDecorator(), null, false, "reqId", 1) when: def act = nativeCall.parseParams(ctx) @@ -613,9 +614,9 @@ class NativeCallSpec extends Specification { _ * it.observeChains() >> Flux.empty() } def nativeCall = nativeCall(multistreamHolder) - nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups, null)) + nativeCall.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, ups, null)) } } def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum, @@ -649,9 +650,9 @@ class NativeCallSpec extends Specification { _ * it.observeChains() >> Flux.empty() } def nativeCall = nativeCall(multistreamHolder) - nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups, null)) + nativeCall.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, ups, null)) } } def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum, diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/ApiReaderMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/ApiReaderMock.groovy index 880a458b..c0a51d44 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/ApiReaderMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/ApiReaderMock.groovy @@ -21,9 +21,9 @@ import com.google.protobuf.ByteString import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError import io.grpc.stub.StreamObserver import io.netty.buffer.ByteBuf @@ -56,7 +56,7 @@ import java.util.function.BiFunction import java.util.function.Consumer import java.util.function.Predicate -class ApiReaderMock implements Reader { +class ApiReaderMock implements Reader { private static final Logger log = LoggerFactory.getLogger(this) List predefined = [] @@ -79,11 +79,11 @@ class ApiReaderMock implements Reader { } @Override - Mono read(JsonRpcRequest request, boolean required = true) { - Callable call = { + Mono read(ChainRequest request, boolean required = true) { + Callable call = { def predefined = predefined.find { it.isSame(request.method, request.params.list) } byte[] result = null - JsonRpcError error = null + ChainCallError error = null calls.incrementAndGet() if (predefined != null) { if (predefined.exception != null) { @@ -93,7 +93,7 @@ class ApiReaderMock implements Reader { } if (predefined.result instanceof RpcResponseError) { ((RpcResponseError) predefined.result).with { err -> - error = new JsonRpcError(err.code, err.message) + error = new ChainCallError(err.code, err.message) } } else { // ResponseJson json = new ResponseJson(id: 1, result: predefined.result) @@ -106,16 +106,16 @@ class ApiReaderMock implements Reader { if (!required) { return null } - error = new JsonRpcError(-32601, "Method ${request.method} with ${request.params} is not mocked") + error = new ChainCallError(-32601, "Method ${request.method} with ${request.params} is not mocked") } - return new JsonRpcResponse(result, error, JsonRpcResponse.Id.from(request.id), null, null, null) - } as Callable + return new ChainResponse(result, error, ChainResponse.Id.from(request.id), null, null, null) + } as Callable return Mono.fromCallable(call) } def nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver responseObserver) { request.itemsList.forEach { req -> - JsonRpcResponse resp = read(new JsonRpcRequest(req.method, objectMapper.readerFor(List).readValue(req.payload.toByteArray()))) + ChainResponse resp = read(new ChainRequest(req.method, objectMapper.readerFor(List).readValue(req.payload.toByteArray()))) .block(Duration.ofSeconds(5)) def proto = BlockchainOuterClass.NativeCallReplyItem.newBuilder() .setId(req.id) @@ -170,7 +170,7 @@ class ApiReaderMock implements Reader { class WebsocketApi { private final ApiReaderMock api - private Sinks.Many responses = Sinks + private Sinks.Many responses = Sinks .many() .unicast() .onBackpressureBuffer() @@ -202,10 +202,10 @@ class ApiReaderMock implements Reader { class WebsocketInboundMock implements WebsocketInbound { - private final Flux responses + private final Flux responses private final Flux jsonResponses - WebsocketInboundMock(Flux responses, Flux jsonResponses) { + WebsocketInboundMock(Flux responses, Flux jsonResponses) { this.responses = responses this.jsonResponses = jsonResponses } @@ -260,9 +260,9 @@ class ApiReaderMock implements Reader { class WebsocketOutboundMock implements WebsocketOutbound { private final ApiReaderMock api - private final Sinks.Many responses + private final Sinks.Many responses - WebsocketOutboundMock(ApiReaderMock api, Sinks.Many responses) { + WebsocketOutboundMock(ApiReaderMock api, Sinks.Many responses) { this.api = api this.responses = responses } @@ -280,9 +280,9 @@ class ApiReaderMock implements Reader { private void handle(Publisher dataStream) { Flux.from(dataStream) .map { it -> - Global.objectMapper.readValue(new ByteBufInputStream(it), JsonRpcRequest) + Global.objectMapper.readValue(new ByteBufInputStream(it), ChainRequest) } - .flatMap { JsonRpcRequest request -> + .flatMap { ChainRequest request -> api.read(request, false) } .doOnNext { diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy index 598f1eb5..1c0bd5c1 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/ConnectorFactoryMock.groovy @@ -4,17 +4,16 @@ import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.Head -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator import io.emeraldpay.dshackle.upstream.generic.connectors.ConnectorFactory import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse class ConnectorFactoryMock implements ConnectorFactory { - Reader api + Reader api Head head - ConnectorFactoryMock(Reader api, Head head) { + ConnectorFactoryMock(Reader api, Head head) { this.api = api this.head = head } diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiStub.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiStub.groovy index ea6b9b0a..04e54795 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiStub.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiStub.groovy @@ -17,11 +17,11 @@ package io.emeraldpay.dshackle.test import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import reactor.core.publisher.Mono -class EthereumApiStub implements Reader { +class EthereumApiStub implements Reader { private String id @@ -39,7 +39,7 @@ class EthereumApiStub implements Reader { } @Override - Mono read(JsonRpcRequest key) { + Mono read(ChainRequest key) { return Mono.error(new Exception("Not implemented in mock")) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/GenericConnectorMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/GenericConnectorMock.groovy index 281aae18..bcfb7419 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/GenericConnectorMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/GenericConnectorMock.groovy @@ -5,16 +5,16 @@ import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.IngressSubscription import io.emeraldpay.dshackle.upstream.ethereum.NoEthereumIngressSubscription import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import reactor.core.publisher.Flux class GenericConnectorMock implements GenericConnector { - Reader api + Reader api Head head Flux liveness - GenericConnectorMock(Reader api, Head head) { + GenericConnectorMock(Reader api, Head head) { this.api = api this.head = head this.liveness = Flux.just(false) @@ -26,7 +26,7 @@ class GenericConnectorMock implements GenericConnector { } @Override - Reader getIngressReader() { + Reader getIngressReader() { return this.api } diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy index 40cc0b05..201d058e 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/GenericUpstreamMock.groovy @@ -27,8 +27,8 @@ import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.calls.* import io.emeraldpay.dshackle.upstream.generic.GenericUpstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import org.jetbrains.annotations.NotNull import org.reactivestreams.Publisher @@ -43,27 +43,27 @@ class GenericUpstreamMock extends GenericUpstream { ]) } - GenericUpstreamMock(@NotNull Chain chain, @NotNull Reader api) { + GenericUpstreamMock(@NotNull Chain chain, @NotNull Reader api) { this(chain, api, allMethods()) } - GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, Map labels) { + GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, Map labels) { this(id, chain, api, allMethods(), labels) } - GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api) { + GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api) { this(id, chain, api, allMethods()) } - GenericUpstreamMock(@NotNull Chain chain, @NotNull Reader api, CallMethods methods) { + GenericUpstreamMock(@NotNull Chain chain, @NotNull Reader api, CallMethods methods) { this("test", chain, api, methods) } - GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods) { + GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods) { this(id, chain, api, methods, Collections.emptyMap()) } - GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods, Map labels) { + GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods, Map labels) { super(id, chain, (byte)id.hashCode(), diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy index 6f39c847..cb50fa55 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy @@ -33,8 +33,8 @@ import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson import io.emeraldpay.dshackle.upstream.generic.GenericMultistream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionRefJson @@ -68,27 +68,27 @@ class TestingCommons { return new GenericUpstreamMock(id, Chain.ETHEREUM__MAINNET, api(), Collections.singletonMap("provider", provider)) } - static GenericUpstreamMock upstream(String id, Reader api) { + static GenericUpstreamMock upstream(String id, Reader api) { return new GenericUpstreamMock(id, Chain.ETHEREUM__MAINNET, api) } - static GenericUpstreamMock upstream(Reader api) { + static GenericUpstreamMock upstream(Reader api) { return new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, api) } - static GenericUpstreamMock upstream(Reader api, String method) { + static GenericUpstreamMock upstream(Reader api, String method) { return upstream(api, [method]) } - static GenericUpstreamMock upstream(Reader api, List methods) { + static GenericUpstreamMock upstream(Reader api, List methods) { return new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, api, new DirectCallMethods(methods)) } - static GenericUpstreamMock upstream(Reader api, CallMethods callMethods) { + static GenericUpstreamMock upstream(Reader api, CallMethods callMethods) { return new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, api, callMethods) } - static Multistream multistream(Reader api) { + static Multistream multistream(Reader api) { return multistream(upstream(api)) } @@ -109,7 +109,7 @@ class TestingCommons { } static Multistream emptyMultistream() { - return multistream(new EmptyReader()) + return multistream(new EmptyReader()) } static CachesFactory emptyCaches() { diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy index f3d0dbb2..5fa2d995 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy @@ -19,7 +19,6 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.config.ChainsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.reader.JsonRpcHttpReader import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.test.EthereumApiStub import io.emeraldpay.dshackle.test.TestingCommons @@ -29,7 +28,6 @@ import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactor import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import reactor.core.scheduler.Schedulers import reactor.test.StepVerifier -import spock.lang.Retry import io.emeraldpay.dshackle.foundation.ChainOptions import spock.lang.Specification @@ -54,7 +52,7 @@ class FilteredApisSpec extends Specification { ].collect { def httpFactory = Mock(HttpFactory) { - create(_, _) >> Stub(JsonRpcHttpReader) + create(_, _) >> Stub(HttpReader) } def connectorFactory = new GenericConnectorFactory( GenericConnectorFactory.ConnectorMode.RPC_ONLY, diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy index cdd3ea56..63bb374f 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/MultistreamSpec.groovy @@ -33,8 +33,6 @@ import io.emeraldpay.dshackle.upstream.generic.GenericUpstream import io.emeraldpay.dshackle.upstream.generic.GenericMultistream import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson import io.emeraldpay.dshackle.upstream.grpc.GenericGrpcUpstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionRefJson @@ -376,7 +374,7 @@ class MultistreamSpec extends Specification { @NotNull @Override - Mono> getLocalReader() { + Mono> getLocalReader() { return null } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHeadSpec.groovy index 87be360e..f1f125a9 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHeadSpec.groovy @@ -16,8 +16,8 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.publisher.Mono import reactor.core.scheduler.Schedulers @@ -79,13 +79,13 @@ class BitcoinRpcHeadSpec extends Specification { """ def api = Mock(Reader) { - _ * read(new JsonRpcRequest("getbestblockhash", new ListParams())) >>> [ - Mono.just(new JsonRpcResponse("\"$hash1\"".bytes, null)), - Mono.just(new JsonRpcResponse("\"$hash1\"".bytes, null)), - Mono.just(new JsonRpcResponse("\"$hash2\"".bytes, null)) + _ * read(new ChainRequest("getbestblockhash", new ListParams())) >>> [ + Mono.just(new ChainResponse("\"$hash1\"".bytes, null)), + Mono.just(new ChainResponse("\"$hash1\"".bytes, null)), + Mono.just(new ChainResponse("\"$hash2\"".bytes, null)) ] - _ * read(new JsonRpcRequest("getblock", new ListParams([hash1]))) >> Mono.just(new JsonRpcResponse(block1.bytes, null)) - _ * read(new JsonRpcRequest("getblock", new ListParams([hash2]))) >> Mono.just(new JsonRpcResponse(block2.bytes, null)) + _ * read(new ChainRequest("getblock", new ListParams([hash1]))) >> Mono.just(new ChainResponse(block1.bytes, null)) + _ * read(new ChainRequest("getblock", new ListParams([hash2]))) >> Mono.just(new ChainResponse(block2.bytes, null)) } BitcoinRpcHead head = new BitcoinRpcHead(api, new ExtractBlock(), Duration.ofMillis(200), Schedulers.boundedElastic()) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReaderSpec.groovy index 25c48b08..578ad832 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/RpcUnspentReaderSpec.groovy @@ -16,8 +16,8 @@ package io.emeraldpay.dshackle.upstream.bitcoin import io.emeraldpay.dshackle.reader.Reader -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.bitcoinj.core.Address import org.bitcoinj.params.MainNetParams @@ -30,7 +30,7 @@ class RpcUnspentReaderSpec extends Specification { setup: def json = this.class.getClassLoader().getResourceAsStream("bitcoin/unspent-one-addr.json").bytes def rpcReader = Mock(Reader) { - 1 * read(new JsonRpcRequest("listunspent", new ListParams([1, 9999999, ["1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"]]))) >> Mono.just(JsonRpcResponse.ok(json)) + 1 * read(new ChainRequest("listunspent", new ListParams([1, 9999999, ["1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"]]))) >> Mono.just(ChainResponse.ok(json)) } def upstreams = Mock(BitcoinMultistream) { 1 * getDirectApi(_) >> Mono.just(rpcReader) @@ -64,7 +64,7 @@ class RpcUnspentReaderSpec extends Specification { setup: def json = this.class.getClassLoader().getResourceAsStream("bitcoin/unspent-two-addr.json").bytes def rpcReader = Mock(Reader) { - 1 * read(new JsonRpcRequest("listunspent", new ListParams([1, 9999999, ["35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP"]]))) >> Mono.just(JsonRpcResponse.ok(json)) + 1 * read(new ChainRequest("listunspent", new ListParams([1, 9999999, ["35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP"]]))) >> Mono.just(ChainResponse.ok(json)) } def upstreams = Mock(BitcoinMultistream) { 1 * getDirectApi(_) >> Mono.just(rpcReader) @@ -99,7 +99,7 @@ class RpcUnspentReaderSpec extends Specification { setup: def json = this.class.getClassLoader().getResourceAsStream("bitcoin/unspent-two-addr.json").bytes def rpcReader = Mock(Reader) { - 1 * read(_) >> Mono.just(JsonRpcResponse.ok(json)) + 1 * read(_) >> Mono.just(ChainResponse.ok(json)) } def upstreams = Mock(BitcoinMultistream) { 1 * getDirectApi(_) >> Mono.just(rpcReader) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy index 501135f0..da76fbe0 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumCachingReaderSpec.groovy @@ -6,13 +6,12 @@ import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.CurrentBlockCache import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.DefaultContainer -import io.emeraldpay.dshackle.reader.RpcReader -import io.emeraldpay.dshackle.reader.RpcReaderFactory +import io.emeraldpay.dshackle.reader.RequestReader +import io.emeraldpay.dshackle.reader.RequestReaderFactory import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.ethereum.domain.Address import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash @@ -51,10 +50,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null) ) } @@ -78,10 +77,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(null), null, 1, resolver, null ) ) @@ -111,10 +110,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x64", false]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x64", false]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null ) ) @@ -143,10 +142,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getLogs", new ListParams([Map.of("blockHash", hash1)]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getLogs", new ListParams([Map.of("blockHash", hash1)]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes([json]), null, 1, resolver, null ) ) @@ -176,10 +175,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getTransactionByHash", new ListParams([hash1]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getTransactionByHash", new ListParams([hash1]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null ) ) @@ -209,10 +208,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getTransactionReceipt", new ListParams([hash1]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getTransactionReceipt", new ListParams([hash1]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null ) ) @@ -243,10 +242,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), caches, new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getTransactionReceipt", new ListParams([hash1]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getTransactionReceipt", new ListParams([hash1]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null ) ) @@ -268,10 +267,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getTransactionByHash", new ListParams([hash1]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getTransactionByHash", new ListParams([hash1]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(null), null, 1, resolver, null ) ) @@ -298,10 +297,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getBalance", new ListParams([address1, "latest"]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getBalance", new ListParams([address1, "latest"]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver, null ) ) @@ -329,10 +328,10 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getBalance", new ListParams([address1, "0xa8c9bb"]))) >> Mono.just( - new RpcReader.Result( + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getBalance", new ListParams([address1, "0xa8c9bb"]))) >> Mono.just( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver, null ) ) @@ -361,19 +360,19 @@ class EthereumDirectReaderSpec extends Specification { 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } def result = Mono.just( - new RpcReader.Result( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null) ) EthereumDirectReader ethereumDirectReader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - ethereumDirectReader.rpcReaderFactory = Mock(RpcReaderFactory) { - 2 * create(_) >> Mock(RpcReader) { - 2 * read(new JsonRpcRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >>> + ethereumDirectReader.requestReaderFactory = Mock(RequestReaderFactory) { + 2 * create(_) >> Mock(RequestReader) { + 2 * read(new ChainRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >>> [Mono.error(new RuntimeException()), Mono.error(new RuntimeException())] } - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> result + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> result } } when: @@ -401,19 +400,19 @@ class EthereumDirectReaderSpec extends Specification { 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false) } def result = Mono.just( - new RpcReader.Result( + new RequestReader.Result( Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null) ) EthereumDirectReader ethereumDirectReader = new EthereumDirectReader( Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - ethereumDirectReader.rpcReaderFactory = Mock(RpcReaderFactory) { - 2 * create(_) >> Mock(RpcReader) { - 2 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x64", false]))) >>> + ethereumDirectReader.requestReaderFactory = Mock(RequestReaderFactory) { + 2 * create(_) >> Mock(RequestReader) { + 2 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x64", false]))) >>> [Mono.error(new RuntimeException()), Mono.error(new RuntimeException())] } - 1 * create(_) >> Mock(RpcReader) { - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x64", false]))) >> result + 1 * create(_) >> Mock(RequestReader) { + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x64", false]))) >> result } } when: @@ -440,9 +439,9 @@ class EthereumDirectReaderSpec extends Specification { EthereumDirectReader reader = new EthereumDirectReader( up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() ) - reader.rpcReaderFactory = Mock(RpcReaderFactory) { - 4 * create(_) >> Mock(RpcReader) { - 4 * read(new JsonRpcRequest("eth_getBalance", new ListParams([address1, "latest"]))) >>> + reader.requestReaderFactory = Mock(RequestReaderFactory) { + 4 * create(_) >> Mock(RequestReader) { + 4 * read(new ChainRequest("eth_getBalance", new ListParams([address1, "latest"]))) >>> [Mono.error(new RuntimeException()), Mono.error(new RuntimeException()), Mono.error(new RuntimeException()), Mono.error(new RuntimeException())] } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLabelsDetectorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLabelsDetectorSpec.groovy index b5d2f12b..d8bb5ced 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLabelsDetectorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLabelsDetectorSpec.groovy @@ -6,8 +6,8 @@ import io.emeraldpay.dshackle.test.ApiReaderMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.ethereum.subscribe.EthereumLabelsDetector -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import kotlin.Pair import reactor.core.publisher.Mono @@ -53,14 +53,14 @@ class EthereumLabelsDetectorSpec extends Specification { setup: def up = Mock(DefaultUpstream) { 1 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("web3_clientVersion", new ListParams())) >> - Mono.just(new JsonRpcResponse('no/v1.19.3+e8ac1da4/linux-x64/dotnet7.0.8'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> - Mono.just(new JsonRpcResponse("\"0x10df3e5\"".getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBalance", new ListParams(["0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", "0x10dccd5"]))) >> + 1 * read(new ChainRequest("web3_clientVersion", new ListParams())) >> + Mono.just(new ChainResponse('no/v1.19.3+e8ac1da4/linux-x64/dotnet7.0.8'.getBytes(), null)) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> + Mono.just(new ChainResponse("\"0x10df3e5\"".getBytes(), null)) + 1 * read(new ChainRequest("eth_getBalance", new ListParams(["0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", "0x10dccd5"]))) >> Mono.error(new RuntimeException()) - 1 * read(new JsonRpcRequest("eth_getBalance", new ListParams(["0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", "0x2710"]))) >> - Mono.just(new JsonRpcResponse("".getBytes(), null)) + 1 * read(new ChainRequest("eth_getBalance", new ListParams(["0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", "0x2710"]))) >> + Mono.just(new ChainResponse("".getBytes(), null)) } } def detector = new EthereumLabelsDetector(up.getIngressReader(), Chain.ETHEREUM__MAINNET) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy index 8c04c378..239a5cb8 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumLocalReaderSpec.groovy @@ -9,7 +9,7 @@ import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.EmptyHead import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson import org.apache.commons.collections4.functors.ConstantFactory @@ -35,7 +35,7 @@ class EthereumLocalReaderSpec extends Specification { null ) when: - def act = router.read(new JsonRpcRequest("eth_coinbase", new ListParams())).block(Duration.ofSeconds(1)) + def act = router.read(new ChainRequest("eth_coinbase", new ListParams())).block(Duration.ofSeconds(1)) then: act.resultAsProcessedString == "0x0000000000000000000000000000000000000000" } @@ -55,7 +55,7 @@ class EthereumLocalReaderSpec extends Specification { null ) when: - def act = router.read(new JsonRpcRequest("eth_getTransactionByHash", new ListParams(["test"]), 10)) + def act = router.read(new ChainRequest("eth_getTransactionByHash", new ListParams(["test"]), 10)) .block(Duration.ofSeconds(1)) then: act == null diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy index 28f3db1d..56ac7df1 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy @@ -21,9 +21,9 @@ import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.test.ApiReaderMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.Head -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.ethereum.domain.Address @@ -108,8 +108,8 @@ class EthereumUpstreamValidatorSpec extends Specification { def up = Mock(Upstream) { 2 * getIngressReader() >> Mock(Reader) { reader -> 2 * reader.read(_) >>> [ - Mono.just(new JsonRpcResponse('true'.getBytes(), null)), - Mono.just(new JsonRpcResponse('false'.getBytes(), null)) + Mono.just(new ChainResponse('true'.getBytes(), null)), + Mono.just(new ChainResponse('false'.getBytes(), null)) ] } 2 * getHead() >> Mock(Head) {head -> @@ -280,9 +280,9 @@ class EthereumUpstreamValidatorSpec extends Specification { def up = Mock(Upstream) { 2 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> - Mono.just(new JsonRpcResponse('"result"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> + Mono.just(new ChainResponse('"result"'.getBytes(), null)) } } def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf) @@ -300,13 +300,13 @@ class EthereumUpstreamValidatorSpec extends Specification { }.buildOptions() def up = Mock(Upstream) { 3 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_call", new ListParams([new TransactionCallJson( + 1 * read(new ChainRequest("eth_call", new ListParams([new TransactionCallJson( Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"), HexData.from("0xd8a26e3a00000000000000000000000000000000000000000000000000000000000f4240") - ), "latest"]))) >> Mono.just(new JsonRpcResponse("0x00000000000000000000".getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> - Mono.just(new JsonRpcResponse('"result"'.getBytes(), null)) + ), "latest"]))) >> Mono.just(new ChainResponse("0x00000000000000000000".getBytes(), null)) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> + Mono.just(new ChainResponse('"result"'.getBytes(), null)) } } def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf) @@ -324,13 +324,13 @@ class EthereumUpstreamValidatorSpec extends Specification { }.buildOptions() def up = Mock(Upstream) { 3 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_call", new ListParams([new TransactionCallJson( + 1 * read(new ChainRequest("eth_call", new ListParams([new TransactionCallJson( Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"), HexData.from("0xd8a26e3a00000000000000000000000000000000000000000000000000000000000f4240") - ), "latest"]))) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long"))) - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> - Mono.just(new JsonRpcResponse('"result"'.getBytes(), null)) + ), "latest"]))) >> Mono.just(new ChainResponse(null, new ChainCallError(1, "Too long"))) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> + Mono.just(new ChainResponse('"result"'.getBytes(), null)) } } def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf) @@ -348,11 +348,11 @@ class EthereumUpstreamValidatorSpec extends Specification { }.buildOptions() def up = Mock(Upstream) { 4 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_chainId", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("net_version", new ListParams())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> - Mono.just(new JsonRpcResponse('"result"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse('"0x1"'.getBytes(), null)) + 1 * read(new ChainRequest("net_version", new ListParams())) >> Mono.just(new ChainResponse('"1"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> + Mono.just(new ChainResponse('"result"'.getBytes(), null)) } } def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf) @@ -370,11 +370,11 @@ class EthereumUpstreamValidatorSpec extends Specification { }.buildOptions() def up = Mock(Upstream) { 4 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_chainId", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("net_version", new ListParams())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> - Mono.just(new JsonRpcResponse('"result"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse('"0x1"'.getBytes(), null)) + 1 * read(new ChainRequest("net_version", new ListParams())) >> Mono.just(new ChainResponse('"1"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> + Mono.just(new ChainResponse('"result"'.getBytes(), null)) } } def validator = new EthereumUpstreamValidator(OPTIMISM__MAINNET, up, options, conf) @@ -390,15 +390,15 @@ class EthereumUpstreamValidatorSpec extends Specification { def options = ChainOptions.PartialOptions.getDefaults().buildOptions() def up = Mock(Upstream) { 5 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_chainId", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("net_version", new ListParams())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_call", new ListParams([new TransactionCallJson( + 1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse('"0x1"'.getBytes(), null)) + 1 * read(new ChainRequest("net_version", new ListParams())) >> Mono.just(new ChainResponse('"1"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_call", new ListParams([new TransactionCallJson( Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"), HexData.from("0xd8a26e3a00000000000000000000000000000000000000000000000000000000000f4240") - ), "latest"]))) >> Mono.just(new JsonRpcResponse("0x00000000000000000000".getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> - Mono.just(new JsonRpcResponse('"result"'.getBytes(), null)) + ), "latest"]))) >> Mono.just(new ChainResponse("0x00000000000000000000".getBytes(), null)) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> + Mono.just(new ChainResponse('"result"'.getBytes(), null)) } } def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf) @@ -414,15 +414,15 @@ class EthereumUpstreamValidatorSpec extends Specification { def options = ChainOptions.PartialOptions.getDefaults().buildOptions() def up = Mock(Upstream) { 5 * getIngressReader() >> Mock(Reader) { - 1 * read(new JsonRpcRequest("eth_chainId", new ListParams())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long"))) - 1 * read(new JsonRpcRequest("net_version", new ListParams())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long"))) - 1 * read(new JsonRpcRequest("eth_call", new ListParams([new TransactionCallJson( + 1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse(null, new ChainCallError(1, "Too long"))) + 1 * read(new ChainRequest("net_version", new ListParams())) >> Mono.just(new ChainResponse(null, new ChainCallError(1, "Too long"))) + 1 * read(new ChainRequest("eth_call", new ListParams([new TransactionCallJson( Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"), HexData.from("0xd8a26e3a00000000000000000000000000000000000000000000000000000000000f4240") - ), "latest"]))) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long"))) - 1 * read(new JsonRpcRequest("eth_blockNumber", new ListParams())) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null)) - 1 * read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> - Mono.just(new JsonRpcResponse('"result"'.getBytes(), null)) + ), "latest"]))) >> Mono.just(new ChainResponse(null, new ChainCallError(1, "Too long"))) + 1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null)) + 1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >> + Mono.just(new ChainResponse('"result"'.getBytes(), null)) } } def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHeadSpec.groovy index 432778e2..60c6c9d7 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHeadSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/GenericWsHeadSpec.groovy @@ -25,8 +25,8 @@ import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson import io.emeraldpay.dshackle.upstream.forkchoice.AlwaysForkChoice -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionRefJson @@ -62,7 +62,7 @@ class GenericWsHeadSpec extends Specification { } def reader = Mock(Reader) { - 1 * it.read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams("latest", false))) >> Mono.empty() + 1 * it.read(new ChainRequest("eth_getBlockByNumber", new ListParams("latest", false))) >> Mono.empty() } def ws = Mock(WsSubscriptions) { @@ -339,7 +339,7 @@ class GenericWsHeadSpec extends Specification { block.totalDifficulty = BigInteger.ONE def reader = Mock(Reader) { - 1 * it.read(new JsonRpcRequest("eth_getBlockByNumber", new ListParams("latest", false))) >> Mono.empty() + 1 * it.read(new ChainRequest("eth_getBlockByNumber", new ListParams("latest", false))) >> Mono.empty() } def subId = "subId" def ws = Mock(WsSubscriptions) { @@ -347,8 +347,8 @@ class GenericWsHeadSpec extends Specification { 1 * it.subscribe(_) >> new WsSubscriptions.SubscribeData( Flux.error(new RuntimeException()), "id", new AtomicReference(subId) ) - 1 * it.unsubscribe(new JsonRpcRequest("eth_unsubscribe", new ListParams(subId), 2, null, null, false)) >> - Mono.just(new JsonRpcResponse("".bytes, null)) + 1 * it.unsubscribe(new ChainRequest("eth_unsubscribe", new ListParams(subId), 2, null, null, false)) >> + Mono.just(new ChainResponse("".bytes, null)) } def head = new GenericWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, reader, ws, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream, EthereumChainSpecific.INSTANCE) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplRealSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplRealSpec.groovy index 3249e6e4..969b65fc 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplRealSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplRealSpec.groovy @@ -5,7 +5,7 @@ import io.emeraldpay.dshackle.test.GenericUpstreamMock import io.emeraldpay.dshackle.test.MockWSServer import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.DefaultUpstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.scheduler.Schedulers import reactor.test.StepVerifier @@ -56,7 +56,7 @@ class WsConnectionImplRealSpec extends Specification { def "Can make a RPC request"() { when: conn.connect() - def resp = conn.callRpc(new JsonRpcRequest("foo_bar", new ListParams())) + def resp = conn.callRpc(new ChainRequest("foo_bar", new ListParams())) then: StepVerifier.create(resp) .then { @@ -88,7 +88,7 @@ class WsConnectionImplRealSpec extends Specification { server.onNextReply('{"jsonrpc":"2.0","id":100,"result":1}') // reconnects in 2 seconds, give 1 extra Thread.sleep(3_000) - def resp = conn.callRpc(new JsonRpcRequest("foo_bar", new ListParams())).block(Duration.ofSeconds(1)) + def resp = conn.callRpc(new ChainRequest("foo_bar", new ListParams())).block(Duration.ofSeconds(1)) def act = server.received then: @@ -101,7 +101,7 @@ class WsConnectionImplRealSpec extends Specification { conn.connect() conn.reconnectIntervalSeconds = 2 - def resp = conn.callRpc(new JsonRpcRequest("foo_bar", new ListParams())) + def resp = conn.callRpc(new ChainRequest("foo_bar", new ListParams())) then: StepVerifier.create(resp) @@ -122,7 +122,7 @@ class WsConnectionImplRealSpec extends Specification { server.onNextReply('{"jsonrpc":"2.0","id":100,"result":1}') Thread.sleep(3_000) - def resp = conn.callRpc(new JsonRpcRequest("foo_bar", new ListParams())).block(Duration.ofSeconds(1)) + def resp = conn.callRpc(new ChainRequest("foo_bar", new ListParams())).block(Duration.ofSeconds(1)) def act = server.received then: act.size() == 1 @@ -141,7 +141,7 @@ class WsConnectionImplRealSpec extends Specification { // reconnects in 2 seconds, give 1 extra Thread.sleep(3_000) - def resp = conn.callRpc(new JsonRpcRequest("foo_bar", new ListParams())) + def resp = conn.callRpc(new ChainRequest("foo_bar", new ListParams())) then: StepVerifier.create(resp) .then { diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplSpec.groovy index e4d799db..0bb9acc5 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsConnectionImplSpec.groovy @@ -20,7 +20,7 @@ import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.test.GenericUpstreamMock import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.DefaultUpstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError @@ -59,7 +59,7 @@ class WsConnectionImplSpec extends Specification { when: Flux.from(ws.handle(wsApiMock.inbound, wsApiMock.outbound)).subscribe() - def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", new ListParams(["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"]), 15, null, null, false)) + def act = ws.callRpc(new ChainRequest("eth_getTransactionByHash", new ListParams(["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"]), 15, null, null, false)) then: StepVerifier.create(act) @@ -91,7 +91,7 @@ class WsConnectionImplSpec extends Specification { when: Flux.from(ws.handle(wsApiMock.inbound, wsApiMock.outbound)).subscribe() - def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", new ListParams(["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"]), 15, null, null, false)) + def act = ws.callRpc(new ChainRequest("eth_getTransactionByHash", new ListParams(["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"]), 15, null, null, false)) then: StepVerifier.create(act) @@ -125,7 +125,7 @@ class WsConnectionImplSpec extends Specification { when: Flux.from(ws.handle(wsApiMock.inbound, wsApiMock.outbound)).subscribe() - def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", new ListParams(["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"]), 15, null, null, false)) + def act = ws.callRpc(new ChainRequest("eth_getTransactionByHash", new ListParams(["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"]), 15, null, null, false)) then: StepVerifier.create(act) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImplSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImplSpec.groovy index e1011682..f7de40f2 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImplSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/WsSubscriptionsImplSpec.groovy @@ -15,8 +15,8 @@ */ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcWsMessage import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.publisher.Flux @@ -46,7 +46,7 @@ class WsSubscriptionsImplSpec extends Specification { def ws = new WsSubscriptionsImpl(pool) when: - def act = ws.subscribe(new JsonRpcRequest("eth_subscribe", new ListParams(["foo_bar"]))) + def act = ws.subscribe(new ChainRequest("eth_subscribe", new ListParams(["foo_bar"]))) .data .map { new String(it) } .take(3) @@ -55,9 +55,9 @@ class WsSubscriptionsImplSpec extends Specification { then: act == ["100", "101", "102"] - 1 * conn.callRpc({ JsonRpcRequest req -> + 1 * conn.callRpc({ ChainRequest req -> req.method == "eth_subscribe" && req.params == new ListParams(["foo_bar"]) - }) >> Mono.just(new JsonRpcResponse('"0xcff45d00e7"'.bytes, null)) + }) >> Mono.just(new ChainResponse('"0xcff45d00e7"'.bytes, null)) 1 * conn.getSubscribeResponses() >> answers } @@ -84,7 +84,7 @@ class WsSubscriptionsImplSpec extends Specification { def ws = new WsSubscriptionsImpl(pool) when: - def act = ws.subscribe(new JsonRpcRequest("eth_subscribe", new ListParams(["foo_bar"]))) + def act = ws.subscribe(new ChainRequest("eth_subscribe", new ListParams(["foo_bar"]))) .data .map { new String(it) } .take(3) @@ -93,9 +93,9 @@ class WsSubscriptionsImplSpec extends Specification { then: act == ["100", "101", "102"] - 1 * conn.callRpc({ JsonRpcRequest req -> + 1 * conn.callRpc({ ChainRequest req -> req.method == "eth_subscribe" && req.params == new ListParams(["foo_bar"]) - }) >> Mono.just(new JsonRpcResponse('"0xcff45d00e7"'.bytes, null)) + }) >> Mono.just(new ChainResponse('"0xcff45d00e7"'.bytes, null)) 1 * conn.getSubscribeResponses() >> answers } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxesSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxesSpec.groovy index 36b2b595..7f079c26 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxesSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/subscribe/WebsocketPendingTxesSpec.groovy @@ -15,7 +15,7 @@ */ package io.emeraldpay.dshackle.upstream.ethereum.subscribe -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions @@ -43,7 +43,7 @@ class WebsocketPendingTxesSpec extends Specification { .collectList().block(Duration.ofSeconds(1)) then: - 1 * ws.subscribe(new JsonRpcRequest("eth_subscribe", new ListParams(["newPendingTransactions"]))) >> new WsSubscriptions.SubscribeData( + 1 * ws.subscribe(new ChainRequest("eth_subscribe", new ListParams(["newPendingTransactions"]))) >> new WsSubscriptions.SubscribeData( Flux.fromIterable(responses), "id", new AtomicReference("") ) txes.collect {it.toHex() } == [ diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcErrorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcErrorSpec.groovy index db3d116f..1c2a7deb 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcErrorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcErrorSpec.groovy @@ -1,5 +1,6 @@ package io.emeraldpay.dshackle.upstream.rpcclient +import io.emeraldpay.dshackle.upstream.ChainCallError import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import nl.jqno.equalsverifier.EqualsVerifier import spock.lang.Specification @@ -8,7 +9,7 @@ class JsonRpcErrorSpec extends Specification { def "Build from RpcException"() { when: - def act = JsonRpcError.from(new RpcException(-32123, "test test")) + def act = ChainCallError.from(new RpcException(-32123, "test test")) then: act.code == -32123 act.message == "test test" @@ -17,7 +18,7 @@ class JsonRpcErrorSpec extends Specification { def "Build from RpcException with details"() { when: - def act = JsonRpcError.from(new RpcException(-32123, "test test", "foo bar")) + def act = ChainCallError.from(new RpcException(-32123, "test test", "foo bar")) then: act.code == -32123 act.message == "test test" @@ -26,7 +27,7 @@ class JsonRpcErrorSpec extends Specification { def "Equals"() { when: - def v = EqualsVerifier.forClass(JsonRpcError) + def v = EqualsVerifier.forClass(ChainCallError) then: v.verify() } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClientSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClientSpec.groovy index 7fb47bd5..7110ab3d 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClientSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcGrpcClientSpec.groovy @@ -6,9 +6,9 @@ import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.test.MockGrpcServer +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.grpc.stub.StreamObserver import spock.lang.Specification @@ -40,7 +40,7 @@ class JsonRpcGrpcClientSpec extends Specification { when: def act = client.read( - new JsonRpcRequest("test", new ListParams()) + new ChainRequest("test", new ListParams()) ).block(Duration.ofSeconds(1)) then: @@ -74,7 +74,7 @@ class JsonRpcGrpcClientSpec extends Specification { when: client.read( - new JsonRpcRequest("test", new ListParams()) + new ChainRequest("test", new ListParams()) ).block(Duration.ofSeconds(1)) then: diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpClientSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpReaderSpec.groovy similarity index 80% rename from src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpClientSpec.groovy rename to src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpReaderSpec.groovy index 0ddd5d8a..c93476bc 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpClientSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcHttpReaderSpec.groovy @@ -17,8 +17,10 @@ package io.emeraldpay.dshackle.upstream.rpcclient import io.emeraldpay.dshackle.test.TestingCommons +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.RequestMetrics import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcResponseError -import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.Timer import org.mockserver.integration.ClientAndServer @@ -29,11 +31,11 @@ import spock.lang.Specification import java.time.Duration -class JsonRpcHttpClientSpec extends Specification { +class JsonRpcHttpReaderSpec extends Specification { ClientAndServer mockServer int port = 19332 - RpcMetrics metrics = new RpcMetrics( + RequestMetrics metrics = new RequestMetrics( Timer.builder("test1").register(TestingCommons.meterRegistry), Counter.builder("test2").register(TestingCommons.meterRegistry) ) @@ -49,7 +51,7 @@ class JsonRpcHttpClientSpec extends Specification { def "Make a request"() { setup: - JsonRpcHttpClient client = new JsonRpcHttpClient("localhost:${port}", metrics,null, null) + JsonRpcHttpReader client = new JsonRpcHttpReader("localhost:${port}", metrics,null, null) def resp = '{' + ' "jsonrpc": "2.0",' + ' "result": "0x98de45",' + @@ -62,7 +64,7 @@ class JsonRpcHttpClientSpec extends Specification { HttpResponse.response(resp) ) when: - def act = client.read(new JsonRpcRequest("test", new ListParams())).block() + def act = client.read(new ChainRequest("test", new ListParams())).block() then: act.error == null new String(act.result) == '"0x98de45"' @@ -70,7 +72,7 @@ class JsonRpcHttpClientSpec extends Specification { def "Produces RPC Exception on error status code"() { setup: - def client = new JsonRpcHttpClient("localhost:${port}", metrics, null, null) + def client = new JsonRpcHttpReader("localhost:${port}", metrics, null, null) mockServer.when( HttpRequest.request() @@ -81,12 +83,12 @@ class JsonRpcHttpClientSpec extends Specification { ) when: def act = client.read( - new JsonRpcRequest("ping", new ListParams()) + new ChainRequest("ping", new ListParams()) ).block(Duration.ofSeconds(1)) then: def t = thrown(RuntimeException) // reactor.core.Exceptions$ReactiveException - t.cause instanceof JsonRpcException - with(((JsonRpcException)t.cause).error) { + t.cause instanceof ChainException + with(((ChainException)t.cause).error) { code == RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE message == "HTTP Code: 500" } @@ -94,7 +96,7 @@ class JsonRpcHttpClientSpec extends Specification { def "Tries to extract message if HTTP error if it still contains a JSON RPC message"() { setup: - def client = new JsonRpcHttpClient("localhost:${port}", metrics, null, null) + def client = new JsonRpcHttpReader("localhost:${port}", metrics, null, null) mockServer.when( HttpRequest.request() @@ -109,12 +111,12 @@ class JsonRpcHttpClientSpec extends Specification { ) when: def act = client.read( - new JsonRpcRequest("ping", new ListParams()) + new ChainRequest("ping", new ListParams()) ).block(Duration.ofSeconds(1)) then: def t = thrown(RuntimeException) // reactor.core.Exceptions$ReactiveException - t.cause instanceof JsonRpcException - with(((JsonRpcException)t.cause).error) { + t.cause instanceof ChainException + with(((ChainException)t.cause).error) { code == -32603 message == "Something happened" } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcRequestSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcRequestSpec.groovy index 677d7544..72efce5a 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcRequestSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcRequestSpec.groovy @@ -15,15 +15,14 @@ */ package io.emeraldpay.dshackle.upstream.rpcclient - +import io.emeraldpay.dshackle.upstream.ChainRequest import spock.lang.Specification -import io.emeraldpay.dshackle.upstream.rpcclient.ListParams class JsonRpcRequestSpec extends Specification { def "Serialize empty params"() { setup: - def req = new JsonRpcRequest("test_foo", new ListParams()) + def req = new ChainRequest("test_foo", new ListParams()) when: def act = req.toJson() then: @@ -32,7 +31,7 @@ class JsonRpcRequestSpec extends Specification { def "Serialize single param"() { setup: - def req = new JsonRpcRequest("test_foo", new ListParams(["0x0000"])) + def req = new ChainRequest("test_foo", new ListParams(["0x0000"])) when: def act = req.toJson() then: @@ -41,7 +40,7 @@ class JsonRpcRequestSpec extends Specification { def "Serialize two params"() { setup: - def req = new JsonRpcRequest("test_foo", new ListParams(["0x0000", false])) + def req = new ChainRequest("test_foo", new ListParams(["0x0000", false])) when: def act = req.toJson() then: @@ -50,8 +49,8 @@ class JsonRpcRequestSpec extends Specification { def "Same requests are equal"() { setup: - def req1 = new JsonRpcRequest("test_foo", new ListParams(["0x0000", false])) - def req2 = new JsonRpcRequest("test_foo", new ListParams(["0x0000", false])) + def req1 = new ChainRequest("test_foo", new ListParams(["0x0000", false])) + def req2 = new ChainRequest("test_foo", new ListParams(["0x0000", false])) when: def act = req1.equals(req2) then: diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcResponseSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcResponseSpec.groovy index 80015210..9a315fe8 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcResponseSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcResponseSpec.groovy @@ -17,6 +17,8 @@ package io.emeraldpay.dshackle.upstream.rpcclient import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainResponse import spock.lang.Specification class JsonRpcResponseSpec extends Specification { @@ -25,8 +27,8 @@ class JsonRpcResponseSpec extends Specification { def "Same responses are equal"() { setup: - def resp1 = new JsonRpcResponse("\"hello\"".bytes, null) - def resp2 = new JsonRpcResponse("\"hello\"".bytes, null) + def resp1 = new ChainResponse("\"hello\"".bytes, null) + def resp2 = new ChainResponse("\"hello\"".bytes, null) when: def act = resp1.equals(resp2) then: @@ -35,35 +37,35 @@ class JsonRpcResponseSpec extends Specification { def "Extract processed string without quoted"() { when: - def act = new JsonRpcResponse("\"hello\"".bytes, null).resultAsProcessedString + def act = new ChainResponse("\"hello\"".bytes, null).resultAsProcessedString then: act == "hello" } def "Extract raw string with quoted"() { when: - def act = new JsonRpcResponse("\"hello\"".bytes, null).resultAsRawString + def act = new ChainResponse("\"hello\"".bytes, null).resultAsRawString then: act == "\"hello\"" } def "Fails to extract processed string if not quoted"() { when: - new JsonRpcResponse("{\"hello\": 1}".bytes, null).resultAsProcessedString + new ChainResponse("{\"hello\": 1}".bytes, null).resultAsProcessedString then: thrown(IllegalStateException) } def "Recognizes null"() { when: - def act = new JsonRpcResponse("null".bytes, null) + def act = new ChainResponse("null".bytes, null) then: act.isNull() } def "Serialize int id and null result"() { setup: - def json = new JsonRpcResponse("null".bytes, null, new JsonRpcResponse.NumberId(1), null, null, null) + def json = new ChainResponse("null".bytes, null, new ChainResponse.NumberId(1), null, null, null) when: def act = objectMapper.writeValueAsString(json) then: @@ -72,7 +74,7 @@ class JsonRpcResponseSpec extends Specification { def "Serialize int id and string result"() { setup: - def json = new JsonRpcResponse('"Hello World"'.bytes, null, new JsonRpcResponse.NumberId(10), null, null, null) + def json = new ChainResponse('"Hello World"'.bytes, null, new ChainResponse.NumberId(10), null, null, null) when: def act = objectMapper.writeValueAsString(json) then: @@ -81,7 +83,7 @@ class JsonRpcResponseSpec extends Specification { def "Serialize int id and object result"() { setup: - def json = new JsonRpcResponse('{"foo": "Hello World", "bar": 1}'.bytes, null, new JsonRpcResponse.NumberId(101), null, null, null) + def json = new ChainResponse('{"foo": "Hello World", "bar": 1}'.bytes, null, new ChainResponse.NumberId(101), null, null, null) when: def act = objectMapper.writeValueAsString(json) then: @@ -90,7 +92,7 @@ class JsonRpcResponseSpec extends Specification { def "Serialize int id and error"() { setup: - def json = new JsonRpcResponse(null, new JsonRpcError(-32041, "Oooops"), new JsonRpcResponse.NumberId(101), null, null, null) + def json = new ChainResponse(null, new ChainCallError(-32041, "Oooops"), new ChainResponse.NumberId(101), null, null, null) when: def act = objectMapper.writeValueAsString(json) then: @@ -99,7 +101,7 @@ class JsonRpcResponseSpec extends Specification { def "Serialize string id and null result"() { setup: - def json = new JsonRpcResponse("null".bytes, null, new JsonRpcResponse.StringId("asf01t1gg"), null, null, null) + def json = new ChainResponse("null".bytes, null, new ChainResponse.StringId("asf01t1gg"), null, null, null) when: def act = objectMapper.writeValueAsString(json) then: @@ -108,7 +110,7 @@ class JsonRpcResponseSpec extends Specification { def "Serialize string id and string result"() { setup: - def json = new JsonRpcResponse('"Hello World"'.bytes, null, new JsonRpcResponse.StringId("10"), null, null, null) + def json = new ChainResponse('"Hello World"'.bytes, null, new ChainResponse.StringId("10"), null, null, null) when: def act = objectMapper.writeValueAsString(json) then: @@ -117,7 +119,7 @@ class JsonRpcResponseSpec extends Specification { def "Serialize string id and object result"() { setup: - def json = new JsonRpcResponse('{"foo": "Hello World", "bar": 1}'.bytes, null, new JsonRpcResponse.StringId("g8gk19g"), null, null, null) + def json = new ChainResponse('{"foo": "Hello World", "bar": 1}'.bytes, null, new ChainResponse.StringId("g8gk19g"), null, null, null) when: def act = objectMapper.writeValueAsString(json) then: @@ -126,9 +128,9 @@ class JsonRpcResponseSpec extends Specification { def "Serialize string id and error"() { setup: - def json = new JsonRpcResponse(null, - new JsonRpcError(-32041, "Oooops"), - new JsonRpcResponse.StringId("9kbo29gkaasf"), null, null, null ) + def json = new ChainResponse(null, + new ChainCallError(-32041, "Oooops"), + new ChainResponse.StringId("9kbo29gkaasf"), null, null, null ) when: def act = objectMapper.writeValueAsString(json) then: diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClientSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClientSpec.groovy index 739ac7e0..144a5ca9 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClientSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcWsClientSpec.groovy @@ -1,8 +1,9 @@ package io.emeraldpay.dshackle.upstream.rpcclient +import io.emeraldpay.dshackle.upstream.ChainException +import io.emeraldpay.dshackle.upstream.ChainRequest import io.emeraldpay.dshackle.upstream.ethereum.WsConnection import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionPool -import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import reactor.core.Exceptions import spock.lang.Specification @@ -18,11 +19,11 @@ class JsonRpcWsClientSpec extends Specification { } def client = new JsonRpcWsClient(pool) when: - client.read(new JsonRpcRequest("foo_bar", new ListParams([]), 1)) + client.read(new ChainRequest("foo_bar", new ListParams([]), 1)) .block(Duration.ofSeconds(1)) then: def t = thrown(Exceptions.ReactiveException) - t.cause instanceof JsonRpcException + t.cause instanceof ChainException 1 * ws.isConnected() >> false } } diff --git a/src/test/kotlin/io/emeraldpay/dshackle/reader/RpcReaderFactoryTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/reader/RequestReaderFactoryTest.kt similarity index 79% rename from src/test/kotlin/io/emeraldpay/dshackle/reader/RpcReaderFactoryTest.kt rename to src/test/kotlin/io/emeraldpay/dshackle/reader/RequestReaderFactoryTest.kt index 5b2f0aa4..e53ce7a4 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/reader/RpcReaderFactoryTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/reader/RequestReaderFactoryTest.kt @@ -11,15 +11,15 @@ import org.junit.jupiter.params.provider.MethodSource import org.mockito.kotlin.mock import org.springframework.cloud.sleuth.Tracer -class RpcReaderFactoryTest { - private val defaultFactory = RpcReaderFactory.Default() +class RequestReaderFactoryTest { + private val defaultFactory = RequestReaderFactory.Default() @ParameterizedTest @MethodSource("data") fun `create BroadcastReader for MaximumValueQuorum and BroadcastQuorum`( - rpcReaderData: RpcReaderFactory.RpcReaderData, + readerData: RequestReaderFactory.ReaderData, ) { - val reader = defaultFactory.create(rpcReaderData) + val reader = defaultFactory.create(readerData) assertTrue(reader is BroadcastReader) } @@ -32,9 +32,8 @@ class RpcReaderFactoryTest { fun data(): List { return listOf( Arguments.of( - RpcReaderFactory.RpcReaderData( + RequestReaderFactory.ReaderData( ms, - "method", Selector.empty, MaximumValueQuorum(), null, @@ -42,9 +41,8 @@ class RpcReaderFactoryTest { ), ), Arguments.of( - RpcReaderFactory.RpcReaderData( + RequestReaderFactory.ReaderData( ms, - "method", Selector.empty, BroadcastQuorum(), null, diff --git a/src/test/kotlin/io/emeraldpay/dshackle/rpc/NativeCallTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/rpc/NativeCallTest.kt index dd8a2c51..86f5ed41 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/rpc/NativeCallTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/rpc/NativeCallTest.kt @@ -5,8 +5,8 @@ import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallReplyItem import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallRequest import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.upstream.MultistreamHolder -import io.emeraldpay.dshackle.upstream.rpcclient.stream.Chunk import io.emeraldpay.dshackle.upstream.signature.ResponseSigner +import io.emeraldpay.dshackle.upstream.stream.Chunk import org.junit.jupiter.api.Test import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/RecursiveLowerBoundBlockDetectorTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/RecursiveLowerBoundBlockDetectorTest.kt index 1f194713..2577a53c 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/RecursiveLowerBoundBlockDetectorTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/RecursiveLowerBoundBlockDetectorTest.kt @@ -1,11 +1,9 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.Chain -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader import io.emeraldpay.dshackle.upstream.ethereum.EthereumLowerBoundBlockDetector import io.emeraldpay.dshackle.upstream.polkadot.PolkadotLowerBoundBlockDetector -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.params.ParameterizedTest @@ -23,7 +21,7 @@ class RecursiveLowerBoundBlockDetectorTest { @ParameterizedTest @MethodSource("detectors") fun `find lower block closer to the height`( - reader: JsonRpcReader, + reader: ChainReader, detectorClass: Class, ) { val head = mock { @@ -49,7 +47,7 @@ class RecursiveLowerBoundBlockDetectorTest { @ParameterizedTest @MethodSource("detectorsFirstBlock") fun `lower block is 0x1`( - reader: JsonRpcReader, + reader: ChainReader, detectorClass: Class, ) { val head = mock { @@ -84,15 +82,15 @@ class RecursiveLowerBoundBlockDetectorTest { @JvmStatic fun detectors(): List = listOf( Arguments.of( - mock { + mock { blocks.forEach { if (it == 17964844L) { on { - read(JsonRpcRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex()))) - } doReturn Mono.just(JsonRpcResponse(ByteArray(0), null)) + read(ChainRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex()))) + } doReturn Mono.just(ChainResponse(ByteArray(0), null)) } else { on { - read(JsonRpcRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex()))) + read(ChainRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex()))) } doReturn Mono.error(RuntimeException("missing trie node")) } } @@ -100,21 +98,21 @@ class RecursiveLowerBoundBlockDetectorTest { EthereumLowerBoundBlockDetector::class.java, ), Arguments.of( - mock { + mock { blocks.forEach { if (it == 17964844L) { on { - read(JsonRpcRequest("chain_getBlockHash", ListParams(it.toHex()))) - } doReturn Mono.just(JsonRpcResponse("\"$hash1\"".toByteArray(), null)) + read(ChainRequest("chain_getBlockHash", ListParams(it.toHex()))) + } doReturn Mono.just(ChainResponse("\"$hash1\"".toByteArray(), null)) on { - read(JsonRpcRequest("state_getMetadata", ListParams(hash1))) - } doReturn Mono.just(JsonRpcResponse(ByteArray(0), null)) + read(ChainRequest("state_getMetadata", ListParams(hash1))) + } doReturn Mono.just(ChainResponse(ByteArray(0), null)) } else { on { - read(JsonRpcRequest("chain_getBlockHash", ListParams(it.toHex()))) - } doReturn Mono.just(JsonRpcResponse("\"$hash2\"".toByteArray(), null)) + read(ChainRequest("chain_getBlockHash", ListParams(it.toHex()))) + } doReturn Mono.just(ChainResponse("\"$hash2\"".toByteArray(), null)) on { - read(JsonRpcRequest("state_getMetadata", ListParams(hash2))) + read(ChainRequest("state_getMetadata", ListParams(hash2))) } doReturn Mono.error(RuntimeException("State already discarded for")) } } @@ -126,16 +124,16 @@ class RecursiveLowerBoundBlockDetectorTest { @JvmStatic fun detectorsFirstBlock(): List = listOf( Arguments.of( - mock { + mock { on { read(any()) - } doReturn Mono.just(JsonRpcResponse("\"0x1\"".toByteArray(), null)) + } doReturn Mono.just(ChainResponse("\"0x1\"".toByteArray(), null)) }, PolkadotLowerBoundBlockDetector::class.java, ), Arguments.of( - mock { - on { read(any()) } doReturn Mono.just(JsonRpcResponse(ByteArray(0), null)) + mock { + on { read(any()) } doReturn Mono.just(ChainResponse(ByteArray(0), null)) }, EthereumLowerBoundBlockDetector::class.java, ), diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecificTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecificTest.kt new file mode 100644 index 00000000..528bd59d --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainSpecificTest.kt @@ -0,0 +1,50 @@ +package io.emeraldpay.dshackle.upstream.beaconchain + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.math.BigInteger +import java.time.Instant + +class BeaconChainSpecificTest { + + @Test + fun `test getting latest block`() { + val header = """ + { + "execution_optimistic": false, + "finalized": false, + "data": { + "header": { + "message": { + "slot": "8639623", + "proposer_index": "830020", + "parent_root": "0x3306b3f2d5f4de709a9b13c51ca7bbeab41ee93f44c6873baf6a59495620dbbf", + "state_root": "0x1445392736bd2485e5afaaea62a642bea69f75379653e7a1f0846806af8d6d75", + "body_root": "0x0a02f2980fda874a050fc2f6fde63ebbeb749318f23325673f1fc4af2b252e84" + }, + "signature": "0x961b9c71530f2a55630e3c943aa054f6a863c86fd53b6644c1680bdb2d70a65a0082e5282fb3bf54a0167ba706fe78961289a652da13594a00d63c4ef8d60f53f515ce5ca1ced41b25a7689fd6e6773ec6b771e4e1f67c37eea2b8ef26c5eb4f" + }, + "root": "0x3ae4855df015bf9906f59aa0c612396105fa7c11495b5596fa946672c3ce5674", + "canonical": true + } + } + """.trimIndent().toByteArray() + val expected = BlockContainer( + height = 8639623, + hash = BlockId.from("0x3ae4855df015bf9906f59aa0c612396105fa7c11495b5596fa946672c3ce5674"), + difficulty = BigInteger.ZERO, + timestamp = Instant.EPOCH, + full = false, + json = header, + transactions = emptyList(), + upstreamId = "upId", + parentHash = BlockId.from("0x3306b3f2d5f4de709a9b13c51ca7bbeab41ee93f44c6873baf6a59495620dbbf"), + parsed = null, + ) + + val block = BeaconChainSpecific.parseBlock(header, "upId") + assertEquals(expected, block) + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidatorTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidatorTest.kt new file mode 100644 index 00000000..7de9099f --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/beaconchain/BeaconChainValidatorTest.kt @@ -0,0 +1,98 @@ +package io.emeraldpay.dshackle.upstream.beaconchain + +import io.emeraldpay.dshackle.foundation.ChainOptions +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainCallError +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.UpstreamAvailability +import io.emeraldpay.dshackle.upstream.rpcclient.RestParams +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import reactor.core.publisher.Mono + +class BeaconChainValidatorTest { + + @ParameterizedTest + @MethodSource("data") + fun `validation beacon chain`( + respHealth: Mono, + respSync: Mono, + respPeers: Mono, + expected: UpstreamAvailability, + ) { + val reader = mock { + on { read(ChainRequest("GET#/eth/v1/node/health", RestParams.emptyParams())) } doReturn + respHealth + on { read(ChainRequest("GET#/eth/v1/node/syncing", RestParams.emptyParams())) } doReturn + respSync + on { read(ChainRequest("GET#/eth/v1/node/peer_count", RestParams.emptyParams())) } doReturn + respPeers + } + val upstream = mock { + on { getIngressReader() } doReturn reader + on { getHead() } doReturn mock() + } + val validator = BeaconChainValidator(upstream, ChainOptions.PartialOptions.getDefaults().buildOptions()) + + val result = validator.validate().block() + + assertEquals(expected, result) + } + + companion object { + @JvmStatic + fun data(): List { + return listOf( + Arguments.of( + Mono.just(ChainResponse(ByteArray(0), null)), + Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)), + Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)), + UpstreamAvailability.OK, + ), + Arguments.of( + Mono.error(RuntimeException("error")), + Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)), + Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)), + UpstreamAvailability.UNAVAILABLE, + ), + Arguments.of( + Mono.just(ChainResponse(ByteArray(0), null)), + Mono.just(ChainResponse("""{"data": {"is_syncing": true}}""".toByteArray(), null)), + Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)), + UpstreamAvailability.SYNCING, + ), + Arguments.of( + Mono.just(ChainResponse(ByteArray(0), null)), + Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)), + Mono.just(ChainResponse("""{"data": {"connected": "0"}}""".toByteArray(), null)), + UpstreamAvailability.IMMATURE, + ), + Arguments.of( + Mono.just(ChainResponse(ByteArray(0), null)), + Mono.error(RuntimeException("error")), + Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)), + UpstreamAvailability.UNAVAILABLE, + ), + Arguments.of( + Mono.just(ChainResponse(ByteArray(0), null)), + Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)), + Mono.error(RuntimeException("error")), + UpstreamAvailability.UNAVAILABLE, + ), + Arguments.of( + Mono.just(ChainResponse(null, ChainCallError(1, "Error"))), + Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)), + Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)), + UpstreamAvailability.UNAVAILABLE, + ), + ) + } + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnectorFactoryCreatorTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnectorFactoryCreatorTest.kt index 25225d42..0d8f1432 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnectorFactoryCreatorTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/generic/connectors/GenericConnectorFactoryCreatorTest.kt @@ -42,7 +42,7 @@ class GenericConnectorFactoryCreatorTest { mockConstruction(GenericRpcHead::class.java) { _, ctx -> args = ctx.arguments() } .use { - factory.createConnectorFactoryCreator( + factory.createConnectorFactory( "id", UpstreamsConfig.RpcConnection( UpstreamsConfig.HttpEndpoint(URI("http://localhost")), diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParserTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParserTest.kt new file mode 100644 index 00000000..a9854b81 --- /dev/null +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/restclient/RestRequestParserTest.kt @@ -0,0 +1,50 @@ +package io.emeraldpay.dshackle.upstream.restclient + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +class RestRequestParserTest { + + @Test + fun `transform query params into string`() { + val queryParams = mapOf( + "first" to "coolParam", + "second" to "moreCoolParam", + ) + + val result = RestRequestParser.transformQueryParams(queryParams) + + assertEquals("?first=coolParam&second=moreCoolParam", result) + } + + @Test + fun `transform query params into empty string`() { + val result = RestRequestParser.transformQueryParams(emptyMap()) + + assertEquals("", result) + } + + @ParameterizedTest + @MethodSource("data") + fun `transform path params into path with these params`(path: String, params: List, expected: String) { + val result = RestRequestParser.transformPathParams(path, params) + + assertEquals(expected, result) + } + + companion object { + @JvmStatic + fun data(): List { + return listOf( + Arguments.of("/eth/v1/blocks/*/state/*/root", listOf("123", "678"), "/eth/v1/blocks/123/state/678/root"), + Arguments.of("/eth/v1/blocks/*/state/*", listOf("123", "678"), "/eth/v1/blocks/123/state/678"), + Arguments.of("/eth/v1/blocks", emptyList(), "/eth/v1/blocks"), + Arguments.of("/eth/v1/blocks/*", listOf("123", "678"), "/eth/v1/blocks/123"), + Arguments.of("/eth/v1/blocks", listOf("123", "678"), "/eth/v1/blocks"), + ) + } + } +} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParserTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParserTest.kt index 73afb931..6c2c61b7 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParserTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/stream/JsonRpcStreamParserTest.kt @@ -1,5 +1,9 @@ package io.emeraldpay.dshackle.upstream.rpcclient.stream +import io.emeraldpay.dshackle.upstream.stream.AggregateResponse +import io.emeraldpay.dshackle.upstream.stream.Chunk +import io.emeraldpay.dshackle.upstream.stream.SingleResponse +import io.emeraldpay.dshackle.upstream.stream.StreamResponse import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetectorTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetectorTest.kt index 70015896..851de30e 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetectorTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaLowerBoundBlockDetectorTest.kt @@ -2,10 +2,10 @@ package io.emeraldpay.dshackle.upstream.solana import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Global -import io.emeraldpay.dshackle.reader.JsonRpcReader +import io.emeraldpay.dshackle.reader.ChainReader +import io.emeraldpay.dshackle.upstream.ChainRequest +import io.emeraldpay.dshackle.upstream.ChainResponse import io.emeraldpay.dshackle.upstream.Upstream -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest -import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -19,12 +19,12 @@ class SolanaLowerBoundBlockDetectorTest { @Test fun `get solana lower block and slot`() { - val reader = mock { - on { read(JsonRpcRequest("getFirstAvailableBlock", ListParams())) } doReturn - Mono.just(JsonRpcResponse("25000000".toByteArray(), null)) + val reader = mock { + on { read(ChainRequest("getFirstAvailableBlock", ListParams())) } doReturn + Mono.just(ChainResponse("25000000".toByteArray(), null)) on { read( - JsonRpcRequest( + ChainRequest( "getBlock", ListParams( 25000000L, @@ -37,7 +37,7 @@ class SolanaLowerBoundBlockDetectorTest { ), ) } doReturn Mono.just( - JsonRpcResponse( + ChainResponse( Global.objectMapper.writeValueAsBytes( mapOf( "blockHeight" to 21000000,