Maximum value quorum for BroadcastReader (#272)
Fixes eth_getTransactionCount when tx did not propagated to all nodes
This commit is contained in:
@@ -29,58 +29,43 @@ class BroadcastQuorumSpec extends Specification {
|
||||
|
||||
def "Resolved with first after 3 tries"() {
|
||||
setup:
|
||||
def q = Spy(new BroadcastQuorum(3))
|
||||
def q = Spy(new BroadcastQuorum())
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
def upstream3 = Stub(Upstream)
|
||||
|
||||
when:
|
||||
q.init(Stub(Head))
|
||||
then:
|
||||
!q.isResolved()
|
||||
|
||||
when:
|
||||
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream1)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _)
|
||||
|
||||
when:
|
||||
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _)
|
||||
|
||||
when:
|
||||
q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3)
|
||||
then:
|
||||
1 * q.recordError(_, _, _, _)
|
||||
q.isResolved()
|
||||
objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
|
||||
}
|
||||
|
||||
def "Remembers first response"() {
|
||||
setup:
|
||||
def q = Spy(new BroadcastQuorum(3))
|
||||
def q = Spy(new BroadcastQuorum())
|
||||
def upstream1 = Stub(Upstream)
|
||||
def upstream2 = Stub(Upstream)
|
||||
def upstream3 = Stub(Upstream)
|
||||
|
||||
when:
|
||||
q.init(Stub(Head))
|
||||
then:
|
||||
!q.isResolved()
|
||||
|
||||
when:
|
||||
q.record(new JsonRpcException(1, "Internal error"), null, upstream1)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordError(_, _, _, _)
|
||||
|
||||
when:
|
||||
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2)
|
||||
then:
|
||||
!q.isResolved()
|
||||
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _)
|
||||
|
||||
when:
|
||||
@@ -93,21 +78,11 @@ class BroadcastQuorumSpec extends Specification {
|
||||
|
||||
def "Failed if error received 3+ times"() {
|
||||
setup:
|
||||
def quorum = new BroadcastQuorum(3)
|
||||
def quorum = new BroadcastQuorum()
|
||||
def up = Stub(Upstream)
|
||||
when:
|
||||
quorum.record(new JsonRpcException(1, "test 1"), null, up)
|
||||
then:
|
||||
!quorum.isFailed()
|
||||
!quorum.isResolved()
|
||||
|
||||
when:
|
||||
quorum.record(new JsonRpcException(1, "test 2"), null, up)
|
||||
then:
|
||||
!quorum.isFailed()
|
||||
!quorum.isResolved()
|
||||
|
||||
when:
|
||||
quorum.record(new JsonRpcException(1, "test 3"), null, up)
|
||||
then:
|
||||
quorum.isFailed()
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package io.emeraldpay.dshackle.quorum
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
|
||||
import spock.lang.Specification
|
||||
|
||||
class MaximumValueQuorumSpec extends Specification {
|
||||
def "selects maximum from 3 values"() {
|
||||
setup:
|
||||
def up = Mock(Upstream) {
|
||||
getId() >> "id"
|
||||
}
|
||||
def up1 = Mock(Upstream) {
|
||||
getId() >> "id1"
|
||||
}
|
||||
def up2 = Mock(Upstream) {
|
||||
getId() >> "id2"
|
||||
}
|
||||
when:
|
||||
def quorum = new MaximumValueQuorum()
|
||||
quorum.record('"0x137"'.bytes, null, up)
|
||||
quorum.record('"0x138"'.bytes, null, up1)
|
||||
quorum.record('"0x139"'.bytes, null, up2)
|
||||
then:
|
||||
quorum.result == '"0x139"'.bytes
|
||||
quorum.resolvedBy.size() == 1
|
||||
quorum.isResolved()
|
||||
quorum.resolvedBy.contains(up2)
|
||||
}
|
||||
|
||||
def "selects maximum from 2 values and an error"() {
|
||||
setup:
|
||||
def up = Mock(Upstream) {
|
||||
getId() >> "id"
|
||||
}
|
||||
def up1 = Mock(Upstream) {
|
||||
getId() >> "id1"
|
||||
}
|
||||
def up2 = Mock(Upstream) {
|
||||
getId() >> "id2"
|
||||
}
|
||||
when:
|
||||
def quorum = new MaximumValueQuorum()
|
||||
quorum.record('"0x137"'.bytes, null, up)
|
||||
quorum.record('"0x138"'.bytes, null, up1)
|
||||
quorum.record(new JsonRpcException(10, "error"), null, up2)
|
||||
then:
|
||||
quorum.result == '"0x138"'.bytes
|
||||
quorum.isResolved()
|
||||
quorum.resolvedBy.size() == 1
|
||||
quorum.resolvedBy.contains(up1)
|
||||
}
|
||||
|
||||
def "returns error is all error"() {
|
||||
setup:
|
||||
def up = Mock(Upstream) {
|
||||
getId() >> "id"
|
||||
}
|
||||
def up1 = Mock(Upstream) {
|
||||
getId() >> "id1"
|
||||
}
|
||||
def up2 = Mock(Upstream) {
|
||||
getId() >> "id2"
|
||||
}
|
||||
when:
|
||||
def quorum = new MaximumValueQuorum()
|
||||
quorum.record(new JsonRpcException(10, "error1"), null, up)
|
||||
quorum.record(new JsonRpcException(10, "error2"), null, up1)
|
||||
quorum.record(new JsonRpcException(10, "error3"), null, up2)
|
||||
then:
|
||||
!quorum.isResolved()
|
||||
quorum.isFailed()
|
||||
quorum.error == new JsonRpcException(10, "error3").error
|
||||
quorum.resolvedBy.size() == 3
|
||||
}
|
||||
|
||||
}
|
||||
@@ -77,11 +77,6 @@ class ValueAwareQuorumSpec extends Specification {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
void init(@NotNull Head head) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isResolved() {
|
||||
return false
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.emeraldpay.dshackle.reader
|
||||
|
||||
import io.emeraldpay.dshackle.quorum.BroadcastQuorum
|
||||
import io.emeraldpay.dshackle.upstream.Selector
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
|
||||
@@ -41,7 +42,7 @@ class BroadcastReaderSpec extends Specification {
|
||||
Mono.just(new JsonRpcResponse(result, null))
|
||||
}
|
||||
}
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer))
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
|
||||
then:
|
||||
@@ -79,7 +80,7 @@ class BroadcastReaderSpec extends Specification {
|
||||
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
|
||||
Mono.error(new JsonRpcException(1, "too low")) }
|
||||
}
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer))
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
|
||||
then:
|
||||
@@ -112,7 +113,7 @@ class BroadcastReaderSpec extends Specification {
|
||||
0 * getId() >> "id"
|
||||
0 * getIngressReader() >> Mock(Reader)
|
||||
}
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer))
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
|
||||
then:
|
||||
@@ -150,7 +151,7 @@ class BroadcastReaderSpec extends Specification {
|
||||
Mono.error(new JsonRpcException(1, "too low"))
|
||||
}
|
||||
}
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer))
|
||||
when:
|
||||
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
|
||||
then:
|
||||
@@ -176,17 +177,14 @@ class BroadcastReaderSpec extends Specification {
|
||||
0 * getId() >> "id"
|
||||
0 * getIngressReader() >> Mock(Reader)
|
||||
}
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
|
||||
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, new BroadcastQuorum(), Stub(Tracer))
|
||||
when:
|
||||
def act = reader
|
||||
.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
|
||||
.switchIfEmpty(Mono.just(new RpcReader.Result(new byte[0], null, 0, null)))
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches {
|
||||
it.value == new byte[0]
|
||||
}
|
||||
.expectComplete()
|
||||
.expectErrorMessage("Unhandled Upstream error")
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user