problem: some JSON RPC client cannot use IDs and process response by their order

solution: add option to preserve batch request-response correspondence order
rel: #148
This commit is contained in:
Vyacheslav Shebanov
2022-04-21 05:08:27 +03:00
committed by GitHub
parent feb2bb89f7
commit 4f1216b14f
14 changed files with 187 additions and 16 deletions

View File

@@ -81,6 +81,7 @@ class ProxyConfigReaderSpec extends Specification {
act.enabled
act.host == '0.0.0.0'
act.port == 8080
act.preserveBatchOrder
act.routes.size() == 2
with(act.routes[0]) {
id == "ethereum"

View File

@@ -34,7 +34,7 @@ class BaseHandlerSpec extends Specification {
setup:
def handler = new BaseHandlerImpl(new WriteRpcJson(), Stub(NativeCall), Stub(ProxyServer.RequestMetricsFactory))
when:
def act = Mono.from(handler.execute(Chain.ETHEREUM, new ProxyCall(ProxyCall.RpcType.SINGLE), requestHandler))
def act = Mono.from(handler.execute(Chain.ETHEREUM, new ProxyCall(ProxyCall.RpcType.SINGLE), requestHandler, false))
.block(Duration.ofSeconds(1))
then:
act == ""
@@ -44,7 +44,7 @@ class BaseHandlerSpec extends Specification {
setup:
def handler = new BaseHandlerImpl(new WriteRpcJson(), Stub(NativeCall), Stub(ProxyServer.RequestMetricsFactory))
when:
def act = Mono.from(handler.execute(Chain.ETHEREUM, new ProxyCall(ProxyCall.RpcType.BATCH), requestHandler))
def act = Mono.from(handler.execute(Chain.ETHEREUM, new ProxyCall(ProxyCall.RpcType.BATCH), requestHandler, false))
.block(Duration.ofSeconds(1))
then:
act == "[]"
@@ -64,7 +64,7 @@ class BaseHandlerSpec extends Specification {
call.ids[0] = 5
def response = new NativeCall.CallResult(0, '{"foo": 1}'.bytes, null)
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM, call, requestHandler))
def act = Flux.from(handler.execute(Chain.ETHEREUM, call, requestHandler, false))
.collectList()
.block(Duration.ofSeconds(1))
.join("")
@@ -87,7 +87,7 @@ class BaseHandlerSpec extends Specification {
call.ids[0] = 5
def response = new NativeCall.CallResult(0, '{"foo": 1}'.bytes, null)
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM, call, requestHandler))
def act = Flux.from(handler.execute(Chain.ETHEREUM, call, requestHandler, false))
.collectList()
.block(Duration.ofSeconds(1))
.join("")
@@ -96,6 +96,112 @@ class BaseHandlerSpec extends Specification {
1 * nativeCall.nativeCallResult(_) >> Flux.fromIterable([response])
}
def "Execute ordered batch call with 2 items"() {
setup:
def nativeCall = Mock(NativeCall)
def handler = new BaseHandlerImpl(new WriteRpcJson(), nativeCall, Stub(ProxyServer.RequestMetricsFactory))
def request1 = BlockchainOuterClass.NativeCallItem.newBuilder()
.setMethod("eth_test")
.setId(0)
.build()
def request2 = BlockchainOuterClass.NativeCallItem.newBuilder()
.setMethod("eth_test2")
.setId(1)
.build()
def call = new ProxyCall(ProxyCall.RpcType.BATCH)
call.items.add(request1)
call.ids[0] = 5
call.items.add(request2)
call.ids[1] = 6
def response = [
new NativeCall.CallResult(1, '{"foo": 2}'.bytes, null),
new NativeCall.CallResult(0, '{"foo": 1}'.bytes, null)
]
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM, call, requestHandler, true))
.collectList()
.block(Duration.ofSeconds(1))
.join("")
then:
act == '[{"jsonrpc":"2.0","id":5,"result":{"foo": 1}},{"jsonrpc":"2.0","id":6,"result":{"foo": 2}}]'
1 * nativeCall.nativeCallResult(_) >> Flux.fromIterable(response)
}
def "Execute ordered batch call with 2 items even if original ids are not in sequence"() {
setup:
def nativeCall = Mock(NativeCall)
def handler = new BaseHandlerImpl(new WriteRpcJson(), nativeCall, Stub(ProxyServer.RequestMetricsFactory))
def request1 = BlockchainOuterClass.NativeCallItem.newBuilder()
.setMethod("eth_test")
.setId(0)
.build()
def request2 = BlockchainOuterClass.NativeCallItem.newBuilder()
.setMethod("eth_test2")
.setId(1)
.build()
def call = new ProxyCall(ProxyCall.RpcType.BATCH)
call.items.add(request1)
call.ids[0] = 15
call.items.add(request2)
call.ids[1] = 6
def response = [
new NativeCall.CallResult(1, '{"foo": 2}'.bytes, null),
new NativeCall.CallResult(0, '{"foo": 1}'.bytes, null)
]
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM, call, requestHandler, true))
.collectList()
.block(Duration.ofSeconds(1))
.join("")
then:
act == '[{"jsonrpc":"2.0","id":15,"result":{"foo": 1}},{"jsonrpc":"2.0","id":6,"result":{"foo": 2}}]'
1 * nativeCall.nativeCallResult(_) >> Flux.fromIterable(response)
}
def "Adds a missing element if order is requests"() {
setup:
def nativeCall = Mock(NativeCall)
def handler = new BaseHandlerImpl(new WriteRpcJson(), nativeCall, Stub(ProxyServer.RequestMetricsFactory))
def request1 = BlockchainOuterClass.NativeCallItem.newBuilder()
.setMethod("eth_test")
.setId(0)
.build()
def request2 = BlockchainOuterClass.NativeCallItem.newBuilder()
.setMethod("eth_test2")
.setId(1)
.build()
def request3 = BlockchainOuterClass.NativeCallItem.newBuilder()
.setMethod("eth_test3")
.setId(2)
.build()
def call = new ProxyCall(ProxyCall.RpcType.BATCH)
call.items.add(request1)
call.ids[0] = 5
call.items.add(request2)
call.ids[1] = 6
call.items.add(request3)
call.ids[2] = 7
// note there is only 2 responses
def response = [
new NativeCall.CallResult(1, '{"foo": 2}'.bytes, null),
new NativeCall.CallResult(2, '{"foo": 3}'.bytes, null)
]
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM, call, requestHandler, true))
.collectList()
.block(Duration.ofSeconds(1))
.join("")
then:
act == '[{"jsonrpc":"2.0","id":5,"error":{"code":-32002,"message":"No response"}},{"jsonrpc":"2.0","id":6,"result":{"foo": 2}},{"jsonrpc":"2.0","id":7,"result":{"foo": 3}}]'
1 * nativeCall.nativeCallResult(_) >> Flux.fromIterable(response)
}
class BaseHandlerImpl extends BaseHandler {
BaseHandlerImpl(@NotNull WriteRpcJson writeRpcJson, @NotNull NativeCall nativeCall, @NotNull ProxyServer.RequestMetricsFactory requestMetrics) {

View File

@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.proxy
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
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
@@ -56,6 +57,7 @@ class HttpHandlerSpec extends Specification {
_ * it.create(_,) >> accessHandler
}
def handler = new HttpHandler(
new ProxyConfig(),
new ReadRpcJson(), new WriteRpcJson(),
nativeCall, accessHandlerFactory, Stub(ProxyServer.RequestMetricsFactory)
)
@@ -84,6 +86,7 @@ class HttpHandlerSpec extends Specification {
}
def handler = new HttpHandler(
new ProxyConfig(),
read, new WriteRpcJson(),
Stub(NativeCall), Stub(AccessHandlerHttp.HandlerFactory),
metrics
@@ -110,6 +113,7 @@ class HttpHandlerSpec extends Specification {
def handler = new HttpHandler(
new ProxyConfig(),
new ReadRpcJson(), writeRpcJson,
nativeCall, Stub(AccessHandlerHttp.HandlerFactory), Stub(ProxyServer.RequestMetricsFactory)
)
@@ -122,7 +126,7 @@ class HttpHandlerSpec extends Specification {
.build()
)
when:
def act = handler.execute(Chain.ETHEREUM, call, new AccessHandlerHttp.NoOpHandler())
def act = handler.execute(Chain.ETHEREUM, call, new AccessHandlerHttp.NoOpHandler(), false)
then:
1 * nativeCall.nativeCallResult(_) >> Flux.just(new NativeCall.CallResult(1, "".bytes, null))

View File

@@ -2,6 +2,7 @@ proxy:
enabled: true
host: 0.0.0.0
port: 8080
preserve-batch-order: true
routes:
- id: ethereum
blockchain: ethereum