Add NotNullQuorum for tx methods (#208)
This commit is contained in:
@@ -1,129 +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.quorum
|
||||
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
|
||||
import spock.lang.Specification
|
||||
|
||||
class NonEmptyQuorumSpec extends Specification {
|
||||
|
||||
def "Fail if too many errors"() {
|
||||
setup:
|
||||
def q = Spy(new NonEmptyQuorum(3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
def upstream3 = Stub(Upstream)
|
||||
|
||||
when:
|
||||
q.init(Stub(Head))
|
||||
then:
|
||||
!q.isResolved()
|
||||
!q.isFailed()
|
||||
|
||||
when:
|
||||
q.record(new JsonRpcException(1, "Internal"), null, upstream1)
|
||||
then:
|
||||
!q.isResolved()
|
||||
!q.isFailed()
|
||||
|
||||
when:
|
||||
q.record(new JsonRpcException(1, "Internal"), null, upstream2)
|
||||
then:
|
||||
!q.isResolved()
|
||||
!q.isFailed()
|
||||
|
||||
when:
|
||||
q.record(new JsonRpcException(1, "Internal"), null, upstream3)
|
||||
then:
|
||||
q.isFailed()
|
||||
!q.isResolved()
|
||||
q.signature == null
|
||||
}
|
||||
|
||||
def "Fail first if not error"() {
|
||||
setup:
|
||||
def q = Spy(new NonEmptyQuorum(3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
|
||||
when:
|
||||
q.init(Stub(Head))
|
||||
then:
|
||||
!q.isResolved()
|
||||
!q.isFailed()
|
||||
|
||||
when:
|
||||
q.record('"0x11"'.bytes, null, upstream1, null)
|
||||
then:
|
||||
q.isResolved()
|
||||
!q.isFailed()
|
||||
}
|
||||
|
||||
def "Fail second if first is error"() {
|
||||
setup:
|
||||
def q = Spy(new NonEmptyQuorum(3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
|
||||
when:
|
||||
q.init(Stub(Head))
|
||||
then:
|
||||
!q.isResolved()
|
||||
!q.isFailed()
|
||||
|
||||
when:
|
||||
q.record(new JsonRpcException(1, "Internal"), null, upstream1)
|
||||
then:
|
||||
!q.isFailed()
|
||||
!q.isResolved()
|
||||
q.signature == null
|
||||
|
||||
when:
|
||||
q.record('"0x11"'.bytes, null, upstream2, null)
|
||||
then:
|
||||
q.isResolved()
|
||||
!q.isFailed()
|
||||
}
|
||||
|
||||
def "Fail second if first is null"() {
|
||||
setup:
|
||||
def q = Spy(new NonEmptyQuorum(3))
|
||||
def upstream1 = Stub(Upstream)
|
||||
|
||||
when:
|
||||
q.init(Stub(Head))
|
||||
then:
|
||||
!q.isResolved()
|
||||
!q.isFailed()
|
||||
|
||||
when:
|
||||
q.record('null'.bytes, null, upstream1, null)
|
||||
then:
|
||||
!q.isFailed()
|
||||
!q.isResolved()
|
||||
|
||||
|
||||
when:
|
||||
q.record('"0x11"'.bytes, null, upstream1, null)
|
||||
then:
|
||||
q.isResolved()
|
||||
!q.isFailed()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.emeraldpay.dshackle.quorum
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
|
||||
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
|
||||
import spock.lang.Specification
|
||||
|
||||
class NotNullQuorumSpec extends Specification {
|
||||
|
||||
def "Resolves if attempts are exhausted and response is null"() {
|
||||
setup:
|
||||
def up = Mock(Upstream) {
|
||||
2 * getId() >> "id"
|
||||
}
|
||||
def up1 = Mock(Upstream) {
|
||||
1 * getId() >> "id1"
|
||||
}
|
||||
def up2 = Mock(Upstream) {
|
||||
1 * getId() >> "id2"
|
||||
}
|
||||
def value = "null".getBytes()
|
||||
def quorum = new NotNullQuorum()
|
||||
|
||||
when:
|
||||
def res = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up, "id")
|
||||
def res1 = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up1, "id1")
|
||||
def res2 = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up2, "id2")
|
||||
def res3 = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up, "id")
|
||||
then:
|
||||
!res
|
||||
!res1
|
||||
!res2
|
||||
res3
|
||||
quorum.result == value
|
||||
!quorum.isFailed()
|
||||
quorum.isResolved()
|
||||
quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100)
|
||||
quorum.providedUpstreamId == "id"
|
||||
}
|
||||
|
||||
def "Failed if all upstreams respond with error"() {
|
||||
setup:
|
||||
def up = Mock(Upstream) {
|
||||
2 * getId() >> "id"
|
||||
}
|
||||
def up1 = Mock(Upstream) {
|
||||
1 * getId() >> "id1"
|
||||
}
|
||||
def up2 = Mock(Upstream) {
|
||||
1 * getId() >> "id2"
|
||||
}
|
||||
def quorum = new NotNullQuorum()
|
||||
|
||||
when:
|
||||
quorum.record(new JsonRpcException(10, "error"), null, up)
|
||||
quorum.record(new JsonRpcException(10, "error"), null, up1)
|
||||
quorum.record(new JsonRpcException(10, "error"), null, up2)
|
||||
quorum.record(new JsonRpcException(10, "error"), null, up)
|
||||
|
||||
then:
|
||||
quorum.isFailed()
|
||||
!quorum.isResolved()
|
||||
quorum.error == new JsonRpcException(10, "error").error
|
||||
}
|
||||
|
||||
def "Resolve if one of upstream responds with value"() {
|
||||
setup:
|
||||
def up = Mock(Upstream) {
|
||||
2 * getId() >> "id"
|
||||
}
|
||||
def up1 = Mock(Upstream) {
|
||||
1 * getId() >> "id1"
|
||||
}
|
||||
def up2 = Mock(Upstream) {
|
||||
1 * getId() >> "id2"
|
||||
}
|
||||
def value = "null".getBytes()
|
||||
def quorum = new NotNullQuorum()
|
||||
|
||||
when:
|
||||
def res = quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up, "id")
|
||||
quorum.record(new JsonRpcException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up1)
|
||||
quorum.record(new JsonRpcException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up2)
|
||||
quorum.record(new JsonRpcException(10, "error"), new ResponseSigner.Signature("sig1".bytes, "test", 100), up)
|
||||
|
||||
then:
|
||||
!res
|
||||
quorum.isResolved()
|
||||
!quorum.isFailed()
|
||||
quorum.result == value
|
||||
quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100)
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ class QuorumRpcReaderSpec extends Specification {
|
||||
Chain.ETHEREUM,
|
||||
[up], Selector.empty
|
||||
)
|
||||
def reader = new QuorumRpcReader(apis, new NonEmptyQuorum(3), Stub(Tracer))
|
||||
def reader = new QuorumRpcReader(apis, new NotNullQuorum(), Stub(Tracer))
|
||||
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_test", []))
|
||||
@@ -180,39 +180,7 @@ class QuorumRpcReaderSpec extends Specification {
|
||||
Chain.ETHEREUM,
|
||||
[up], Selector.empty
|
||||
)
|
||||
def reader = new QuorumRpcReader(apis, new NonEmptyQuorum(3), Stub(Tracer))
|
||||
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_test", []))
|
||||
.map {
|
||||
new String(it.value)
|
||||
}
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext("1")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "non-empty-quorum - get the third result if first two are not ok"() {
|
||||
setup:
|
||||
def up = Mock(Upstream) {
|
||||
_ * isAvailable() >> true
|
||||
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
|
||||
_ * getIngressReader() >> Mock(Reader) {
|
||||
3 * read(new JsonRpcRequest("eth_test", [])) >>> [
|
||||
Mono.just(JsonRpcResponse.ok("null")),
|
||||
Mono.just(JsonRpcResponse.error(1, "test")),
|
||||
Mono.just(JsonRpcResponse.ok("1"))
|
||||
]
|
||||
}
|
||||
}
|
||||
def apis = new FilteredApis(
|
||||
Chain.ETHEREUM,
|
||||
[up], Selector.empty
|
||||
)
|
||||
def reader = new QuorumRpcReader(apis, new NonEmptyQuorum(3), Stub(Tracer))
|
||||
def reader = new QuorumRpcReader(apis, new NotNullQuorum(), Stub(Tracer))
|
||||
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_test", []))
|
||||
@@ -230,13 +198,13 @@ class QuorumRpcReaderSpec extends Specification {
|
||||
def "non-empty-quorum - error if all failed"() {
|
||||
setup:
|
||||
def api = Mock(Reader) {
|
||||
3 * read(new JsonRpcRequest("eth_test", [])) >>> [
|
||||
Mono.just(JsonRpcResponse.ok("null")),
|
||||
2 * read(new JsonRpcRequest("eth_test", [])) >>> [
|
||||
Mono.just(JsonRpcResponse.error(1, "test")),
|
||||
Mono.just(JsonRpcResponse.error(1, "test")),
|
||||
Mono.just(JsonRpcResponse.ok("null"))
|
||||
]
|
||||
}
|
||||
def up = Mock(Upstream) {
|
||||
_ * getId() >> "test"
|
||||
_ * isAvailable() >> true
|
||||
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
|
||||
_ * getIngressReader() >> api
|
||||
@@ -245,7 +213,7 @@ class QuorumRpcReaderSpec extends Specification {
|
||||
Chain.ETHEREUM,
|
||||
[up], Selector.empty
|
||||
)
|
||||
def reader = new QuorumRpcReader(apis, new NonEmptyQuorum(3), Stub(Tracer))
|
||||
def reader = new QuorumRpcReader(apis, new NotNullQuorum(), Stub(Tracer))
|
||||
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_test", []))
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.rpc
|
||||
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
@@ -141,6 +140,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
act
|
||||
.expectSubscription()
|
||||
.expectNoEvent(Duration.ofSeconds(20)).as("Waited for updates")
|
||||
.thenAwait(Duration.ofSeconds(2))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
@@ -171,18 +171,15 @@ class TrackEthereumTxSpec extends Specification {
|
||||
def apiMock = TestingCommons.api()
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
MultistreamHolder upstreams = new MultistreamHolderMock(Chain.ETHEREUM, upstreamMock)
|
||||
def scheduler = VirtualTimeScheduler.create(true)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, scheduler)
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.boundedElastic())
|
||||
|
||||
apiMock.answerOnce("eth_getTransactionByHash", [txId], null)
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], null, 2)
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], txJson)
|
||||
|
||||
when:
|
||||
def act = StepVerifier.withVirtualTime({
|
||||
return trackTx.subscribe(req).take(2)
|
||||
}, { scheduler }, 5)
|
||||
def act = trackTx.subscribe(req).take(2)
|
||||
then:
|
||||
act
|
||||
StepVerifier.create(act)
|
||||
.expectSubscription()
|
||||
.expectNext(exp1).as("Unknown tx")
|
||||
.expectNext(exp2).as("Found in mempool")
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.emeraldpay.dshackle.FileResolver
|
||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
||||
import io.emeraldpay.dshackle.config.CompressionConfig
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.quorum.NonEmptyQuorum
|
||||
import io.emeraldpay.dshackle.quorum.NotNullQuorum
|
||||
import io.emeraldpay.dshackle.upstream.CallTargetsHolder
|
||||
import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
@@ -47,7 +47,7 @@ class ConfiguredUpstreamsSpec extends Specification {
|
||||
def act = configurer.buildMethods(upstream, Chain.ETHEREUM)
|
||||
then:
|
||||
act instanceof ManagedCallMethods
|
||||
act.createQuorumFor("foo_bar") instanceof NonEmptyQuorum
|
||||
act.createQuorumFor("foo_bar") instanceof NotNullQuorum
|
||||
}
|
||||
|
||||
def "Got static response from extra methods"() {
|
||||
|
||||
@@ -17,11 +17,7 @@
|
||||
package io.emeraldpay.dshackle.upstream.calls
|
||||
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
||||
import io.emeraldpay.dshackle.quorum.BroadcastQuorum
|
||||
import io.emeraldpay.dshackle.quorum.CallQuorum
|
||||
import io.emeraldpay.dshackle.quorum.NonEmptyQuorum
|
||||
import io.emeraldpay.dshackle.quorum.NotLaggingQuorum
|
||||
import io.emeraldpay.dshackle.quorum.*
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.util.concurrent.Executors
|
||||
@@ -109,7 +105,7 @@ class ManagedCallMethodsSpec extends Specification {
|
||||
when:
|
||||
def act = managed.createQuorumFor("eth_test")
|
||||
then:
|
||||
act instanceof NonEmptyQuorum
|
||||
act instanceof NotNullQuorum
|
||||
|
||||
when:
|
||||
act = managed.createQuorumFor("eth_foo")
|
||||
|
||||
@@ -427,32 +427,6 @@ class EthereumDirectReaderSpec extends Specification {
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Reads tx by hash with retries - expects an error within 1 sec"() {
|
||||
setup:
|
||||
def up = Mock(Multistream) {
|
||||
4 * getApiSource(_) >> Stub(ApiSource)
|
||||
}
|
||||
def calls = Mock(Factory) {
|
||||
4 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||
}
|
||||
EthereumDirectReader reader = new EthereumDirectReader(
|
||||
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
|
||||
)
|
||||
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
|
||||
4 * create(_, _, _, _) >> Mock(Reader) {
|
||||
4 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >>>
|
||||
[Mono.error(new RuntimeException()), Mono.error(new RuntimeException()),
|
||||
Mono.error(new RuntimeException()), Mono.error(new RuntimeException())]
|
||||
}
|
||||
}
|
||||
when:
|
||||
def act = reader.txReader.read(TransactionId.from(hash1))
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectError()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Reads balance with retries - expects an error within 1 sec"() {
|
||||
setup:
|
||||
def up = Mock(Multistream) {
|
||||
|
||||
Reference in New Issue
Block a user