solution: refactor and redesign API access, now with AggregateUpstream providing base direct/routed apis, plus actual chain upstreams providing specific readers
This commit is contained in:
@@ -63,6 +63,7 @@ class BlocksRedisCacheSpec extends Specification {
|
||||
Instant.ofEpochSecond(10501050),
|
||||
false,
|
||||
"test".bytes,
|
||||
null,
|
||||
[TxId.from(hash2), TxId.from(hash1)]
|
||||
)
|
||||
|
||||
|
||||
@@ -63,7 +63,8 @@ class TxRedisCacheSpec extends Specification {
|
||||
2000,
|
||||
TxId.from(hash1),
|
||||
BlockId.from(hash2),
|
||||
"test".bytes
|
||||
"test".bytes,
|
||||
null
|
||||
)
|
||||
when:
|
||||
def enc = cache.toProto(cont)
|
||||
|
||||
@@ -20,16 +20,16 @@ import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.quorum.BroadcastQuorum
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import spock.lang.Specification
|
||||
|
||||
class BroadcastQuorumSpec extends Specification {
|
||||
|
||||
def rpcConverted = TestingCommons.rpcConverter()
|
||||
def objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Resolved with first after 3 tries"() {
|
||||
setup:
|
||||
def q = Spy(new BroadcastQuorum(rpcConverted, 3))
|
||||
def q = Spy(new BroadcastQuorum(objectMapper, 3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
def upstream3 = Stub(Upstream)
|
||||
@@ -40,28 +40,28 @@ class BroadcastQuorumSpec extends Specification {
|
||||
!q.isResolved()
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"]), upstream1)
|
||||
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, upstream1)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"]), upstream2)
|
||||
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, upstream2)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([error: [message: "Nonce too low"]]), upstream3)
|
||||
q.record(new RpcException(1, "Nonce too low"), upstream3)
|
||||
then:
|
||||
1 * q.recordError(_, _, _)
|
||||
q.isResolved()
|
||||
objectMapper.readValue(q.result, Map) == [result: "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"]
|
||||
objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
|
||||
}
|
||||
|
||||
def "Remembers first response"() {
|
||||
setup:
|
||||
def q = Spy(new BroadcastQuorum(rpcConverted, 3))
|
||||
def q = Spy(new BroadcastQuorum(objectMapper, 3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
def upstream3 = Stub(Upstream)
|
||||
@@ -72,22 +72,22 @@ class BroadcastQuorumSpec extends Specification {
|
||||
!q.isResolved()
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([error: [message: "Internal error"]]), upstream1)
|
||||
q.record(new RpcException(1, "Internal error"), upstream1)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordError(_, _, _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"]), upstream2)
|
||||
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, upstream2)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([error: [message: "Nonce too low"]]), upstream3)
|
||||
q.record(new RpcException(1, "Nonce too low"), upstream3)
|
||||
then:
|
||||
1 * q.recordError(_, _, _)
|
||||
q.isResolved()
|
||||
objectMapper.readValue(q.result, Map) == [result: "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"]
|
||||
objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,16 @@ import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.quorum.NonceQuorum
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import spock.lang.Specification
|
||||
|
||||
class NonceQuorumSpec extends Specification {
|
||||
|
||||
def rpcConverted = TestingCommons.rpcConverter()
|
||||
def objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Gets max value"() {
|
||||
setup:
|
||||
def q = Spy(new NonceQuorum(rpcConverted, 3))
|
||||
def q = Spy(new NonceQuorum(objectMapper, 3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
def upstream3 = Stub(Upstream)
|
||||
@@ -40,28 +40,28 @@ class NonceQuorumSpec extends Specification {
|
||||
!q.isResolved()
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0x10"]), upstream1)
|
||||
q.record('"0x10"'.bytes, upstream1)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0x10", _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0x11"]), upstream2)
|
||||
q.record('"0x11"'.bytes, upstream2)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0x11", _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0x10"]), upstream3)
|
||||
q.record('"0x10"'.bytes, upstream3)
|
||||
then:
|
||||
1 * q.recordValue(_, "0x10", _)
|
||||
q.isResolved()
|
||||
objectMapper.readValue(q.result, Map) == [result: "0x11"]
|
||||
objectMapper.readValue(q.result, Object) == "0x11"
|
||||
}
|
||||
|
||||
def "Ignores errors"() {
|
||||
setup:
|
||||
def q = Spy(new NonceQuorum(rpcConverted, 3))
|
||||
def q = Spy(new NonceQuorum(objectMapper, 3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
def upstream3 = Stub(Upstream)
|
||||
@@ -72,28 +72,28 @@ class NonceQuorumSpec extends Specification {
|
||||
!q.isResolved()
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([error: [error: "Internal"]]), upstream1)
|
||||
q.record(new RpcException(1, "Internal"), upstream1)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordError(_, _, _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0x11"]), upstream2)
|
||||
q.record('"0x11"'.bytes, upstream2)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0x11", _)
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0x10"]), upstream3)
|
||||
q.record('"0x10"'.bytes, upstream3)
|
||||
then:
|
||||
1 * q.recordValue(_, "0x10", _)
|
||||
!q.isResolved()
|
||||
|
||||
when:
|
||||
q.record(objectMapper.writeValueAsBytes([result: "0x11"]), upstream1)
|
||||
q.record('"0x11"'.bytes, upstream1)
|
||||
then:
|
||||
1 * q.recordValue(_, "0x11", _)
|
||||
q.isResolved()
|
||||
objectMapper.readValue(q.result, Map) == [result: "0x11"]
|
||||
objectMapper.readValue(q.result, Object) == "0x11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.quorum
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.annotations.Nullable
|
||||
import spock.lang.Specification
|
||||
|
||||
class ValueAwareQuorumSpec extends Specification {
|
||||
|
||||
def "Extract null"() {
|
||||
setup:
|
||||
def quorum = new ValueAwareQuorumImpl()
|
||||
when:
|
||||
def act = quorum.extractValue("null".bytes, Object)
|
||||
then:
|
||||
act == null
|
||||
}
|
||||
|
||||
def "Extract string"() {
|
||||
setup:
|
||||
def quorum = new ValueAwareQuorumImpl()
|
||||
when:
|
||||
def act = quorum.extractValue("\"foo\"".bytes, Object)
|
||||
then:
|
||||
act == "foo"
|
||||
}
|
||||
|
||||
def "Extract number"() {
|
||||
setup:
|
||||
def quorum = new ValueAwareQuorumImpl()
|
||||
when:
|
||||
def act = quorum.extractValue("100".bytes, Object)
|
||||
then:
|
||||
act == 100
|
||||
}
|
||||
|
||||
def "Extract map"() {
|
||||
setup:
|
||||
def quorum = new ValueAwareQuorumImpl()
|
||||
when:
|
||||
def act = quorum.extractValue("{\"foo\": 1}".bytes, Object)
|
||||
then:
|
||||
act == [foo: 1]
|
||||
}
|
||||
|
||||
class ValueAwareQuorumImpl extends ValueAwareQuorum {
|
||||
ValueAwareQuorumImpl() {
|
||||
super(TestingCommons.objectMapper(), Object)
|
||||
}
|
||||
|
||||
@Override
|
||||
void recordValue(@NotNull byte[] response, @Nullable Object responseValue, @NotNull Upstream upstream) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
void recordError(@Nullable byte[] response, @Nullable String errorMessage, @NotNull Upstream upstream) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
void init(@NotNull Head head) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isResolved() {
|
||||
return false
|
||||
}
|
||||
|
||||
@Override
|
||||
byte[] getResult() {
|
||||
return new byte[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,6 @@ import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.dshackle.quorum.BroadcastQuorum
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
||||
import io.emeraldpay.dshackle.upstream.CachingEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.quorum.NonEmptyQuorum
|
||||
import io.emeraldpay.dshackle.upstream.Selector
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
@@ -31,9 +29,9 @@ import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.RpcResponseError
|
||||
import io.infinitape.etherjar.rpc.RpcResponseException
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Ignore
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
@@ -47,9 +45,7 @@ class NativeCallSpec extends Specification {
|
||||
setup:
|
||||
def quorum = Spy(new AlwaysQuorum())
|
||||
def upstreams = Stub(Upstreams)
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
apiMock.upstream = Stub(Upstream)
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
apiMock.answer("eth_test", [], "foo")
|
||||
|
||||
@@ -60,21 +56,19 @@ class NativeCallSpec extends Specification {
|
||||
|
||||
when:
|
||||
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(2))
|
||||
def act = objectMapper.readValue(resp.payload, Map)
|
||||
def act = objectMapper.readValue(resp.payload, Object)
|
||||
then:
|
||||
act == [jsonrpc:"2.0", id:1, result: "foo"]
|
||||
act == "foo"
|
||||
1 * quorum.record(_, _)
|
||||
1 * quorum.getResult()
|
||||
}
|
||||
|
||||
def "Quorum may return not first received value"() {
|
||||
setup:
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.rpcConverter(), 3))
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.objectMapper(), 3))
|
||||
|
||||
def upstreams = Stub(Upstreams)
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
apiMock.upstream = Stub(Upstream)
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
apiMock.answerOnce("eth_test", [], null)
|
||||
apiMock.answerOnce("eth_test", [], "bar")
|
||||
@@ -88,21 +82,19 @@ class NativeCallSpec extends Specification {
|
||||
|
||||
when:
|
||||
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(2))
|
||||
def act = objectMapper.readValue(resp.payload, Map)
|
||||
def act = objectMapper.readValue(resp.payload, Object)
|
||||
then:
|
||||
act == [jsonrpc:"2.0", id:1, result: "bar"]
|
||||
act == "bar"
|
||||
2 * quorum.record(_, _)
|
||||
1 * quorum.getResult()
|
||||
}
|
||||
|
||||
def "Have pause between repeats"() {
|
||||
setup:
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.rpcConverter(), 3))
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.objectMapper(), 3))
|
||||
|
||||
def upstreams = Stub(Upstreams)
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
apiMock.upstream = Stub(Upstream)
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
apiMock.answerOnce("eth_test", [], null)
|
||||
apiMock.answerOnce("eth_test", [], "bar")
|
||||
@@ -117,19 +109,19 @@ class NativeCallSpec extends Specification {
|
||||
def t1 = System.currentTimeMillis()
|
||||
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(2))
|
||||
def delta = System.currentTimeMillis() - t1
|
||||
def act = objectMapper.readValue(resp.payload, Object)
|
||||
then:
|
||||
delta > 95 // should be 100, but sometimes gives less ???
|
||||
new String(resp.payload) == '{"jsonrpc":"2.0","id":1,"result":"bar"}'
|
||||
act == "bar"
|
||||
}
|
||||
|
||||
def "One call has no pause"() {
|
||||
setup:
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.rpcConverter(), 3))
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.objectMapper(), 3))
|
||||
|
||||
def upstreams = Stub(Upstreams)
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
apiMock.upstream = Stub(Upstream)
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
apiMock.answerOnce("eth_test", [], "bar")
|
||||
|
||||
@@ -149,12 +141,11 @@ class NativeCallSpec extends Specification {
|
||||
|
||||
def "Returns error if no quorum"() {
|
||||
setup:
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.rpcConverter(), 3))
|
||||
def quorum = Spy(new NonEmptyQuorum(TestingCommons.objectMapper(), 3))
|
||||
|
||||
def upstreams = Stub(Upstreams)
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
apiMock.upstream = Stub(Upstream)
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
apiMock.answer("eth_test", [], null, 3)
|
||||
apiMock.answerOnce("eth_test", [], "foo")
|
||||
@@ -215,7 +206,7 @@ class NativeCallSpec extends Specification {
|
||||
|
||||
when:
|
||||
def resp = nativeCall.buildResponse(
|
||||
new NativeCall.CallContext<byte[]>(1561, TestingCommons.aggregatedUpstream(Stub(DirectEthereumApi)), Selector.empty, new AlwaysQuorum(), objectMapper.writeValueAsBytes(json))
|
||||
new NativeCall.CallContext<byte[]>(1561, TestingCommons.aggregatedUpstream(TestingCommons.api()), Selector.empty, new AlwaysQuorum(), objectMapper.writeValueAsBytes(json))
|
||||
)
|
||||
then:
|
||||
resp.id == 1561
|
||||
@@ -270,14 +261,14 @@ class NativeCallSpec extends Specification {
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
@Ignore
|
||||
//TODO
|
||||
def "Calls cache before remote"() {
|
||||
setup:
|
||||
def upstreams = Stub(Upstreams)
|
||||
def nativeCall = new NativeCall(upstreams, TestingCommons.objectMapper())
|
||||
def api = Mock(DirectEthereumApi)
|
||||
def api = TestingCommons.api()
|
||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||
def cacheMock = Mock(CachingEthereumApi)
|
||||
upstream.cache = cacheMock
|
||||
|
||||
def ctx = new NativeCall.CallContext<NativeCall.ParsedCallDetails>(10,
|
||||
upstream,
|
||||
@@ -289,13 +280,13 @@ class NativeCallSpec extends Specification {
|
||||
1 * cacheMock.execute(10, "eth_test", []) >> Mono.empty()
|
||||
}
|
||||
|
||||
@Ignore
|
||||
//TODO
|
||||
def "Uses cached value"() {
|
||||
setup:
|
||||
def upstreams = Stub(Upstreams)
|
||||
def nativeCall = new NativeCall(upstreams, TestingCommons.objectMapper())
|
||||
def upstream = TestingCommons.aggregatedUpstream(Stub(DirectEthereumApi))
|
||||
def cacheMock = Mock(CachingEthereumApi)
|
||||
upstream.cache = cacheMock
|
||||
def upstream = TestingCommons.aggregatedUpstream(TestingCommons.api())
|
||||
|
||||
def ctx = new NativeCall.CallContext<NativeCall.ParsedCallDetails>(10,
|
||||
upstream,
|
||||
@@ -313,9 +304,7 @@ class NativeCallSpec extends Specification {
|
||||
def quorum = Spy(new AlwaysQuorum())
|
||||
|
||||
def upstreams = Stub(Upstreams)
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
apiMock.upstream = Stub(Upstream)
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
apiMock.answer("eth_test", [], null, 1, new TimeoutException("test 1"))
|
||||
apiMock.answer("eth_test", [], null, 1, new TimeoutException("test 2"))
|
||||
@@ -329,21 +318,19 @@ class NativeCallSpec extends Specification {
|
||||
|
||||
when:
|
||||
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(2))
|
||||
def act = objectMapper.readValue(resp.payload, Map)
|
||||
def act = objectMapper.readValue(resp.payload, Object)
|
||||
then:
|
||||
act == [jsonrpc:"2.0", id:1, result: "bar"]
|
||||
act == "bar"
|
||||
1 * quorum.record(_, _)
|
||||
1 * quorum.getResult()
|
||||
}
|
||||
|
||||
def "Send raw retries 3 times"() {
|
||||
setup:
|
||||
def quorum = Spy(new BroadcastQuorum(TestingCommons.rpcConverter(), 3))
|
||||
def quorum = Spy(new BroadcastQuorum(TestingCommons.objectMapper(), 3))
|
||||
|
||||
def upstreams = Stub(Upstreams)
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
apiMock.upstream = Stub(Upstream)
|
||||
def apiMock = TestingCommons.api()
|
||||
|
||||
apiMock.answer("eth_sendRawTransaction", ["0x1234"],
|
||||
"0x4b66b555df9faed6f0711f2104d183736c8e2dc7434626dd2622e243f041d41b", 1)
|
||||
@@ -360,9 +347,9 @@ class NativeCallSpec extends Specification {
|
||||
|
||||
when:
|
||||
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(2))
|
||||
def act = objectMapper.readValue(resp.payload, Map)
|
||||
def act = objectMapper.readValue(resp.payload, Object)
|
||||
then:
|
||||
act == [jsonrpc:"2.0", id:1, result: "0x4b66b555df9faed6f0711f2104d183736c8e2dc7434626dd2622e243f041d41b"]
|
||||
act == "0x4b66b555df9faed6f0711f2104d183736c8e2dc7434626dd2622e243f041d41b"
|
||||
1 * quorum.record(_ as byte[], _)
|
||||
2 * quorum.record(_ as RpcException, _)
|
||||
}
|
||||
|
||||
@@ -24,12 +24,9 @@ import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import reactor.core.publisher.Mono
|
||||
@@ -80,7 +77,7 @@ class StreamHeadSpec extends Specification {
|
||||
.build()
|
||||
}
|
||||
|
||||
def upstream = new EthereumUpstreamMock(Chain.ETHEREUM, Stub(DirectEthereumApi.class))
|
||||
def upstream = new EthereumUpstreamMock(Chain.ETHEREUM, TestingCommons.api())
|
||||
def upstreams = new UpstreamsMock(Chain.ETHEREUM, upstream)
|
||||
def streamHead = new StreamHead(upstreams)
|
||||
when:
|
||||
|
||||
@@ -19,12 +19,18 @@ import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.test.ReaderMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinChainUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinReader
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
@@ -216,24 +222,24 @@ class TrackBitcoinAddressSpec extends Specification {
|
||||
def "Get update for a balance"() {
|
||||
setup:
|
||||
|
||||
DirectBitcoinApi api = Mock(DirectBitcoinApi) {
|
||||
2 * executeAndResult(0, "listunspent", [], List) >>> [
|
||||
Mono.just([]), Mono.just([[address: "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", amount: 0.0123]])
|
||||
]
|
||||
}
|
||||
def blocks = TopicProcessor.create()
|
||||
Head head = Mock(Head) {
|
||||
1 * getFlux() >> Flux.from(blocks)
|
||||
}
|
||||
Upstream upstream
|
||||
upstream = Mock(AggregatedUpstream) {
|
||||
_ * getApi(_) >> Mono.just(api)
|
||||
def upstream = null
|
||||
upstream = Mock(BitcoinChainUpstreams) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
2 * listUnspent() >>> [
|
||||
Mono.just([]),
|
||||
Mono.just([[address: "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", amount: 0.0123]])
|
||||
]
|
||||
}
|
||||
_ * getHead() >> head
|
||||
_ * castApi(_) >> { return upstream }
|
||||
}
|
||||
Upstreams upstreams = Mock(Upstreams) {
|
||||
_ * getUpstream(Chain.BITCOIN) >> upstream
|
||||
_ * cast(_) >> {
|
||||
upstream
|
||||
}
|
||||
}
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.BITCOIN, upstream)
|
||||
TrackBitcoinAddress track = new TrackBitcoinAddress(upstreams)
|
||||
|
||||
when:
|
||||
@@ -253,7 +259,7 @@ class TrackBitcoinAddressSpec extends Specification {
|
||||
StepVerifier.create(resp)
|
||||
.expectNext("0")
|
||||
.then {
|
||||
blocks.onNext(new BlockContainer(1L, BlockId.from(hash1), BigInteger.ONE, Instant.now(), false, null, []))
|
||||
blocks.onNext(new BlockContainer(1L, BlockId.from(hash1), BigInteger.ONE, Instant.now(), false, null, null, []))
|
||||
}
|
||||
.expectNext("1230000")
|
||||
.then {
|
||||
|
||||
@@ -17,11 +17,12 @@ package io.emeraldpay.dshackle.rpc
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinChainUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinReader
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.CachingMempoolData
|
||||
import io.emeraldpay.grpc.Chain
|
||||
@@ -45,8 +46,8 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
"d296c6d47335a7f283574b06f1d6303b30ac75631e081ab128346a549ad93350"
|
||||
])
|
||||
}
|
||||
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
||||
_ * getData() >> Mock(BitcoinReader) {
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
_ * getMempool() >> mempoolAccess
|
||||
}
|
||||
}
|
||||
@@ -71,8 +72,8 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
"d296c6d47335a7f283574b06f1d6303b30ac75631e081ab128346a549ad93350"
|
||||
])
|
||||
}
|
||||
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
||||
_ * getData() >> Mock(BitcoinReader) {
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
_ * getMempool() >> mempoolAccess
|
||||
}
|
||||
}
|
||||
@@ -92,13 +93,15 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
DirectBitcoinApi api = Mock(DirectBitcoinApi) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid: txid
|
||||
])
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid: txid
|
||||
])
|
||||
}
|
||||
}
|
||||
when:
|
||||
def act = track.loadExisting(api, txid)
|
||||
def act = track.loadExisting(upstream, txid)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
@@ -113,15 +116,17 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
DirectBitcoinApi api = Mock(DirectBitcoinApi) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid : txid,
|
||||
blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f",
|
||||
height : 100
|
||||
])
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
_ * getReader() >> Mock(BitcoinReader) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid : txid,
|
||||
blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f",
|
||||
height : 100
|
||||
])
|
||||
}
|
||||
}
|
||||
when:
|
||||
def act = track.loadExisting(api, txid)
|
||||
def act = track.loadExisting(upstream, txid)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
@@ -140,12 +145,12 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
// start with the current block
|
||||
def next = Flux.fromIterable([10, 12, 13, 14, 15]).map { h ->
|
||||
new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, [])
|
||||
new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, [])
|
||||
}
|
||||
Head head = Mock(Head) {
|
||||
1 * getFlux() >> next
|
||||
}
|
||||
Upstream upstream = Mock(BitcoinUpstream) {
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
1 * getHead() >> head
|
||||
}
|
||||
def status = new TrackBitcoinTx.TxStatus(
|
||||
@@ -171,12 +176,12 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
// start with the current block
|
||||
def next = Flux.fromIterable([10, 12, 13]).map { h ->
|
||||
new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, [])
|
||||
new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, [])
|
||||
}
|
||||
Head head = Mock(Head) {
|
||||
1 * getFlux() >> next
|
||||
}
|
||||
DirectBitcoinApi api = Mock(DirectBitcoinApi) {
|
||||
BitcoinReader api = Mock(BitcoinReader) {
|
||||
3 * getTx(txid) >>> [
|
||||
Mono.just([
|
||||
txid: txid
|
||||
@@ -191,9 +196,9 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
])
|
||||
]
|
||||
}
|
||||
Upstream upstream = Mock(BitcoinUpstream) {
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
1 * getHead() >> head
|
||||
_ * getApi(_) >> Mono.just(api)
|
||||
_ * getReader() >> api
|
||||
}
|
||||
def status = new TrackBitcoinTx.TxStatus(
|
||||
txid, false, null, false, null, null, null, 0
|
||||
@@ -212,11 +217,7 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
DirectBitcoinApi api = Mock(DirectBitcoinApi) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid: txid
|
||||
])
|
||||
}
|
||||
|
||||
Head head = Mock(Head) {
|
||||
_ * getFlux() >> Flux.empty()
|
||||
}
|
||||
@@ -228,17 +229,20 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
Mono.just(["4523c7ac0c5c1e5628f025474529c69cd44d7c641db82e6982f5ffe64527efc9", txid]) //second call when started over
|
||||
]
|
||||
}
|
||||
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
||||
_ * getApi(_) >> Mono.just(api)
|
||||
BitcoinReader api = Mock(BitcoinReader) {
|
||||
1 * getTx(txid) >> Mono.just([
|
||||
txid: txid
|
||||
])
|
||||
_ * getMempool() >> mempoolAccess
|
||||
}
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
_ * getHead() >> head
|
||||
_ * getData() >> Mock(BitcoinReader) {
|
||||
_ * getMempool() >> mempoolAccess
|
||||
}
|
||||
_ * getReader() >> api
|
||||
}
|
||||
|
||||
when:
|
||||
def steps = StepVerifier.withVirtualTime {
|
||||
track.untilFound(Chain.BITCOIN, api, upstream, txid).take(1)
|
||||
track.untilFound(Chain.BITCOIN, upstream, txid).take(1)
|
||||
}
|
||||
|
||||
then:
|
||||
@@ -254,7 +258,7 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
setup:
|
||||
TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams))
|
||||
def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9"
|
||||
DirectBitcoinApi api = Mock(DirectBitcoinApi) {
|
||||
BitcoinReader api = Mock(BitcoinReader) {
|
||||
_ * getTx(txid) >> Mono.just([
|
||||
txid : txid,
|
||||
blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f",
|
||||
@@ -267,18 +271,18 @@ class TrackBitcoinTxSpec extends Specification {
|
||||
])
|
||||
}
|
||||
def next = Flux.fromIterable([10, 11, 12]).map { h ->
|
||||
new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, [])
|
||||
new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, null, [])
|
||||
}
|
||||
Head head = Mock(Head) {
|
||||
_ * getFlux() >> next
|
||||
}
|
||||
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
||||
_ * getApi(_) >> Mono.just(api)
|
||||
BitcoinChainUpstreams upstream = Mock(BitcoinChainUpstreams) {
|
||||
_ * getReader() >> api
|
||||
_ * getHead() >> head
|
||||
}
|
||||
|
||||
when:
|
||||
def act = track.subscribe(Chain.BITCOIN, api, upstream, txid)
|
||||
def act = track.subscribe(Chain.BITCOIN, upstream, txid)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
|
||||
@@ -64,7 +64,7 @@ class TrackEthereumAddressSpec extends Specification {
|
||||
.setBalance("1234567890")
|
||||
.build()
|
||||
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackEthereumAddress trackAddress = new TrackEthereumAddress(upstreams)
|
||||
@@ -104,7 +104,7 @@ class TrackEthereumAddressSpec extends Specification {
|
||||
return it
|
||||
}
|
||||
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackEthereumAddress trackAddress = new TrackEthereumAddress(upstreams)
|
||||
|
||||
@@ -26,12 +26,10 @@ import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
@@ -98,7 +96,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
.setTimestamp(blockJson.timestamp.toEpochMilli())
|
||||
).build()
|
||||
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
||||
@@ -118,10 +116,10 @@ class TrackEthereumTxSpec extends Specification {
|
||||
|
||||
def "Wait for unknown transaction"() {
|
||||
setup:
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
((AggregatedEthereumUpstreams) upstreams.getUpstream(Chain.ETHEREUM)).head = Mock(Head) {
|
||||
((EthereumChainUpstream) upstreams.getUpstream(Chain.ETHEREUM)).head = Mock(Head) {
|
||||
_ * getFlux() >> Flux.empty()
|
||||
}
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
||||
@@ -133,7 +131,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
when:
|
||||
def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM, Instant.now(), TransactionId.from(txId), 6)
|
||||
def act = StepVerifier.withVirtualTime(
|
||||
{ trackTx.subscribe(tx, upstreams.getUpstream(Chain.ETHEREUM).castApi(EthereumApi.class)) },
|
||||
{ trackTx.subscribe(tx, upstreams.getUpstream(Chain.ETHEREUM).cast(EthereumChainUpstream)) },
|
||||
{ scheduler },
|
||||
5)
|
||||
|
||||
@@ -168,7 +166,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
it
|
||||
}
|
||||
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
||||
@@ -193,14 +191,14 @@ class TrackEthereumTxSpec extends Specification {
|
||||
|
||||
def "New block makes tx mined"() {
|
||||
setup:
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
||||
|
||||
def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM, Instant.now(), TransactionId.from(txId), 6)
|
||||
def block = new BlockContainer(
|
||||
100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes,
|
||||
100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes, null,
|
||||
[TxId.from(txId)]
|
||||
)
|
||||
|
||||
@@ -215,14 +213,14 @@ class TrackEthereumTxSpec extends Specification {
|
||||
|
||||
def "New block without current tx requires a call"() {
|
||||
setup:
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
||||
|
||||
def tx = new TrackEthereumTx.TxDetails(Chain.ETHEREUM, Instant.now(), TransactionId.from(txId), 6)
|
||||
def block = new BlockContainer(
|
||||
100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes,
|
||||
100, BlockId.from(txId), BigInteger.ONE, Instant.now(), false, "".bytes, null,
|
||||
[TxId.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")]
|
||||
)
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], null)
|
||||
@@ -288,7 +286,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
)
|
||||
|
||||
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
||||
|
||||
@@ -19,11 +19,10 @@ package io.emeraldpay.dshackle.test
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.grpc.stub.StreamObserver
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import io.infinitape.etherjar.rpc.RpcResponseError
|
||||
import io.infinitape.etherjar.rpc.json.ResponseJson
|
||||
import org.jetbrains.annotations.NotNull
|
||||
@@ -31,16 +30,18 @@ import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
class EthereumApiMock extends DirectEthereumApi {
|
||||
class EthereumApiMock implements Reader<JsonRpcRequest, JsonRpcResponse> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(this)
|
||||
List<PredefinedResponse> predefined = []
|
||||
private ObjectMapper objectMapper
|
||||
|
||||
EthereumApiMock(@NotNull ReactorRpcClient rpcClient, @NotNull ObjectMapper objectMapper, @NotNull Chain chain) {
|
||||
super(rpcClient, null, objectMapper, new DirectCallMethods())
|
||||
String id = "default"
|
||||
|
||||
EthereumApiMock(@NotNull ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper
|
||||
}
|
||||
|
||||
@@ -55,10 +56,11 @@ class EthereumApiMock extends DirectEthereumApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
Mono<byte[]> execute(int id, @NotNull String method, @NotNull List<?> params) {
|
||||
Callable<byte[]> call = {
|
||||
def predefined = predefined.find { it.isSame(id, method, params) }
|
||||
ResponseJson json = new ResponseJson<Object, Integer>(id: id)
|
||||
Mono<JsonRpcResponse> read(JsonRpcRequest request) {
|
||||
Callable<JsonRpcResponse> call = {
|
||||
def predefined = predefined.find { it.isSame(request.method, request.params) }
|
||||
byte[] result = null
|
||||
JsonRpcResponse.ResponseError error = null
|
||||
if (predefined != null) {
|
||||
if (predefined.exception != null) {
|
||||
predefined.onCalled()
|
||||
@@ -66,32 +68,37 @@ class EthereumApiMock extends DirectEthereumApi {
|
||||
throw predefined.exception
|
||||
}
|
||||
if (predefined.result instanceof RpcResponseError) {
|
||||
json.error = predefined.result
|
||||
((RpcResponseError) predefined.result).with { err ->
|
||||
error = new JsonRpcResponse.ResponseError(err.code, err.message)
|
||||
}
|
||||
} else {
|
||||
json.result = predefined.result
|
||||
// ResponseJson json = new ResponseJson<Object, Integer>(id: 1, result: predefined.result)
|
||||
result = objectMapper.writeValueAsBytes(predefined.result)
|
||||
}
|
||||
predefined.onCalled()
|
||||
predefined.print()
|
||||
} else {
|
||||
log.error("Method ${method} with ${params} is not mocked")
|
||||
json.error = new RpcResponseError(-32601, "Method ${method} with ${params} is not mocked")
|
||||
log.error("Method ${request.method} with ${request.params} is not mocked")
|
||||
error = new JsonRpcResponse.ResponseError(-32601, "Method ${request.method} with ${request.params} is not mocked")
|
||||
}
|
||||
byte[] result = objectMapper.writeValueAsBytes(json)
|
||||
return result
|
||||
} as Callable<byte[]>
|
||||
return new JsonRpcResponse(result, error)
|
||||
} as Callable<JsonRpcResponse>
|
||||
return Mono.fromCallable(call)
|
||||
}
|
||||
|
||||
def nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
|
||||
request.itemsList.forEach { req ->
|
||||
def resp = execute(req.id, req.method, objectMapper.readerFor(List).readValue(req.payload.toByteArray()))
|
||||
resp.subscribe {
|
||||
def proto = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(req.id)
|
||||
.setSucceed(true)
|
||||
.setPayload(ByteString.copyFrom(resp.block()))
|
||||
responseObserver.onNext(proto.build())
|
||||
JsonRpcResponse resp = read(new JsonRpcRequest(req.method, objectMapper.readerFor(List).readValue(req.payload.toByteArray())))
|
||||
.block(Duration.ofSeconds(5))
|
||||
def proto = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
|
||||
.setId(req.id)
|
||||
.setSucceed(resp.hasResult())
|
||||
.setPayload(ByteString.copyFrom(resp.getResult()))
|
||||
|
||||
resp.error?.with { err ->
|
||||
proto.setErrorMessage(err.message)
|
||||
}
|
||||
responseObserver.onNext(proto.build())
|
||||
}
|
||||
responseObserver.onCompleted()
|
||||
}
|
||||
@@ -103,7 +110,7 @@ class EthereumApiMock extends DirectEthereumApi {
|
||||
Integer limit
|
||||
Throwable exception
|
||||
|
||||
boolean isSame(int id, String method, List<?> params) {
|
||||
boolean isSame(String method, List<?> params) {
|
||||
if (limit != null) {
|
||||
if (limit <= 0) {
|
||||
return false
|
||||
|
||||
@@ -16,28 +16,20 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.test
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.infinitape.etherjar.rpc.ReactorBatch
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import io.infinitape.etherjar.rpc.RpcCall
|
||||
import io.infinitape.etherjar.rpc.RpcCallResponse
|
||||
import reactor.core.publisher.Flux
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class EthereumApiStub extends DirectEthereumApi {
|
||||
class EthereumApiStub implements Reader<JsonRpcRequest, JsonRpcResponse> {
|
||||
|
||||
private String id
|
||||
private static ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
private static ReactorRpcClient rpcClient = new RpcClientMock();
|
||||
|
||||
EthereumApiStub(Integer id) {
|
||||
this(id.toString())
|
||||
}
|
||||
|
||||
EthereumApiStub(String id) {
|
||||
super(rpcClient, null, objectMapper, new DirectCallMethods())
|
||||
this.id = id
|
||||
}
|
||||
|
||||
@@ -46,16 +38,9 @@ class EthereumApiStub extends DirectEthereumApi {
|
||||
return "API Stub $id"
|
||||
}
|
||||
|
||||
static class RpcClientMock implements ReactorRpcClient {
|
||||
|
||||
@Override
|
||||
Flux<RpcCallResponse> execute(ReactorBatch batch) {
|
||||
return Flux.error(new Exception("Not implemented in mock"))
|
||||
}
|
||||
|
||||
@Override
|
||||
def <JS, RES> Mono<RES> execute(RpcCall<JS, RES> call) {
|
||||
return Mono.error(new Exception("Not implemented in mock"))
|
||||
}
|
||||
@Override
|
||||
Mono<JsonRpcResponse> read(JsonRpcRequest key) {
|
||||
return Mono.error(new Exception("Not implemented in mock"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,18 +16,19 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.test
|
||||
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.reactivestreams.Publisher
|
||||
|
||||
@@ -35,19 +36,19 @@ class EthereumUpstreamMock extends EthereumUpstream {
|
||||
|
||||
EthereumHeadMock ethereumHeadMock = new EthereumHeadMock()
|
||||
|
||||
EthereumUpstreamMock(@NotNull Chain chain, @NotNull DirectEthereumApi api) {
|
||||
EthereumUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||
this(chain, api, new DefaultEthereumMethods(TestingCommons.objectMapper(), chain))
|
||||
}
|
||||
|
||||
EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull DirectEthereumApi api) {
|
||||
EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||
this(id, chain, api, new DefaultEthereumMethods(TestingCommons.objectMapper(), chain))
|
||||
}
|
||||
|
||||
EthereumUpstreamMock(@NotNull Chain chain, @NotNull DirectEthereumApi api, CallMethods methods) {
|
||||
EthereumUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
||||
this("test", chain, api, methods)
|
||||
}
|
||||
|
||||
EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull DirectEthereumApi api, CallMethods methods) {
|
||||
EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
||||
super(id, chain, api, null,
|
||||
UpstreamsConfig.Options.getDefaults(), new QuorumForLabels.QuorumItem(1, new UpstreamsConfig.Labels()),
|
||||
methods, TestingCommons.objectMapper())
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.test
|
||||
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class ReaderMock<K, D> implements Reader<K, D> {
|
||||
|
||||
private Map<K, D> mapping = new HashMap<K, D>()
|
||||
|
||||
ReaderMock() {
|
||||
}
|
||||
|
||||
ReaderMock with(K key, D data) {
|
||||
mapping[key] = data
|
||||
return this
|
||||
}
|
||||
|
||||
@Override
|
||||
Mono<D> read(K key) {
|
||||
return Mono.justOrEmpty(mapping.get(key))
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,15 @@ import io.emeraldpay.dshackle.FileResolver
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.CachesFactory
|
||||
import io.emeraldpay.dshackle.config.CacheConfig
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.JacksonRpcConverter
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@@ -50,32 +51,32 @@ class TestingCommons {
|
||||
return objectMapper
|
||||
}
|
||||
|
||||
static EthereumApiMock api(ReactorRpcClient rpcClient) {
|
||||
return new EthereumApiMock(rpcClient, objectMapper(), Chain.ETHEREUM)
|
||||
static EthereumApiMock api() {
|
||||
return new EthereumApiMock(objectMapper())
|
||||
}
|
||||
|
||||
static JacksonRpcConverter rpcConverter() {
|
||||
return new JacksonRpcConverter(objectMapper())
|
||||
}
|
||||
|
||||
static EthereumUpstreamMock upstream(DirectEthereumApi api) {
|
||||
static EthereumUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||
return new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
||||
}
|
||||
|
||||
static EthereumUpstreamMock upstream(DirectEthereumApi api, String method) {
|
||||
static EthereumUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, String method) {
|
||||
return upstream(api, [method])
|
||||
}
|
||||
|
||||
static EthereumUpstreamMock upstream(DirectEthereumApi api, List<String> methods) {
|
||||
static EthereumUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, List<String> methods) {
|
||||
return new EthereumUpstreamMock(Chain.ETHEREUM, api, new DirectCallMethods(methods))
|
||||
}
|
||||
|
||||
static AggregatedUpstream aggregatedUpstream(DirectEthereumApi api) {
|
||||
static AggregatedUpstream aggregatedUpstream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||
return aggregatedUpstream(upstream(api))
|
||||
}
|
||||
|
||||
static AggregatedUpstream aggregatedUpstream(EthereumUpstream up) {
|
||||
return new AggregatedEthereumUpstreams(Chain.ETHEREUM, [up], Caches.default(objectMapper()), objectMapper())
|
||||
return new EthereumChainUpstream(Chain.ETHEREUM, [up], Caches.default(objectMapper()), objectMapper())
|
||||
}
|
||||
|
||||
static CachesFactory emptyCaches() {
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
package io.emeraldpay.dshackle.test
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinChainUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumReader
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
@@ -32,30 +35,39 @@ import reactor.core.publisher.Flux
|
||||
class UpstreamsMock implements Upstreams {
|
||||
|
||||
private Map<Chain, DefaultEthereumMethods> target = [:]
|
||||
private Map<Chain, AggregatedEthereumUpstreamsMock> upstreams = [:]
|
||||
private Map<Chain, AggregatedUpstream> upstreams = [:]
|
||||
|
||||
UpstreamsMock(Chain chain, Upstream up) {
|
||||
addUpstream(chain, up)
|
||||
}
|
||||
UpstreamsMock(Chain chain1, Upstream up1, Chain chain2, Upstream up2) {
|
||||
addUpstream(chain1, up1)
|
||||
addUpstream(chain2, up2)
|
||||
}
|
||||
|
||||
AggregatedUpstream addUpstream(@NotNull Chain chain, @NotNull EthereumUpstream up) {
|
||||
AggregatedUpstream addUpstream(@NotNull Chain chain, @NotNull Upstream up) {
|
||||
if (!upstreams.containsKey(chain)) {
|
||||
upstreams[chain] = new AggregatedEthereumUpstreamsMock(chain, [up], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
upstreams[chain].start()
|
||||
if (BlockchainType.fromBlockchain(chain) == BlockchainType.ETHEREUM) {
|
||||
if (up instanceof EthereumChainUpstream) {
|
||||
upstreams[chain] = up
|
||||
} else if (up instanceof EthereumUpstream) {
|
||||
upstreams[chain] = new EthereumChainUpstreamMock(chain, [up as EthereumUpstream], Caches.default(TestingCommons.objectMapper()))
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported upstream type ${up.class}")
|
||||
}
|
||||
upstreams[chain].start()
|
||||
} else if (BlockchainType.fromBlockchain(chain) == BlockchainType.BITCOIN) {
|
||||
if (up instanceof BitcoinChainUpstreams) {
|
||||
upstreams[chain] = up
|
||||
} else if (up instanceof BitcoinUpstream) {
|
||||
upstreams[chain] = new BitcoinChainUpstreams(chain, [up as BitcoinUpstream], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported upstream type ${up.class}")
|
||||
}
|
||||
upstreams[chain].start()
|
||||
}
|
||||
} else {
|
||||
upstreams[chain].addUpstream(up)
|
||||
}
|
||||
return upstreams[chain]
|
||||
}
|
||||
|
||||
void setReader(@NotNull Chain chain, EthereumReader reader) {
|
||||
upstreams[chain].customReader = reader
|
||||
}
|
||||
|
||||
@Override
|
||||
AggregatedUpstream getUpstream(@NotNull Chain chain) {
|
||||
return upstreams[chain]
|
||||
@@ -85,12 +97,12 @@ class UpstreamsMock implements Upstreams {
|
||||
return upstreams.containsKey(chain)
|
||||
}
|
||||
|
||||
static class AggregatedEthereumUpstreamsMock extends AggregatedEthereumUpstreams {
|
||||
static class EthereumChainUpstreamMock extends EthereumChainUpstream {
|
||||
|
||||
EthereumReader customReader = null
|
||||
|
||||
AggregatedEthereumUpstreamsMock(@NotNull Chain chain, @NotNull List<EthereumUpstream> upstreams, @NotNull Caches caches, @NotNull ObjectMapper objectMapper) {
|
||||
super(chain, upstreams, caches, objectMapper)
|
||||
EthereumChainUpstreamMock(@NotNull Chain chain, @NotNull List<EthereumUpstream> upstreams, @NotNull Caches caches) {
|
||||
super(chain, upstreams, caches, TestingCommons.objectMapper())
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,8 +21,7 @@ import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
||||
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -30,9 +29,9 @@ class AggregatedUpstreamSpec extends Specification {
|
||||
|
||||
def "Aggregates methods"() {
|
||||
setup:
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||
def up2 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
||||
def aggr = new AggregatedEthereumUpstreams(Chain.ETHEREUM, [up1, up2], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||
def up2 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
||||
def aggr = new EthereumChainUpstream(Chain.ETHEREUM, [up1, up2], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
when:
|
||||
aggr.onUpstreamsUpdated()
|
||||
def act = aggr.getMethods()
|
||||
|
||||
@@ -1,261 +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
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.BlockByHeight
|
||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.HeightCache
|
||||
import io.emeraldpay.dshackle.cache.TxMemCache
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class CachingEthereumApiSpec extends Specification {
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Get blockNumber from head"() {
|
||||
setup:
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.default(objectMapper),
|
||||
head
|
||||
)
|
||||
1 * head.getFlux() >> Flux.just(BlockContainer.from(
|
||||
new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
difficulty: 1,
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
),
|
||||
objectMapper
|
||||
))
|
||||
when:
|
||||
def act = api.execute(1, "eth_blockNumber", []).map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":"0x64"}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
def "Return empty if block is not cached"() {
|
||||
setup:
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.default(objectMapper),
|
||||
head
|
||||
)
|
||||
when:
|
||||
def act = api.execute(1, "eth_getBlockByHash", ["0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58", false]).map { new String(it)}
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
def "Return block by hash when cached"() {
|
||||
setup:
|
||||
def cache = new BlocksMemCache();
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(cache).build(),
|
||||
head
|
||||
)
|
||||
cache.add(BlockContainer.from(
|
||||
new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.ofEpochSecond(0x5e95313a)
|
||||
),
|
||||
objectMapper
|
||||
))
|
||||
|
||||
when:
|
||||
def act = api.execute(1, "eth_getBlockByHash", ["0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58", false]).map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":{"number":"0x64","hash":"0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58","timestamp":"0x5e95313a","transactions":[],"totalDifficulty":"0x1","uncles":[]}}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
def "Return block by height when cached"() {
|
||||
setup:
|
||||
def blocksCache = new BlocksMemCache()
|
||||
def heightCache = new HeightCache()
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setBlockByHeight(heightCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.ofEpochSecond(0x5e95313a)
|
||||
)
|
||||
heightCache.add(BlockContainer.from(block, objectMapper))
|
||||
blocksCache.add(BlockContainer.from(block, objectMapper))
|
||||
|
||||
when:
|
||||
def act = api.execute(1, "eth_getBlockByNumber", ["0x64", false]).map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":{"number":"0x64","hash":"0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58","timestamp":"0x5e95313a","transactions":[],"totalDifficulty":"0x1","uncles":[]}}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
def "Uses base cache when requested, by hash"() {
|
||||
setup:
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
|
||||
when:
|
||||
def act = api.readBlockByHash(1, "eth_getBlockByHash", ["0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58", false]).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
0 * txCache.read(_)
|
||||
}
|
||||
|
||||
def "Uses full cache when requested, by hash"() {
|
||||
setup:
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
block.transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073"))
|
||||
]
|
||||
|
||||
when:
|
||||
def act = api.readBlockByHash(1, "eth_getBlockByHash", ["0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58", true]).block()
|
||||
|
||||
then:
|
||||
act == null
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
1 * txCache.read(TxId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073")) >> Mono.empty()
|
||||
}
|
||||
|
||||
def "Uses base cache when requested, by height"() {
|
||||
setup:
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def heightCache = Mock(HeightCache)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
|
||||
when:
|
||||
def act = api.readBlockByNumber(1, "eth_getBlockByNumber", ["0x64", false]).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
1 * heightCache.read(100) >> Mono.just(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
0 * txCache.read(_)
|
||||
}
|
||||
|
||||
def "Uses full cache when requested, by height"() {
|
||||
setup:
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def txCache = Mock(TxMemCache)
|
||||
def heightCache = Mock(HeightCache)
|
||||
def head = Mock(Head.class)
|
||||
def api = new CachingEthereumApi(
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
block.transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073"))
|
||||
]
|
||||
|
||||
when:
|
||||
def act = api.readBlockByNumber(1, "eth_getBlockByNumber", ["0x64", true]).block()
|
||||
|
||||
then:
|
||||
act == null
|
||||
1 * heightCache.read(100) >> Mono.just(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
1 * txCache.read(TxId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073")) >> Mono.empty()
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class CurrentUpstreamsSpec extends Specification {
|
||||
def "add upstream"() {
|
||||
setup:
|
||||
def current = new CurrentUpstreams(TestingCommons.objectMapper(), TestingCommons.emptyCaches())
|
||||
def up = new EthereumUpstreamMock("test", Chain.ETHEREUM, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up = new EthereumUpstreamMock("test", Chain.ETHEREUM, TestingCommons.api())
|
||||
when:
|
||||
current.update(new UpstreamChange(Chain.ETHEREUM, up, UpstreamChange.ChangeType.ADDED))
|
||||
then:
|
||||
@@ -38,9 +38,9 @@ class CurrentUpstreamsSpec extends Specification {
|
||||
def "add multiple upstreams"() {
|
||||
setup:
|
||||
def current = new CurrentUpstreams(TestingCommons.objectMapper(), TestingCommons.emptyCaches())
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up2 = new EthereumUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up3 = new EthereumUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api())
|
||||
def up2 = new EthereumUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api())
|
||||
def up3 = new EthereumUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api())
|
||||
when:
|
||||
current.update(new UpstreamChange(Chain.ETHEREUM, up1, UpstreamChange.ChangeType.ADDED))
|
||||
current.update(new UpstreamChange(Chain.ETHEREUM_CLASSIC, up2, UpstreamChange.ChangeType.ADDED))
|
||||
@@ -54,10 +54,10 @@ class CurrentUpstreamsSpec extends Specification {
|
||||
def "remove upstream"() {
|
||||
setup:
|
||||
def current = new CurrentUpstreams(TestingCommons.objectMapper(), TestingCommons.emptyCaches())
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up2 = new EthereumUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up3 = new EthereumUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up1_del = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api())
|
||||
def up2 = new EthereumUpstreamMock("test2", Chain.ETHEREUM_CLASSIC, TestingCommons.api())
|
||||
def up3 = new EthereumUpstreamMock("test3", Chain.ETHEREUM, TestingCommons.api())
|
||||
def up1_del = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api())
|
||||
when:
|
||||
current.update(new UpstreamChange(Chain.ETHEREUM, up1, UpstreamChange.ChangeType.ADDED))
|
||||
current.update(new UpstreamChange(Chain.ETHEREUM_CLASSIC, up2, UpstreamChange.ChangeType.ADDED))
|
||||
@@ -72,7 +72,7 @@ class CurrentUpstreamsSpec extends Specification {
|
||||
def "available after adding"() {
|
||||
setup:
|
||||
def current = new CurrentUpstreams(TestingCommons.objectMapper(), TestingCommons.emptyCaches())
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api(Stub(ReactorRpcClient)))
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, TestingCommons.api())
|
||||
|
||||
when:
|
||||
def act = current.isAvailable(Chain.ETHEREUM)
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.test.EthereumApiStub
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWs
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsFactory
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import reactor.test.StepVerifier
|
||||
@@ -40,6 +40,7 @@ class FilteredApisSpec extends Specification {
|
||||
|
||||
def "Verifies labels"() {
|
||||
setup:
|
||||
def i = 0
|
||||
List<EthereumUpstream> upstreams = [
|
||||
[test: "foo"],
|
||||
[test: "bar"],
|
||||
@@ -50,8 +51,8 @@ class FilteredApisSpec extends Specification {
|
||||
new EthereumUpstream(
|
||||
"test",
|
||||
Chain.ETHEREUM,
|
||||
new DirectEthereumApi(rpcClient, null, objectMapper, ethereumTargets),
|
||||
(EthereumWs) null,
|
||||
TestingCommons.api().tap { it.id = "${i++}" },
|
||||
(EthereumWsFactory) null,
|
||||
new UpstreamsConfig.Options(),
|
||||
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(it)),
|
||||
ethereumTargets, TestingCommons.objectMapper()
|
||||
@@ -143,8 +144,8 @@ class FilteredApisSpec extends Specification {
|
||||
|
||||
def "Makes pause between batches"() {
|
||||
when:
|
||||
def api1 = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def api2 = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def api1 = TestingCommons.api()
|
||||
def api2 = TestingCommons.api()
|
||||
def up1 = TestingCommons.upstream(api1)
|
||||
def up2 = TestingCommons.upstream(api2)
|
||||
then:
|
||||
@@ -153,8 +154,8 @@ class FilteredApisSpec extends Specification {
|
||||
apis.request(10)
|
||||
return apis
|
||||
})
|
||||
.expectNext(api1, api2).as("Batch 1")
|
||||
.expectNoEvent(Duration.ofMillis(100)).as("Wait 1")
|
||||
.expectNext(api1, api2).as("Batch 1")
|
||||
.expectNoEvent(Duration.ofMillis(100)).as("Wait 1")
|
||||
.expectNext(api1, api2).as("Batch 2")
|
||||
.expectNoEvent(Duration.ofMillis(400)).as("Wait 2")
|
||||
.expectNext(api1, api2).as("Batch 3")
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.bitcoin
|
||||
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
@@ -26,8 +29,8 @@ class BitcoinRpcHeadSpec extends Specification {
|
||||
|
||||
def "Follow 2 blocks created over 3 requests"() {
|
||||
setup:
|
||||
String hash1 = "0000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50001"
|
||||
String hash2 = "0000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50002"
|
||||
String hash1 = "1000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50001"
|
||||
String hash2 = "2000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50002"
|
||||
|
||||
def block1 = """
|
||||
{
|
||||
@@ -74,12 +77,14 @@ class BitcoinRpcHeadSpec extends Specification {
|
||||
}
|
||||
"""
|
||||
|
||||
DirectBitcoinApi api = Mock(DirectBitcoinApi) {
|
||||
_ * executeAndResult(_, "getbestblockhash", _, String) >>> [
|
||||
Mono.just(hash1), Mono.just(hash1), Mono.just(hash2)
|
||||
def api = Mock(Reader) {
|
||||
_ * read(new JsonRpcRequest("getbestblockhash", [])) >>> [
|
||||
Mono.just(new JsonRpcResponse("\"$hash1\"".bytes, null)),
|
||||
Mono.just(new JsonRpcResponse("\"$hash1\"".bytes, null)),
|
||||
Mono.just(new JsonRpcResponse("\"$hash2\"".bytes, null))
|
||||
]
|
||||
_ * execute(_, "getblock", [hash1]) >> Mono.just(block1.bytes)
|
||||
_ * execute(_, "getblock", [hash2]) >> Mono.just(block2.bytes)
|
||||
_ * read(new JsonRpcRequest("getblock", [hash1])) >> Mono.just(new JsonRpcResponse(block1.bytes, null))
|
||||
_ * read(new JsonRpcRequest("getblock", [hash2])) >> Mono.just(new JsonRpcResponse(block2.bytes, null))
|
||||
}
|
||||
BitcoinRpcHead head = new BitcoinRpcHead(api, new ExtractBlock(TestingCommons.objectMapper()), Duration.ofMillis(200))
|
||||
|
||||
|
||||
@@ -1,123 +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.bitcoin
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import org.mockserver.integration.ClientAndServer
|
||||
import org.mockserver.model.HttpRequest
|
||||
import org.mockserver.model.HttpResponse
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class DirectBitcoinApiSpec extends Specification {
|
||||
|
||||
ClientAndServer mockServer
|
||||
DirectBitcoinApi api
|
||||
|
||||
def setup() {
|
||||
mockServer = ClientAndServer.startClientAndServer(18332);
|
||||
api = new DirectBitcoinApi(
|
||||
new BitcoinRpcClient("localhost:18332", null),
|
||||
TestingCommons.objectMapper(), new DefaultBitcoinMethods(TestingCommons.objectMapper())
|
||||
)
|
||||
}
|
||||
|
||||
def cleanup() {
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
def "Request simple"() {
|
||||
setup:
|
||||
def resp = '{' +
|
||||
' "result": "0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90",' +
|
||||
' "error": null,' +
|
||||
' "id": 15' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
).respond(
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = api.executeAndResult(15, "getbestblockhash", [], String)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody('{"jsonrpc":"2.0","method":"getbestblockhash","params":[],"id":15}')
|
||||
)
|
||||
}
|
||||
|
||||
def "Request with params"() {
|
||||
setup:
|
||||
def resp = '{' +
|
||||
' "result": "something",' +
|
||||
' "id": 1' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
).respond(
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = api.executeAndResult(1, "getsomething", ["something", false], String)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("something")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody('{"jsonrpc":"2.0","method":"getsomething","params":["something",false],"id":1}')
|
||||
)
|
||||
}
|
||||
|
||||
def "Returns error"() {
|
||||
setup:
|
||||
def resp = '{' +
|
||||
' "result": null,' +
|
||||
' "error": {' +
|
||||
' "code": -32601,' +
|
||||
' "message": "Method not found"' +
|
||||
' },' +
|
||||
' "id": 1' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
).respond(
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = api.executeAndResult(1, "geterror", [], String)
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectError(RpcException)
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody('{"jsonrpc":"2.0","method":"geterror","params":[],"id":1}')
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,6 @@ class AggregatedCallMethodsSpec extends Specification {
|
||||
when:
|
||||
def act = aggregate.executeHardcoded("eth_test")
|
||||
then:
|
||||
act == "hello"
|
||||
new String(act) == "hello"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 ETCDEV GmbH
|
||||
* 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.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.RpcResponseError
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class DirectEthereumApiSpec extends Specification {
|
||||
|
||||
DirectEthereumApi api = new DirectEthereumApi(Stub(ReactorRpcClient), null, TestingCommons.objectMapper(), new DirectCallMethods())
|
||||
|
||||
def "Process successful result"() {
|
||||
setup:
|
||||
def result = Mono.just("hello")
|
||||
when:
|
||||
def act = api.processResult(1, "eth_test", result)
|
||||
.map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":"hello"}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
|
||||
}
|
||||
|
||||
def "Process empty result"() {
|
||||
setup:
|
||||
def result = Mono.empty()
|
||||
when:
|
||||
def act = api.processResult(1, "eth_test", result)
|
||||
.map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":null}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
|
||||
}
|
||||
|
||||
def "Process standard RPC error"() {
|
||||
setup:
|
||||
def result = Mono.error(new RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Test Error", Map.of("foo", "bar")))
|
||||
when:
|
||||
def act = api.processResult(1, "eth_test", result)
|
||||
.map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Test Error","data":{"foo":"bar"}}}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
|
||||
}
|
||||
|
||||
def "Process internal exception"() {
|
||||
setup:
|
||||
def result = Mono.error(new InterruptedException("test"))
|
||||
when:
|
||||
def act = api.processResult(1, "eth_test", result)
|
||||
.map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"error":{"code":-32020,"message":"Error reading from upstream"}}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
|
||||
}
|
||||
|
||||
def "Typed mapping for block request"() {
|
||||
when:
|
||||
def act = api.callMapping("eth_getBlockByHash", ["0xacf5611707048efc39cabed483e420672ca1ed070f248ef6202c99994dbc6061", false])
|
||||
then:
|
||||
act.jsonType == BlockJson
|
||||
act.resultType == BlockJson
|
||||
}
|
||||
|
||||
def "Typed mapping for block request with txes"() {
|
||||
when:
|
||||
def act = api.callMapping("eth_getBlockByHash", ["0xacf5611707048efc39cabed483e420672ca1ed070f248ef6202c99994dbc6061", true])
|
||||
then:
|
||||
act.jsonType == BlockJson
|
||||
act.resultType == BlockJson
|
||||
}
|
||||
|
||||
def "Typed mapping for block by height request"() {
|
||||
when:
|
||||
def act = api.callMapping("eth_getBlockByNumber", ["0x135", false])
|
||||
then:
|
||||
act.jsonType == BlockJson
|
||||
act.resultType == BlockJson
|
||||
}
|
||||
|
||||
def "Typed mapping for block by height request with txes"() {
|
||||
when:
|
||||
def act = api.callMapping("eth_getBlockByNumber", ["0xacf5", true])
|
||||
then:
|
||||
act.jsonType == BlockJson
|
||||
act.resultType == BlockJson
|
||||
}
|
||||
|
||||
def "Typed mapping for tx request"() {
|
||||
when:
|
||||
def act = api.callMapping("eth_getTransactionByHash", ["0xacf5611707048efc39cabed483e420672ca1ed070f248ef6202c99994dbc6061"])
|
||||
then:
|
||||
act.jsonType == TransactionJson
|
||||
act.resultType == TransactionJson
|
||||
}
|
||||
|
||||
def "Errors for mapping of invalid tx request"() {
|
||||
when:
|
||||
api.callMapping("eth_getTransactionByHash", ["0xacf5611707048efc39cabed483e420672ca1ed070f248ef6"])
|
||||
then:
|
||||
def t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
|
||||
when:
|
||||
api.callMapping("eth_getTransactionByHash", ["0xacf5611707048efc39cabed483e420672ca1ed070f248ef6202c99994dbc6061", true])
|
||||
then:
|
||||
t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
|
||||
when:
|
||||
api.callMapping("eth_getTransactionByHash", [])
|
||||
then:
|
||||
t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
}
|
||||
|
||||
def "Errors for mapping of invalid block request"() {
|
||||
when:
|
||||
api.callMapping("eth_getBlockByHash", ["0xacf5611707048efc39cabed483e420672ca1ed070f248ef6202c99994dbc6061"])
|
||||
then:
|
||||
def t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
|
||||
when:
|
||||
api.callMapping("eth_getBlockByHash", ["0xacf5611707048efc39cabed48f6202c99994dbc6061", true])
|
||||
then:
|
||||
t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
|
||||
when:
|
||||
api.callMapping("eth_getBlockByHash", [])
|
||||
then:
|
||||
t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
}
|
||||
|
||||
def "Errors for mapping of invalid block by number request"() {
|
||||
when:
|
||||
api.callMapping("eth_getBlockByNumber", ["0xacf5611707048efc3248ef6202c99994dbc6061"])
|
||||
then:
|
||||
def t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
|
||||
when:
|
||||
api.callMapping("eth_getBlockByNumber", ["0x", true])
|
||||
then:
|
||||
t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
|
||||
when:
|
||||
api.callMapping("eth_getBlockByNumber", ["-0x23", true])
|
||||
then:
|
||||
t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
|
||||
when:
|
||||
api.callMapping("eth_getBlockByNumber", [])
|
||||
then:
|
||||
t = thrown(RpcException)
|
||||
t.code == RpcResponseError.CODE_INVALID_METHOD_PARAMS
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.cache
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||
import io.emeraldpay.dshackle.cache.TxMemCache
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
@@ -29,7 +31,7 @@ import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class EthereumBlocksWithTxCacheSpec extends Specification {
|
||||
class EthereumFullBlocksReaderSpec extends Specification {
|
||||
|
||||
// sorted
|
||||
String hash1 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||
@@ -119,7 +121,7 @@ class EthereumBlocksWithTxCacheSpec extends Specification {
|
||||
blocks.add(BlockContainer.from(block2, objectMapper))
|
||||
blocks.add(BlockContainer.from(block3, objectMapper))
|
||||
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
def full = new EthereumFullBlocksReader(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(BlockId.from(block1.hash)).block()
|
||||
@@ -185,7 +187,7 @@ class EthereumBlocksWithTxCacheSpec extends Specification {
|
||||
blocks.add(BlockContainer.from(block2, objectMapper))
|
||||
blocks.add(BlockContainer.from(block3, objectMapper))
|
||||
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
def full = new EthereumFullBlocksReader(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(BlockId.from(block3.hash)).block()
|
||||
@@ -205,7 +207,7 @@ class EthereumBlocksWithTxCacheSpec extends Specification {
|
||||
txes.add(TxContainer.from(tx1, objectMapper))
|
||||
blocks.add(BlockContainer.from(block1, objectMapper)) //missing tx2 in cache
|
||||
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
def full = new EthereumFullBlocksReader(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(BlockId.from(block1.hash)).block()
|
||||
@@ -223,7 +225,7 @@ class EthereumBlocksWithTxCacheSpec extends Specification {
|
||||
txes.add(TxContainer.from(tx2, objectMapper))
|
||||
txes.add(TxContainer.from(tx3, objectMapper))
|
||||
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
def full = new EthereumFullBlocksReader(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(BlockId.from(block1.hash)).block()
|
||||
@@ -17,17 +17,26 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.HeightCache
|
||||
import io.emeraldpay.dshackle.cache.TxMemCache
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.Address
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.domain.Wei
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.RpcResponseError
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
@@ -35,6 +44,7 @@ import reactor.core.publisher.Mono
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class EthereumReaderSpec extends Specification {
|
||||
|
||||
@@ -63,7 +73,7 @@ class EthereumReaderSpec extends Specification {
|
||||
.setBlockByHash(memCache)
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.build()
|
||||
def reader = new EthereumReader(Stub(Upstream), caches, TestingCommons.objectMapper())
|
||||
def reader = new EthereumReader(Stub(AggregatedUpstream), caches, TestingCommons.objectMapper())
|
||||
|
||||
when:
|
||||
def act = reader.blocksById().read(blockId).block()
|
||||
@@ -81,8 +91,7 @@ class EthereumReaderSpec extends Specification {
|
||||
.setBlockByHash(memCache)
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.build()
|
||||
def rpcClient = Stub(ReactorRpcClient)
|
||||
def api = TestingCommons.api(rpcClient)
|
||||
def api = TestingCommons.api()
|
||||
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||
|
||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||
@@ -104,8 +113,7 @@ class EthereumReaderSpec extends Specification {
|
||||
.setBlockByHash(memCache)
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.build()
|
||||
def rpcClient = Stub(ReactorRpcClient)
|
||||
def api = TestingCommons.api(rpcClient)
|
||||
def api = TestingCommons.api()
|
||||
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||
|
||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||
@@ -127,7 +135,7 @@ class EthereumReaderSpec extends Specification {
|
||||
.setBlockByHash(memCache)
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.build()
|
||||
def reader = new EthereumReader(Stub(Upstream), caches, TestingCommons.objectMapper())
|
||||
def reader = new EthereumReader(Stub(AggregatedUpstream), caches, TestingCommons.objectMapper())
|
||||
|
||||
when:
|
||||
def act = reader.blocksByHash().read(blockJson.hash).block()
|
||||
@@ -145,8 +153,7 @@ class EthereumReaderSpec extends Specification {
|
||||
.setBlockByHash(memCache)
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.build()
|
||||
def rpcClient = Stub(ReactorRpcClient)
|
||||
def api = TestingCommons.api(rpcClient)
|
||||
def api = TestingCommons.api()
|
||||
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||
def reader = new EthereumReader(upstream, caches, TestingCommons.objectMapper())
|
||||
@@ -167,7 +174,7 @@ class EthereumReaderSpec extends Specification {
|
||||
.setTxByHash(memCache)
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.build()
|
||||
def reader = new EthereumReader(Stub(Upstream), caches, TestingCommons.objectMapper())
|
||||
def reader = new EthereumReader(Stub(AggregatedUpstream), caches, TestingCommons.objectMapper())
|
||||
|
||||
when:
|
||||
def act = reader.txByHash().read(txJson.hash).block()
|
||||
@@ -186,8 +193,7 @@ class EthereumReaderSpec extends Specification {
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.build()
|
||||
|
||||
def rpcClient = Stub(ReactorRpcClient)
|
||||
def api = TestingCommons.api(rpcClient)
|
||||
def api = TestingCommons.api()
|
||||
api.answer("eth_getTransactionByHash", [txJson.hash.toHex()], txJson)
|
||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||
def reader = new EthereumReader(upstream, caches, TestingCommons.objectMapper())
|
||||
@@ -201,12 +207,12 @@ class EthereumReaderSpec extends Specification {
|
||||
|
||||
def "Caches balance until block mined"() {
|
||||
setup:
|
||||
def rpcClient = Stub(ReactorRpcClient)
|
||||
def api = TestingCommons.api(rpcClient)
|
||||
def api = TestingCommons.api()
|
||||
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0x10")
|
||||
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0xff")
|
||||
def upstream = TestingCommons.upstream(api)
|
||||
def reader = new EthereumReader(upstream, Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
||||
def upstreams = TestingCommons.aggregatedUpstream(upstream)
|
||||
def reader = new EthereumReader(upstreams, Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
reader.start()
|
||||
|
||||
when:
|
||||
|
||||
@@ -18,7 +18,6 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.HeightCache
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
@@ -26,7 +25,6 @@ import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import io.infinitape.etherjar.rpc.ws.WebsocketClient
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
@@ -35,44 +33,14 @@ import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class EthereumWsSpec extends Specification {
|
||||
class EthereumWsFactorySpec extends Specification {
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Uses cache to fetch block"() {
|
||||
def "Fetch block"() {
|
||||
setup:
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
def ws = new EthereumWs(new URI("http://localhost"), new URI("http://localhost"), apiMock, objectMapper)
|
||||
def wsf = new EthereumWsFactory(new URI("http://localhost"), new URI("http://localhost"), objectMapper)
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def caches = Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).build()
|
||||
ws.setCaches(caches)
|
||||
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
||||
block.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
|
||||
when:
|
||||
ws.onNewBlock(block)
|
||||
|
||||
then:
|
||||
1 * blocksCache.read(BlockId.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
StepVerifier.create(ws.flux.take(1))
|
||||
.expectNext(BlockContainer.from(block, objectMapper))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Fetch block if cache is empty"() {
|
||||
setup:
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
def ws = new EthereumWs(new URI("http://localhost"), new URI("http://localhost"), apiMock, objectMapper)
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def caches = Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).build()
|
||||
ws.setCaches(caches)
|
||||
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
@@ -82,13 +50,16 @@ class EthereumWsSpec extends Specification {
|
||||
block.uncles = []
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstream = TestingCommons.upstream(apiMock)
|
||||
def ws = wsf.create(upstream)
|
||||
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], block)
|
||||
|
||||
when:
|
||||
ws.onNewBlock(block)
|
||||
|
||||
then:
|
||||
1 * blocksCache.read(_) >> Mono.empty()
|
||||
StepVerifier.create(ws.flux.take(1))
|
||||
.expectNext(BlockContainer.from(block, objectMapper))
|
||||
.expectComplete()
|
||||
@@ -25,6 +25,7 @@ import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.MockGrpcServer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.grpc.stub.StreamObserver
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
@@ -46,7 +47,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
setup:
|
||||
def callData = [:]
|
||||
def chain = Chain.ETHEREUM
|
||||
def api = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def api = TestingCommons.api()
|
||||
def block1 = new BlockJson().with {
|
||||
it.number = 650246
|
||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
@@ -73,8 +74,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
)
|
||||
}
|
||||
})
|
||||
def transport = ReactorEmeraldClient.newBuilder().connectUsing(client.channel).build()
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, objectMapper, transport)
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, objectMapper, new JsonRpcGrpcClient(client, chain, objectMapper))
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
@@ -90,7 +90,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
|
||||
def "Follows difficulty, ignores less difficult"() {
|
||||
setup:
|
||||
def api = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def api = TestingCommons.api()
|
||||
def block1 = new BlockJson().with {
|
||||
it.number = 650246
|
||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
@@ -131,8 +131,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
)
|
||||
}
|
||||
})
|
||||
def transport = ReactorEmeraldClient.newBuilder().connectUsing(client.channel).build()
|
||||
def upstream = new EthereumGrpcUpstream("test", Chain.ETHEREUM, client, objectMapper, transport)
|
||||
def upstream = new EthereumGrpcUpstream("test", Chain.ETHEREUM, client, objectMapper, new JsonRpcGrpcClient(client, Chain.ETHEREUM, objectMapper))
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
@@ -151,7 +150,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
def callData = [:]
|
||||
def finished = new CompletableFuture<Boolean>()
|
||||
def chain = Chain.ETHEREUM
|
||||
def api = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def api = TestingCommons.api()
|
||||
def block1 = new BlockJson().with {
|
||||
it.number = 650246
|
||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
@@ -193,8 +192,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
finished.complete(true)
|
||||
}
|
||||
})
|
||||
def transport = ReactorEmeraldClient.newBuilder().connectUsing(client.channel).build()
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, objectMapper, transport)
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, objectMapper, new JsonRpcGrpcClient(client, chain, objectMapper))
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
|
||||
@@ -1,59 +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.test.TestingCommons
|
||||
import org.mockserver.integration.ClientAndServer
|
||||
import org.mockserver.model.HttpRequest
|
||||
import org.mockserver.model.HttpResponse
|
||||
import spock.lang.Specification
|
||||
|
||||
class JsonRpcClientSpec extends Specification {
|
||||
|
||||
ClientAndServer mockServer
|
||||
JsonRpcClient client
|
||||
|
||||
def setup() {
|
||||
mockServer = ClientAndServer.startClientAndServer(18332);
|
||||
client = new JsonRpcClient("localhost:18332", TestingCommons.objectMapper(), null)
|
||||
}
|
||||
|
||||
def cleanup() {
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
|
||||
def "Make a request"() {
|
||||
setup:
|
||||
def resp = '{' +
|
||||
' "jsonrpc": "2.0",' +
|
||||
' "result": "0x98de45",' +
|
||||
' "error": null,' +
|
||||
' "id": 15' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
).respond(
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = client.execute(new JsonRpcRequest("test", [])).block()
|
||||
then:
|
||||
act.error == null
|
||||
new String(act.result) == '"0x98de45"'
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,22 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.bitcoin
|
||||
package io.emeraldpay.dshackle.upstream.rpcclient
|
||||
|
||||
import io.emeraldpay.dshackle.config.AuthConfig
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import org.mockserver.integration.ClientAndServer
|
||||
import org.mockserver.matchers.Times
|
||||
import org.mockserver.model.HttpRequest
|
||||
import org.mockserver.model.HttpResponse
|
||||
import org.mockserver.model.MediaType
|
||||
import org.mockserver.verify.VerificationTimes
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class BitcoinRpcClientSpec extends Specification {
|
||||
class JsonRpcHttpClientSpec extends Specification {
|
||||
|
||||
ClientAndServer mockServer
|
||||
|
||||
@@ -40,39 +38,31 @@ class BitcoinRpcClientSpec extends Specification {
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
def "Make request"() {
|
||||
def "Make a request"() {
|
||||
setup:
|
||||
def client = new BitcoinRpcClient("localhost:18332", null)
|
||||
|
||||
JsonRpcHttpClient client = new JsonRpcHttpClient("localhost:18332", TestingCommons.objectMapper(), null, null)
|
||||
def resp = '{' +
|
||||
' "jsonrpc": "2.0",' +
|
||||
' "result": "0x98de45",' +
|
||||
' "error": null,' +
|
||||
' "id": 15' +
|
||||
'}'
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody("ping"),
|
||||
Times.exactly(1)
|
||||
).respond(
|
||||
HttpResponse.response()
|
||||
.withBody("pong")
|
||||
HttpResponse.response(resp)
|
||||
)
|
||||
when:
|
||||
def act = client.execute("ping".bytes).map { new String(it) }
|
||||
def act = client.read(new JsonRpcRequest("test", [])).block()
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("pong")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
mockServer.verify(
|
||||
HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withBody("ping")
|
||||
.withContentType(MediaType.APPLICATION_JSON)
|
||||
)
|
||||
|
||||
act.error == null
|
||||
new String(act.result) == '"0x98de45"'
|
||||
}
|
||||
|
||||
def "Make request with basic auth"() {
|
||||
setup:
|
||||
def auth = new AuthConfig.ClientBasicAuth("user", "passwd")
|
||||
def client = new BitcoinRpcClient("localhost:18332", auth)
|
||||
def client = new JsonRpcHttpClient("localhost:18332", TestingCommons.objectMapper(), auth, null)
|
||||
|
||||
mockServer.when(
|
||||
HttpRequest.request()
|
||||
@@ -173,7 +173,8 @@ class JsonRpcParserSpec extends Specification {
|
||||
act.error != null
|
||||
act.error.code == -1111
|
||||
act.error.message == "test"
|
||||
act.result == null
|
||||
act.hasError()
|
||||
!act.hasResult()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,4 +28,32 @@ class JsonRpcResponseSpec extends Specification {
|
||||
then:
|
||||
act == true
|
||||
}
|
||||
|
||||
def "Extract processed string without quoted"() {
|
||||
when:
|
||||
def act = new JsonRpcResponse("\"hello\"".bytes, null).resultAsProcessedString
|
||||
then:
|
||||
act == "hello"
|
||||
}
|
||||
|
||||
def "Extract raw string with quoted"() {
|
||||
when:
|
||||
def act = new JsonRpcResponse("\"hello\"".bytes, null).resultAsRawString
|
||||
then:
|
||||
act == "\"hello\""
|
||||
}
|
||||
|
||||
def "Fails to extract processed string if not quoted"() {
|
||||
when:
|
||||
def act = new JsonRpcResponse("{\"hello\": 1}".bytes, null).resultAsProcessedString
|
||||
then:
|
||||
thrown(IllegalStateException)
|
||||
}
|
||||
|
||||
def "Recognizes null"() {
|
||||
when:
|
||||
def act = new JsonRpcResponse("null".bytes, null)
|
||||
then:
|
||||
act.isNull()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user