Stream responses from node (#361)

This commit is contained in:
KirillPamPam
2023-12-14 12:09:25 +04:00
committed by GitHub
parent a9a3e64c32
commit 6e8f600cdc
39 changed files with 768 additions and 450 deletions

View File

@@ -62,7 +62,7 @@ class BaseHandlerSpec extends Specification {
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
call.items.add(request)
call.ids[0] = 5
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null)
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null, null)
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM__MAINNET, call, requestHandler, false))
.collectList()
@@ -85,7 +85,7 @@ class BaseHandlerSpec extends Specification {
def call = new ProxyCall(ProxyCall.RpcType.BATCH)
call.items.add(request)
call.ids[0] = 5
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null)
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null, null)
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM__MAINNET, call, requestHandler, false))
.collectList()
@@ -116,8 +116,8 @@ class BaseHandlerSpec extends Specification {
call.items.add(request2)
call.ids[1] = 6
def response = [
new NativeCall.CallResult(1, null, '{"foo": 2}'.bytes, null, null, null, null),
new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null)
new NativeCall.CallResult(1, null, '{"foo": 2}'.bytes, null, null, null, null, null),
new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null, null)
]
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM__MAINNET, call, requestHandler, true))
@@ -149,8 +149,8 @@ class BaseHandlerSpec extends Specification {
call.items.add(request2)
call.ids[1] = 6
def response = [
new NativeCall.CallResult(1, null, '{"foo": 2}'.bytes, null, null, null, null),
new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null)
new NativeCall.CallResult(1, null, '{"foo": 2}'.bytes, null, null, null, null, null),
new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, null, null, null)
]
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM__MAINNET, call, requestHandler, true))
@@ -189,8 +189,8 @@ class BaseHandlerSpec extends Specification {
// note there is only 2 responses
def response = [
new NativeCall.CallResult(1, null, '{"foo": 2}'.bytes, null, null, null, null),
new NativeCall.CallResult(2, null, '{"foo": 3}'.bytes, null, null, null, null)
new NativeCall.CallResult(1, null, '{"foo": 2}'.bytes, null, null, null, null, null),
new NativeCall.CallResult(2, null, '{"foo": 3}'.bytes, null, null, null, null, null)
]
when:
def act = Flux.from(handler.execute(Chain.ETHEREUM__MAINNET, call, requestHandler, true))

View File

@@ -42,7 +42,7 @@ class HttpHandlerSpec extends Specification {
.setMethod("test_test")
.setPayload(ByteString.copyFromUtf8("[]"))
.build()
def respItem = new NativeCall.CallResult(1, null, "100".bytes, null, null, null, null)
def respItem = new NativeCall.CallResult(1, null, "100".bytes, null, null, null, null, null)
def req = BlockchainOuterClass.NativeCallRequest.newBuilder()
.setChain(Common.ChainRef.CHAIN_ETHEREUM__MAINNET)
.addItems(reqItem)
@@ -128,7 +128,7 @@ class HttpHandlerSpec extends Specification {
def act = handler.execute(Chain.ETHEREUM__MAINNET, call, new AccessHandlerHttp.NoOpHandler(), false)
then:
1 * nativeCall.nativeCallResult(_) >> Flux.just(new NativeCall.CallResult(1, null, "".bytes, null, null, null, null))
1 * nativeCall.nativeCallResult(_) >> Flux.just(new NativeCall.CallResult(1, null, "".bytes, null, null, null, null, null))
StepVerifier.create(act)
.expectNext("hello")
.expectComplete()

View File

@@ -85,7 +85,7 @@ class WebsocketHandlerSpec extends Specification {
def "Respond to a single call"() {
setup:
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, "test", null)
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, "test", null, null)
def nativeCall = Mock(NativeCall) {
1 * it.nativeCallResult(_) >> Flux.fromIterable([response])

View File

@@ -85,7 +85,7 @@ class WriteRpcJsonSpec extends Specification {
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
call.ids[1] = 105
def data = [
new NativeCall.CallResult(1, null, '"0x98dbb1"'.bytes, null, null, null, null)
new NativeCall.CallResult(1, null, '"0x98dbb1"'.bytes, null, null, null, null, null)
]
when:
def act = writer.toJson(call, data[0])
@@ -98,7 +98,7 @@ class WriteRpcJsonSpec extends Specification {
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
call.ids[1] = 1
def data = [
new NativeCall.CallResult(1, null, null, new NativeCall.CallError(1, "Internal Error", null, null, null), null, null, null)
new NativeCall.CallResult(1, null, null, new NativeCall.CallError(1, "Internal Error", null, null, null), null, null, null, null)
]
when:
def act = writer.toJson(call, data[0])
@@ -111,7 +111,7 @@ class WriteRpcJsonSpec extends Specification {
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
call.ids[1] = "aaa"
def data = [
new NativeCall.CallResult(1, null, '"0x98dbb1"'.bytes, null, null, null, null)
new NativeCall.CallResult(1, null, '"0x98dbb1"'.bytes, null, null, null, null, null)
]
when:
def act = writer.toJson(call, data[0])
@@ -126,9 +126,9 @@ class WriteRpcJsonSpec extends Specification {
call.ids[2] = 11
call.ids[3] = 15
def data = [
new NativeCall.CallResult(1, null, '"0x98dbb1"'.bytes, null, null, null, null),
new NativeCall.CallResult(2, null, null, new NativeCall.CallError(2, "oops", null, null, null), null, null, null),
new NativeCall.CallResult(3, null, '{"hash": "0x2484f459dc"}'.bytes, null, null, null, null),
new NativeCall.CallResult(1, null, '"0x98dbb1"'.bytes, null, null, null, null, null),
new NativeCall.CallResult(2, null, null, new NativeCall.CallError(2, "oops", null, null, null), null, null, null, null),
new NativeCall.CallResult(3, null, '{"hash": "0x2484f459dc"}'.bytes, null, null, null, null, null),
]
when:
def act = Flux.fromIterable(data)
@@ -154,7 +154,7 @@ class WriteRpcJsonSpec extends Specification {
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
call.ids[1] = 10
def data = [
new NativeCall.CallResult(1, null, '"0x1"'.bytes, null, null, null, null),
new NativeCall.CallResult(1, null, '"0x1"'.bytes, null, null, null, null, null),
]
when:
def act = Flux.fromIterable(data)

View File

@@ -17,6 +17,7 @@ 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.signature.ResponseSigner
import spock.lang.Specification
@@ -42,10 +43,10 @@ class AlwaysQuorumSpec extends Specification {
def quorum = new AlwaysQuorum()
def up = Stub(Upstream)
when:
quorum.record("123".bytes, new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
quorum.record(new JsonRpcResponse("123".bytes, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
then:
quorum.isResolved()
quorum.getResult() == "123".bytes
quorum.getResponse().getResult() == "123".bytes
quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100)
!quorum.isFailed()
}

View File

@@ -18,9 +18,9 @@ package io.emeraldpay.dshackle.quorum
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import spock.lang.Specification
class BroadcastQuorumSpec extends Specification {
@@ -35,20 +35,20 @@ class BroadcastQuorumSpec extends Specification {
def upstream3 = Stub(Upstream)
when:
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream1)
q.record(new JsonRpcResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream1)
then:
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _)
when:
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2)
q.record(new JsonRpcResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream2)
then:
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _)
when:
q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3)
then:
1 * q.recordError(_, _, _, _)
objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
1 * q.recordError(_, _, _)
objectMapper.readValue(q.response.getResult(), Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
}
def "Remembers first response"() {
@@ -61,19 +61,19 @@ class BroadcastQuorumSpec extends Specification {
when:
q.record(new JsonRpcException(1, "Internal error"), null, upstream1)
then:
1 * q.recordError(_, _, _, _)
1 * q.recordError(_, _, _)
when:
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2)
q.record(new JsonRpcResponse('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null), null, upstream2)
then:
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _)
when:
q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3)
then:
1 * q.recordError(_, _, _, _)
1 * q.recordError(_, _, _)
q.isResolved()
objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
objectMapper.readValue(q.response.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
}
def "Failed if error received 3+ times"() {

View File

@@ -2,6 +2,7 @@ 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 spock.lang.Specification
class MaximumValueQuorumSpec extends Specification {
@@ -18,11 +19,11 @@ class MaximumValueQuorumSpec extends Specification {
}
when:
def quorum = new MaximumValueQuorum()
quorum.record('"0x137"'.bytes, null, up)
quorum.record('"0x138"'.bytes, null, up1)
quorum.record('"0x139"'.bytes, null, up2)
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)
then:
quorum.result == '"0x139"'.bytes
quorum.response.result == '"0x139"'.bytes
quorum.resolvedBy.size() == 1
quorum.isResolved()
quorum.resolvedBy.contains(up2)
@@ -41,11 +42,11 @@ class MaximumValueQuorumSpec extends Specification {
}
when:
def quorum = new MaximumValueQuorum()
quorum.record('"0x137"'.bytes, null, up)
quorum.record('"0x138"'.bytes, null, up1)
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)
then:
quorum.result == '"0x138"'.bytes
quorum.response.result == '"0x138"'.bytes
quorum.isResolved()
quorum.resolvedBy.size() == 1
quorum.resolvedBy.contains(up1)

View File

@@ -18,6 +18,7 @@ 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.signature.ResponseSigner
import spock.lang.Specification
@@ -30,12 +31,12 @@ class NotLaggingQuorumSpec extends Specification {
def quorum = new NotLaggingQuorum(1)
when:
quorum.record(value, null, up)
quorum.record(new JsonRpcResponse(value, null), null, up)
then:
1 * up.getLag() >> 0
quorum.isResolved()
!quorum.isFailed()
quorum.result == value
quorum.response.result == value
}
def "Keeps signature and upstream"() {
@@ -45,12 +46,12 @@ class NotLaggingQuorumSpec extends Specification {
def quorum = new NotLaggingQuorum(1)
when:
quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
quorum.record(new JsonRpcResponse(value, null), new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
then:
1 * up.getLag() >> 0
quorum.isResolved()
!quorum.isFailed()
quorum.result == value
quorum.response.result == value
quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100)
}
@@ -61,12 +62,12 @@ class NotLaggingQuorumSpec extends Specification {
def quorum = new NotLaggingQuorum(1)
when:
quorum.record(value, null, up)
quorum.record(new JsonRpcResponse(value, null), null, up)
then:
1 * up.getLag() >> 1
quorum.isResolved()
!quorum.isFailed()
quorum.result == value
quorum.response.result == value
}
def "Ignores if lags"() {
@@ -76,7 +77,7 @@ class NotLaggingQuorumSpec extends Specification {
def quorum = new NotLaggingQuorum(1)
when:
quorum.record(value, null, up)
quorum.record(new JsonRpcResponse(value, null), null, up)
then:
1 * up.getLag() >> 2
!quorum.isResolved()

View File

@@ -2,6 +2,7 @@ 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.signature.ResponseSigner
import spock.lang.Specification
@@ -19,19 +20,20 @@ class NotNullQuorumSpec extends Specification {
1 * getId() >> "id2"
}
def value = "null".getBytes()
def response = new JsonRpcResponse(value, null)
def quorum = new NotNullQuorum()
when:
def res = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
def res1 = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up1)
def res2 = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up2)
def res3 = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
def res = quorum.record(response, new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
def res1 = quorum.record(response, new ResponseSigner.Signature("sig1".bytes, "test", 100), up1)
def res2 = quorum.record(response, new ResponseSigner.Signature("sig1".bytes, "test", 100), up2)
def res3 = quorum.record(response, new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
then:
!res
!res1
!res2
res3
quorum.result == value
quorum.response.result == value
!quorum.isFailed()
quorum.isResolved()
quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100)
@@ -77,7 +79,7 @@ class NotNullQuorumSpec extends Specification {
def quorum = new NotNullQuorum()
when:
def res = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
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)
@@ -86,7 +88,7 @@ class NotNullQuorumSpec extends Specification {
!res
quorum.isResolved()
!quorum.isFailed()
quorum.result == value
quorum.response.result == value
quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100)
}
}

View File

@@ -15,8 +15,9 @@
*/
package io.emeraldpay.dshackle.quorum
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
@@ -66,14 +67,14 @@ class ValueAwareQuorumSpec extends Specification {
}
@Override
void recordValue(@NotNull byte[] response, @Nullable Object responseValue, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) {
void recordValue(@NotNull JsonRpcResponse response, @Nullable Object responseValue, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) {
}
@Override
void recordError(@Nullable byte[] response, @Nullable String errorMessage, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) {
void recordError(@Nullable String errorMessage, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) {
}
@@ -88,8 +89,8 @@ class ValueAwareQuorumSpec extends Specification {
}
@Override
byte[] getResult() {
return new byte[0]
JsonRpcResponse getResponse() {
return new JsonRpcResponse(null, null)
}
@Override

View File

@@ -181,7 +181,7 @@ class BroadcastReaderSpec extends Specification {
when:
def act = reader
.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
.switchIfEmpty(Mono.just(new RpcReader.Result(new byte[0], null, 0, null)))
.switchIfEmpty(Mono.just(new RpcReader.Result(new byte[0], null, 0, null, null)))
then:
StepVerifier.create(act)
.expectErrorMessage("Unhandled Upstream error")

View File

@@ -130,7 +130,7 @@ 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))
1 * read(_) >> Mono.just(new RpcReader.Result("\"foo\"".bytes, null, 1, ups, null))
}
}
def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum,
@@ -238,7 +238,7 @@ class NativeCallSpec extends Specification {
when:
def resp = nativeCall.buildResponse(
new NativeCall.CallResult(1561, 10, objectMapper.writeValueAsBytes(json), null, null, null, null)
new NativeCall.CallResult(1561, 10, objectMapper.writeValueAsBytes(json), null, null, null, null, null)
)
then:
resp.id == 1561
@@ -253,7 +253,7 @@ class NativeCallSpec extends Specification {
when:
def resp = nativeCall.buildResponse(
new NativeCall.CallResult(1561, 10, objectMapper.writeValueAsBytes(json), null, new ResponseSigner.Signature("sig1".bytes, "test", 100), "test", null)
new NativeCall.CallResult(1561, 10, objectMapper.writeValueAsBytes(json), null, new ResponseSigner.Signature("sig1".bytes, "test", 100), "test", null, null)
)
then:
resp.id == 1561
@@ -584,7 +584,7 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall()
def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), Selector.empty, new AlwaysQuorum(),
new NativeCall.RawCallDetails("eth_getFilterUpdates", '["0xabcd"]'),
new NativeCall.WithFilterIdDecorator(), new NativeCall.NoneResultDecorator(), null, "reqId", 1)
new NativeCall.WithFilterIdDecorator(), new NativeCall.NoneResultDecorator(), null, false, "reqId", 1)
when:
def act = nativeCall.parseParams(ctx)
then:
@@ -614,12 +614,12 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall(multistreamHolder)
nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_) >> Mock(RpcReader) {
1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups))
1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups, null))
}
}
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,
new NativeCall.ParsedCallDetails("eth_getFilterChanges", []),
new NativeCall.WithFilterIdDecorator(), new NativeCall.CreateFilterDecorator(), null, "reqId", 1)
new NativeCall.WithFilterIdDecorator(), new NativeCall.CreateFilterDecorator(), null, false, "reqId", 1)
when:
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1))
@@ -650,12 +650,12 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall(multistreamHolder)
nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_) >> Mock(RpcReader) {
1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups))
1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups, null))
}
}
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,
new NativeCall.ParsedCallDetails("eth_getFilterChanges", []),
new NativeCall.WithFilterIdDecorator(), new NativeCall.CreateFilterDecorator(), null, "reqId", 1)
new NativeCall.WithFilterIdDecorator(), new NativeCall.CreateFilterDecorator(), null, false, "reqId", 1)
when:
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1))

View File

@@ -108,7 +108,7 @@ class ApiReaderMock implements Reader<JsonRpcRequest, JsonRpcResponse> {
}
error = new JsonRpcError(-32601, "Method ${request.method} with ${request.params} is not mocked")
}
return new JsonRpcResponse(result, error, JsonRpcResponse.Id.from(request.id), null, null)
return new JsonRpcResponse(result, error, JsonRpcResponse.Id.from(request.id), null, null, null)
} as Callable<JsonRpcResponse>
return Mono.fromCallable(call)
}

View File

@@ -54,7 +54,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver)
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null)
)
}
}
@@ -81,7 +81,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver, null
)
)
}
@@ -114,7 +114,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
)
)
}
@@ -146,7 +146,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getLogs", [Map.of("blockHash", hash1)])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes([json]), null, 1, resolver
Global.objectMapper.writeValueAsBytes([json]), null, 1, resolver, null
)
)
}
@@ -179,7 +179,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
)
)
}
@@ -212,7 +212,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
)
)
}
@@ -246,7 +246,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
)
)
}
@@ -271,7 +271,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver, null
)
)
}
@@ -301,7 +301,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver, null
)
)
}
@@ -332,7 +332,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "0xa8c9bb"])) >> Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver, null
)
)
}
@@ -361,7 +361,7 @@ class EthereumDirectReaderSpec extends Specification {
}
def result = Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver)
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null)
)
EthereumDirectReader ethereumDirectReader = new EthereumDirectReader(
Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
@@ -401,7 +401,7 @@ class EthereumDirectReaderSpec extends Specification {
}
def result = Mono.just(
new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver)
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null)
)
EthereumDirectReader ethereumDirectReader = new EthereumDirectReader(
Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()

View File

@@ -335,7 +335,7 @@ class GenericWsHeadSpec extends Specification {
1 * it.subscribe(_) >> new WsSubscriptions.SubscribeData(
Flux.error(new RuntimeException()), "id", new AtomicReference<String>(subId)
)
1 * it.unsubscribe(new JsonRpcRequest("eth_unsubscribe", List.of(subId), 2, null, null)) >>
1 * it.unsubscribe(new JsonRpcRequest("eth_unsubscribe", List.of(subId), 2, null, null, false)) >>
Mono.just(new JsonRpcResponse("".bytes, null))
}

View File

@@ -58,7 +58,7 @@ class WsConnectionImplSpec extends Specification {
when:
Flux.from(ws.handle(wsApiMock.inbound, wsApiMock.outbound)).subscribe()
def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], 15, null, null))
def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], 15, null, null, false))
then:
StepVerifier.create(act)
@@ -90,7 +90,7 @@ class WsConnectionImplSpec extends Specification {
when:
Flux.from(ws.handle(wsApiMock.inbound, wsApiMock.outbound)).subscribe()
def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], 15, null, null))
def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], 15, null, null, false))
then:
StepVerifier.create(act)
@@ -124,7 +124,7 @@ class WsConnectionImplSpec extends Specification {
when:
Flux.from(ws.handle(wsApiMock.inbound, wsApiMock.outbound)).subscribe()
def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], 15, null, null))
def act = ws.callRpc(new JsonRpcRequest("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], 15, null, null, false))
then:
StepVerifier.create(act)

View File

@@ -15,7 +15,7 @@
*/
package io.emeraldpay.dshackle.upstream.rpcclient
import io.emeraldpay.dshackle.config.AuthConfig
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.etherjar.rpc.RpcResponseError
import io.micrometer.core.instrument.Counter
@@ -23,9 +23,7 @@ import io.micrometer.core.instrument.Timer
import org.mockserver.integration.ClientAndServer
import org.mockserver.model.HttpRequest
import org.mockserver.model.HttpResponse
import org.mockserver.model.MediaType
import org.springframework.util.SocketUtils
import reactor.test.StepVerifier
import spock.lang.Specification
import java.time.Duration
@@ -69,35 +67,6 @@ class JsonRpcHttpClientSpec extends Specification {
new String(act.result) == '"0x98de45"'
}
def "Make request with basic auth"() {
setup:
def auth = new AuthConfig.ClientBasicAuth("user", "passwd")
def client = new JsonRpcHttpClient("localhost:${port}", metrics, auth, null)
mockServer.when(
HttpRequest.request()
.withMethod("POST")
.withBody("ping")
).respond(
HttpResponse.response()
.withBody("pong")
)
when:
def act = client.execute("ping".bytes).map { new String(it.t2) }
then:
StepVerifier.create(act)
.expectNext("pong")
.expectComplete()
.verify(Duration.ofSeconds(1))
mockServer.verify(
HttpRequest.request()
.withMethod("POST")
.withBody("ping")
.withContentType(MediaType.APPLICATION_JSON)
.withHeader("authorization", "Basic dXNlcjpwYXNzd2Q=")
)
}
def "Produces RPC Exception on error status code"() {
setup:
def client = new JsonRpcHttpClient("localhost:${port}", metrics, null, null)

View File

@@ -63,7 +63,7 @@ class JsonRpcResponseSpec extends Specification {
def "Serialize int id and null result"() {
setup:
def json = new JsonRpcResponse("null".bytes, null, new JsonRpcResponse.NumberId(1), null, null)
def json = new JsonRpcResponse("null".bytes, null, new JsonRpcResponse.NumberId(1), null, null, null)
when:
def act = objectMapper.writeValueAsString(json)
then:
@@ -72,7 +72,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)
def json = new JsonRpcResponse('"Hello World"'.bytes, null, new JsonRpcResponse.NumberId(10), null, null, null)
when:
def act = objectMapper.writeValueAsString(json)
then:
@@ -81,7 +81,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)
def json = new JsonRpcResponse('{"foo": "Hello World", "bar": 1}'.bytes, null, new JsonRpcResponse.NumberId(101), null, null, null)
when:
def act = objectMapper.writeValueAsString(json)
then:
@@ -90,7 +90,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)
def json = new JsonRpcResponse(null, new JsonRpcError(-32041, "Oooops"), new JsonRpcResponse.NumberId(101), null, null, null)
when:
def act = objectMapper.writeValueAsString(json)
then:
@@ -99,7 +99,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)
def json = new JsonRpcResponse("null".bytes, null, new JsonRpcResponse.StringId("asf01t1gg"), null, null, null)
when:
def act = objectMapper.writeValueAsString(json)
then:
@@ -108,7 +108,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)
def json = new JsonRpcResponse('"Hello World"'.bytes, null, new JsonRpcResponse.StringId("10"), null, null, null)
when:
def act = objectMapper.writeValueAsString(json)
then:
@@ -117,7 +117,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)
def json = new JsonRpcResponse('{"foo": "Hello World", "bar": 1}'.bytes, null, new JsonRpcResponse.StringId("g8gk19g"), null, null, null)
when:
def act = objectMapper.writeValueAsString(json)
then:
@@ -128,7 +128,7 @@ class JsonRpcResponseSpec extends Specification {
setup:
def json = new JsonRpcResponse(null,
new JsonRpcError(-32041, "Oooops"),
new JsonRpcResponse.StringId("9kbo29gkaasf"), null, null)
new JsonRpcResponse.StringId("9kbo29gkaasf"), null, null, null )
when:
def act = objectMapper.writeValueAsString(json)
then:

View File

@@ -1,175 +0,0 @@
package io.emeraldpay.dshackle.rpc
import com.fasterxml.jackson.databind.JsonNode
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallRequest
import io.emeraldpay.dshackle.Global
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.springframework.util.ResourceUtils
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import java.time.Duration
class NativeCallStreamTest {
private val upstreamId = "upstreamId"
private val mapper = Global.objectMapper
@Test
fun `streaming response is equal to the original response`() {
val responseFile = ResourceUtils.getFile("classpath:responses/get-by-number-response.json")
val response = mapper.writeValueAsBytes(mapper.readValue(responseFile, JsonNode::class.java))
val nativeCallResponse = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(1)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(response))
.build()
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(nativeCallResponse)
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setChunkSize(1000)
.build(),
)
val result = nativeCallStream.nativeCall(req)
.collectList()
.block()!!
.map { it.payload.toByteArray() }
.reduce { acc, bytes -> acc.plus(bytes) }
assertTrue(response.contentEquals(result))
}
@Test
fun `streaming responses is correct`() {
val response = "\"0x1126938\"".toByteArray()
val nativeCallResponse = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(15)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(response))
.build()
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(nativeCallResponse)
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setChunkSize(5)
.build(),
)
val chunkResponse: (Int) -> BlockchainOuterClass.NativeCallReplyItem.Builder = { id ->
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(id)
.setChunked(true)
.setSucceed(true)
.setUpstreamId(upstreamId)
}
val result = nativeCallStream.nativeCall(req)
StepVerifier.create(result)
.expectNext(
chunkResponse(15)
.setPayload(ByteString.copyFrom("\"0x11".toByteArray()))
.build(),
)
.expectNext(
chunkResponse(15)
.setPayload(ByteString.copyFrom("26938".toByteArray()))
.build(),
)
.expectNext(
chunkResponse(15)
.setFinalChunk(true)
.setPayload(ByteString.copyFrom("\"".toByteArray()))
.build(),
)
.expectComplete()
.verify(Duration.ofSeconds(3))
}
@Test
fun `no streaming if response is too small`() {
val response = "\"0x1\"".toByteArray()
val nativeCallResponse = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(15)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(response))
.build()
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(nativeCallResponse)
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setChunkSize(1000)
.build(),
)
val result = nativeCallStream.nativeCall(req)
StepVerifier.create(result)
.expectNext(
nativeCallResponse,
)
.expectComplete()
.verify(Duration.ofSeconds(3))
}
@Test
fun `sort responses by request id is correct`() {
val response = "\"0x1\"".toByteArray()
val response2 = "\"0x2\"".toByteArray()
val response3 = "\"0x3\"".toByteArray()
val nativeCallResponse: (Int, ByteArray) -> BlockchainOuterClass.NativeCallReplyItem = { id, resp ->
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(id)
.setChunked(true)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(resp))
.build()
}
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(
nativeCallResponse(1, response),
nativeCallResponse(2, response2),
nativeCallResponse(3, response3),
).flatMap {
when (it.id) {
1 -> Mono.just(it).delayElement(Duration.ofMillis(200))
2 -> Mono.just(it).delayElement(Duration.ofMillis(100))
else -> Mono.just(it)
}
}
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setSorted(true)
.build(),
)
val result = nativeCallStream.nativeCall(req)
StepVerifier.create(result)
.expectNextMatches { it.payload.toByteArray().contentEquals(response) }
.expectNextMatches { it.payload.toByteArray().contentEquals(response2) }
.expectNextMatches { it.payload.toByteArray().contentEquals(response3) }
.expectComplete()
.verify(Duration.ofSeconds(3))
}
}

View File

@@ -0,0 +1,131 @@
package io.emeraldpay.dshackle.upstream.rpcclient.stream
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
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
import reactor.core.publisher.Flux
import reactor.test.StepVerifier
import java.time.Duration
class JsonRpcStreamParserTest {
private val streamParser = JsonRpcStreamParser()
@Test
fun `if first part couldn't be parsed then aggregate response`() {
val statusCode = 200
val bytes = "{\"strangeResponse\": 2}".toByteArray()
val stream: Flux<ByteArray> = Flux.just(bytes)
StepVerifier.create(streamParser.streamParse(statusCode, stream))
.expectNext(AggregateResponse(bytes, statusCode))
.expectComplete()
.verify(Duration.ofSeconds(1))
}
@ParameterizedTest
@MethodSource("data")
fun `if first part has result field then single response`(
response: ByteArray,
result: ByteArray,
) {
val statusCode = 200
val stream: Flux<ByteArray> = Flux.just(response)
StepVerifier.create(streamParser.streamParse(statusCode, stream))
.expectNext(SingleResponse(result, null))
.expectComplete()
.verify(Duration.ofSeconds(1))
}
@ParameterizedTest
@MethodSource("dataStream")
fun `if big result then stream response`(
response: List<ByteArray>,
chunks: List<Chunk>,
) {
val statusCode = 200
val stream: Flux<ByteArray> = Flux.fromIterable(response)
val result = streamParser.streamParse(statusCode, stream).block()
assertTrue(result is StreamResponse)
assertNotNull(result)
StepVerifier.create((result as StreamResponse).stream)
.expectNextSequence(chunks)
.expectComplete()
.verify(Duration.ofSeconds(5))
}
companion object {
@JvmStatic
fun data(): List<Arguments> = listOf(
Arguments.of("{\"id\": 2,\"result\": \"0x12\"}".toByteArray(), "\"0x12\"".toByteArray()),
Arguments.of("{\"id\": 2,\"result\": 11}".toByteArray(), "11".toByteArray()),
Arguments.of("{\"id\": 2,\"result\": false}".toByteArray(), "false".toByteArray()),
Arguments.of("{\"id\": 2,\"result\": null}".toByteArray(), "null".toByteArray()),
Arguments.of("{\"id\": 2,\"result\": {\"name\": \"value\"}".toByteArray(), "{\"name\": \"value\"}".toByteArray()),
Arguments.of("{\"id\": 2,\"result\": [{\"name\": \"value\"}]".toByteArray(), "[{\"name\": \"value\"}]".toByteArray()),
)
@JvmStatic
fun dataStream(): List<Arguments> = listOf(
Arguments.of(
listOf("{\"id\": 2,\"result\": \"0x12".toByteArray(), "222\"}".toByteArray()),
listOf(
Chunk("\"0x12".toByteArray(), false),
Chunk("222\"".toByteArray(), true),
),
),
Arguments.of(
listOf(
"{\"id\": 2,\"result\": \"0x12".toByteArray(),
"123\\\"".toByteArray(),
"222\"}".toByteArray(),
),
listOf(
Chunk("\"0x12".toByteArray(), false),
Chunk("123\\\"".toByteArray(), false),
Chunk("222\"".toByteArray(), true),
),
),
Arguments.of(
listOf(
"{\"id\": 2,\"result\": \"0x12".toByteArray(),
"1\\n23\\\"".toByteArray(),
"456\\".toByteArray(),
"\\222\\\\\\\\\"}".toByteArray(),
),
listOf(
Chunk("\"0x12".toByteArray(), false),
Chunk("1\\n23\\\"".toByteArray(), false),
Chunk("456\\".toByteArray(), false),
Chunk("\\222\\\\\\\\\"".toByteArray(), true),
),
),
Arguments.of(
listOf("{\"id\": 2,\"result\": {\"name\": ".toByteArray(), "\"bigName\"".toByteArray(), "}".toByteArray()),
listOf(
Chunk("{\"name\": ".toByteArray(), false),
Chunk("\"bigName\"".toByteArray(), false),
Chunk("}".toByteArray(), true),
),
),
Arguments.of(
listOf(
"{\"id\": 2,\"result\": [{\"name\": ".toByteArray(),
"\"bigName\"".toByteArray(),
"}],".toByteArray(),
"\"field\": \"value\"}".toByteArray(),
),
listOf(
Chunk("[{\"name\": ".toByteArray(), false),
Chunk("\"bigName\"".toByteArray(), false),
Chunk("}]".toByteArray(), true),
),
),
)
}
}