Merge pull request #120 from p2p-org/provided-upstream-id

propagate grpc provided upstream id
This commit is contained in:
a10zn8
2023-01-24 20:10:35 +04:00
committed by GitHub
21 changed files with 217 additions and 102 deletions

View File

@@ -29,6 +29,7 @@ open class AlwaysQuorum : CallQuorum {
private var result: ByteArray? = null private var result: ByteArray? = null
private var rpcError: JsonRpcError? = null private var rpcError: JsonRpcError? = null
private var sig: ResponseSigner.Signature? = null private var sig: ResponseSigner.Signature? = null
private var providedUpstreamId: String? = null
private val resolvers: MutableCollection<Upstream> = ConcurrentLinkedQueue() private val resolvers: MutableCollection<Upstream> = ConcurrentLinkedQueue()
override fun init(head: Head) { override fun init(head: Head) {
@@ -46,15 +47,29 @@ open class AlwaysQuorum : CallQuorum {
return sig return sig
} }
override fun record(response: ByteArray, signature: ResponseSigner.Signature?, upstream: Upstream): Boolean { override fun getProvidedUpstreamId(): String? {
return providedUpstreamId
}
override fun record(
response: ByteArray,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
): Boolean {
result = response result = response
resolved = true resolved = true
sig = signature sig = signature
resolvers.add(upstream) resolvers.add(upstream)
this.providedUpstreamId = providedUpstreamId
return true return true
} }
override fun record(error: JsonRpcException, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun record(
error: JsonRpcException,
signature: ResponseSigner.Signature?,
upstream: Upstream
) {
this.rpcError = error.error this.rpcError = error.error
sig = signature sig = signature
} }

View File

@@ -28,6 +28,7 @@ open class BroadcastQuorum(
private var txid: String? = null private var txid: String? = null
private var calls = 0 private var calls = 0
private var sig: ResponseSigner.Signature? = null private var sig: ResponseSigner.Signature? = null
private var providedUpstreamId: String? = null
override fun init(head: Head) { override fun init(head: Head) {
} }
@@ -48,21 +49,39 @@ open class BroadcastQuorum(
return sig return sig
} }
override fun recordValue(response: ByteArray, responseValue: String?, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun getProvidedUpstreamId(): String? {
return providedUpstreamId
}
override fun recordValue(
response: ByteArray,
responseValue: String?,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
) {
calls++ calls++
if (txid == null && responseValue != null) { if (txid == null && responseValue != null) {
txid = responseValue txid = responseValue
sig = signature sig = signature
result = response result = response
this.providedUpstreamId = providedUpstreamId
} }
} }
override fun recordError(response: ByteArray?, errorMessage: String?, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun recordError(
response: ByteArray?,
errorMessage: String?,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
) {
// can be "message: known transaction: TXID", "Transaction with the same hash was already imported" or "message: Nonce too low" // can be "message: known transaction: TXID", "Transaction with the same hash was already imported" or "message: Nonce too low"
calls++ calls++
if (result == null) { if (result == null) {
result = response result = response
sig = signature sig = signature
this.providedUpstreamId = providedUpstreamId
} }
} }

View File

@@ -29,9 +29,20 @@ interface CallQuorum {
fun isResolved(): Boolean fun isResolved(): Boolean
fun isFailed(): Boolean fun isFailed(): Boolean
fun record(response: ByteArray, signature: ResponseSigner.Signature?, upstream: Upstream): Boolean fun record(
fun record(error: JsonRpcException, signature: ResponseSigner.Signature?, upstream: Upstream) response: ByteArray,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
): Boolean
fun record(
error: JsonRpcException,
signature: ResponseSigner.Signature?,
upstream: Upstream
)
fun getSignature(): ResponseSigner.Signature? fun getSignature(): ResponseSigner.Signature?
fun getProvidedUpstreamId(): String?
fun getResult(): ByteArray? fun getResult(): ByteArray?
fun getError(): JsonRpcError? fun getError(): JsonRpcError?
fun getResolvedBy(): Collection<Upstream> fun getResolvedBy(): Collection<Upstream>

View File

@@ -27,6 +27,7 @@ open class NonEmptyQuorum(
private var result: ByteArray? = null private var result: ByteArray? = null
private var tries: Int = 0 private var tries: Int = 0
private var sig: ResponseSigner.Signature? = null private var sig: ResponseSigner.Signature? = null
private var providedUpstreamId: String? = null
override fun init(head: Head) { override fun init(head: Head) {
} }
@@ -42,11 +43,22 @@ open class NonEmptyQuorum(
return sig return sig
} }
override fun recordValue(response: ByteArray, responseValue: Any?, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun getProvidedUpstreamId(): String? {
return providedUpstreamId
}
override fun recordValue(
response: ByteArray,
responseValue: Any?,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
) {
tries++ tries++
if (responseValue != null) { if (responseValue != null) {
result = response result = response
sig = signature sig = signature
this.providedUpstreamId = providedUpstreamId
} }
} }
@@ -54,7 +66,13 @@ open class NonEmptyQuorum(
return result return result
} }
override fun recordError(response: ByteArray?, errorMessage: String?, sig: ResponseSigner.Signature?, upstream: Upstream) { override fun recordError(
response: ByteArray?,
errorMessage: String?,
sig: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
) {
tries++ tries++
} }

View File

@@ -33,6 +33,7 @@ open class NonceQuorum(
private var receivedTimes = 0 private var receivedTimes = 0
private var errors = 0 private var errors = 0
private var sig: ResponseSigner.Signature? = null private var sig: ResponseSigner.Signature? = null
private var providedUpstreamId: String? = null
override fun init(head: Head) { override fun init(head: Head) {
} }
@@ -50,7 +51,17 @@ open class NonceQuorum(
return sig return sig
} }
override fun recordValue(response: ByteArray, responseValue: String?, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun getProvidedUpstreamId(): String? {
return providedUpstreamId
}
override fun recordValue(
response: ByteArray,
responseValue: String?,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
) {
val value = responseValue?.let { str -> val value = responseValue?.let { str ->
HexQuantity.from(str).value.toLong() HexQuantity.from(str).value.toLong()
} }
@@ -60,9 +71,11 @@ open class NonceQuorum(
resultValue = value resultValue = value
result = response result = response
sig = signature sig = signature
this.providedUpstreamId = providedUpstreamId
} else if (result == null) { } else if (result == null) {
result = response result = response
sig = signature sig = signature
this.providedUpstreamId = providedUpstreamId
} }
} }
} }
@@ -71,7 +84,13 @@ open class NonceQuorum(
return result return result
} }
override fun recordError(response: ByteArray?, errorMessage: String?, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun recordError(
response: ByteArray?,
errorMessage: String?,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
) {
errors++ errors++
} }

View File

@@ -36,6 +36,7 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum {
private var rpcError: JsonRpcError? = null private var rpcError: JsonRpcError? = null
private var sig: ResponseSigner.Signature? = null private var sig: ResponseSigner.Signature? = null
private val resolvers: MutableCollection<Upstream> = ConcurrentLinkedQueue() private val resolvers: MutableCollection<Upstream> = ConcurrentLinkedQueue()
private var providedUpstreamId: String? = null
override fun init(head: Head) { override fun init(head: Head) {
} }
@@ -48,18 +49,28 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum {
return failed.get() return failed.get()
} }
override fun record(response: ByteArray, signature: ResponseSigner.Signature?, upstream: Upstream): Boolean { override fun record(
response: ByteArray,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
): Boolean {
val lagging = upstream.getLag() > maxLag val lagging = upstream.getLag() > maxLag
if (!lagging) { if (!lagging) {
result.set(response) result.set(response)
sig = signature sig = signature
this.providedUpstreamId = providedUpstreamId
resolvers.add(upstream) resolvers.add(upstream)
return true return true
} }
return false return false
} }
override fun record(error: JsonRpcException, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun record(
error: JsonRpcException,
signature: ResponseSigner.Signature?,
upstream: Upstream
) {
this.rpcError = error.error this.rpcError = error.error
val lagging = upstream.getLag() > maxLag val lagging = upstream.getLag() > maxLag
if (!lagging && result.get() == null) { if (!lagging && result.get() == null) {
@@ -70,6 +81,11 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum {
override fun getSignature(): ResponseSigner.Signature? { override fun getSignature(): ResponseSigner.Signature? {
return sig return sig
} }
override fun getProvidedUpstreamId(): String? {
return providedUpstreamId
}
override fun getResult(): ByteArray { override fun getResult(): ByteArray {
return result.get() return result.get()
} }

View File

@@ -27,8 +27,8 @@ import io.emeraldpay.etherjar.rpc.RpcException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.util.function.Tuple2
import reactor.util.function.Tuple3 import reactor.util.function.Tuple3
import reactor.util.function.Tuple4
import reactor.util.function.Tuples import reactor.util.function.Tuples
import java.util.Optional import java.util.Optional
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@@ -90,8 +90,8 @@ class QuorumRpcReader(
} }
fun execute(key: JsonRpcRequest, retrySpec: reactor.util.retry.Retry): Function<Flux<Upstream>, Mono<CallQuorum>> { fun execute(key: JsonRpcRequest, retrySpec: reactor.util.retry.Retry): Function<Flux<Upstream>, Mono<CallQuorum>> {
val quorumReduce = BiFunction<CallQuorum, Tuple3<ByteArray, Optional<ResponseSigner.Signature>, Upstream>, CallQuorum> { res, a -> val quorumReduce = BiFunction<CallQuorum, Tuple4<ByteArray, Optional<ResponseSigner.Signature>, Upstream, Optional<String>>, CallQuorum> { res, a ->
if (res.record(a.t1, a.t2.orElse(null), a.t3)) { if (res.record(a.t1, a.t2.orElse(null), a.t3, a.t4.orElse(null))) {
apiControl.resolve() apiControl.resolve()
} else { } else {
// quorum needs more responses, so ask api controller to make another // quorum needs more responses, so ask api controller to make another
@@ -119,25 +119,25 @@ class QuorumRpcReader(
.filter { it.isResolved() } // return nothing if not resolved .filter { it.isResolved() } // return nothing if not resolved
.map { quorum -> .map { quorum ->
// TODO find actual quorum number // TODO find actual quorum number
Result(quorum.getResult()!!, quorum.getSignature(), 1, quorum.getResolvedBy()) Result(quorum.getResult()!!, quorum.getSignature(), 1, quorum.getResolvedBy(), quorum.getProvidedUpstreamId())
} }
.switchIfEmpty(defaultResult) .switchIfEmpty(defaultResult)
} }
} }
fun callApi(api: Upstream, key: JsonRpcRequest): Mono<Tuple3<ByteArray, Optional<ResponseSigner.Signature>, Upstream>> { fun callApi(api: Upstream, key: JsonRpcRequest): Mono<Tuple4<ByteArray, Optional<ResponseSigner.Signature>, Upstream, Optional<String>>> {
return api.getIngressReader() return api.getIngressReader()
.read(key) .read(key)
.flatMap { response -> .flatMap { response ->
response.requireResult() response.requireResult()
.transform(withSignature(api, key, response)) .transform(withSignatureAndUpstream(api, key, response))
} }
// must catch not only the processing of a response but also errors thrown from the .read() call // must catch not only the processing of a response but also errors thrown from the .read() call
.transform(withErrorResume(api, key)) .transform(withErrorResume(api, key))
.map { Tuples.of(it.t1, it.t2, api) } .map { Tuples.of(it.t1, it.t2, api, it.t3) }
} }
fun withSignature(api: Upstream, key: JsonRpcRequest, response: JsonRpcResponse): Function<Mono<ByteArray>, Mono<Tuple2<ByteArray, Optional<ResponseSigner.Signature>>>> { fun withSignatureAndUpstream(api: Upstream, key: JsonRpcRequest, response: JsonRpcResponse): Function<Mono<ByteArray>, Mono<Tuple3<ByteArray, Optional<ResponseSigner.Signature>, Optional<String>>>> {
return Function { src -> return Function { src ->
src.map { src.map {
val signature = response.providedSignature val signature = response.providedSignature
@@ -146,7 +146,7 @@ class QuorumRpcReader(
} else { } else {
null null
} }
Tuples.of(it, Optional.ofNullable(signature)) Tuples.of(it, Optional.ofNullable(signature), Optional.ofNullable(response.providedUpstreamId))
} }
} }
} }
@@ -165,7 +165,7 @@ class QuorumRpcReader(
JsonRpcError(-32603, "Unhandled internal error: ${err.javaClass}: ${err.message}") JsonRpcError(-32603, "Unhandled internal error: ${err.javaClass}: ${err.message}")
) )
} }
quorum.record(cleanErr, null, api) quorum.record(cleanErr, null, api,)
// if it's failed after that, then we don't need more calls, stop api source // if it's failed after that, then we don't need more calls, stop api source
if (quorum.isFailed()) { if (quorum.isFailed()) {
apiControl.resolve() apiControl.resolve()
@@ -198,6 +198,7 @@ class QuorumRpcReader(
val value: ByteArray, val value: ByteArray,
val signature: ResponseSigner.Signature?, val signature: ResponseSigner.Signature?,
val quorum: Int, val quorum: Int,
val resolvers: Collection<Upstream> val resolvers: Collection<Upstream>,
val providedUpstreamId: String?
) )
} }

View File

@@ -37,27 +37,48 @@ abstract class ValueAwareQuorum<T>(
return Global.objectMapper.readValue(response.inputStream(), clazz) return Global.objectMapper.readValue(response.inputStream(), clazz)
} }
override fun record(response: ByteArray, signature: ResponseSigner.Signature?, upstream: Upstream): Boolean { override fun record(
response: ByteArray,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
): Boolean {
try { try {
val value = extractValue(response, clazz) val value = extractValue(response, clazz)
recordValue(response, value, signature, upstream) recordValue(response, value, signature, upstream, providedUpstreamId)
resolvers.add(upstream) resolvers.add(upstream)
} catch (e: RpcException) { } catch (e: RpcException) {
recordError(response, e.rpcMessage, signature, upstream) recordError(response, e.rpcMessage, signature, upstream, providedUpstreamId)
} catch (e: Exception) { } catch (e: Exception) {
recordError(response, e.message, signature, upstream) recordError(response, e.message, signature, upstream, providedUpstreamId)
} }
return isResolved() return isResolved()
} }
override fun record(error: JsonRpcException, signature: ResponseSigner.Signature?, upstream: Upstream) { override fun record(
error: JsonRpcException,
signature: ResponseSigner.Signature?,
upstream: Upstream
) {
this.rpcError = error.error this.rpcError = error.error
recordError(null, error.error.message, signature, upstream) recordError(null, error.error.message, signature, upstream, null)
} }
abstract fun recordValue(response: ByteArray, responseValue: T?, signature: ResponseSigner.Signature?, upstream: Upstream) abstract fun recordValue(
response: ByteArray,
responseValue: T?,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
)
abstract fun recordError(response: ByteArray?, errorMessage: String?, signature: ResponseSigner.Signature?, upstream: Upstream) abstract fun recordError(
response: ByteArray?,
errorMessage: String?,
signature: ResponseSigner.Signature?,
upstream: Upstream,
providedUpstreamId: String?
)
override fun getError(): JsonRpcError? { override fun getError(): JsonRpcError? {
return rpcError return rpcError

View File

@@ -312,7 +312,7 @@ open class NativeCall(
.map { .map {
val bytes = ctx.resultDecorator.processResult(it) val bytes = ctx.resultDecorator.processResult(it)
validateResult(bytes, "remote", ctx) validateResult(bytes, "remote", ctx)
CallResult.ok(ctx.id, ctx.nonce, bytes, it.signature, it.resolvers.first().getId(), ctx) CallResult.ok(ctx.id, ctx.nonce, bytes, it.signature, it.providedUpstreamId ?: it.resolvers.first().getId(), ctx)
} }
.onErrorResume { t -> .onErrorResume { t ->
Mono.just(CallResult.fail(ctx.id, ctx.nonce, t, ctx)) Mono.just(CallResult.fail(ctx.id, ctx.nonce, t, ctx))

View File

@@ -90,7 +90,7 @@ class JsonRpcGrpcClient(
} else { } else {
null null
} }
Mono.just(JsonRpcResponse(bytes, null, JsonRpcResponse.NumberId(0), signature)) Mono.just(JsonRpcResponse(bytes, null, JsonRpcResponse.NumberId(0), signature, resp.upstreamId))
} else { } else {
metrics?.fails?.increment() metrics?.fails?.increment()
Mono.error( Mono.error(

View File

@@ -29,7 +29,8 @@ class JsonRpcResponse(
/** /**
* When making a request through Dshackle protocol a remote may provide its signature with the response, which we keep here * When making a request through Dshackle protocol a remote may provide its signature with the response, which we keep here
*/ */
val providedSignature: ResponseSigner.Signature? = null val providedSignature: ResponseSigner.Signature? = null,
val providedUpstreamId: String? = null
) { ) {
constructor(result: ByteArray?, error: JsonRpcError?) : this(result, error, NumberId(0)) constructor(result: ByteArray?, error: JsonRpcError?) : this(result, error, NumberId(0))
@@ -113,11 +114,7 @@ class JsonRpcResponse(
} }
fun copyWithId(id: Id): JsonRpcResponse { fun copyWithId(id: Id): JsonRpcResponse {
return JsonRpcResponse(result, error, id, providedSignature) return JsonRpcResponse(result, error, id, providedSignature, providedUpstreamId)
}
fun copyWithSignature(signature: ResponseSigner.Signature): JsonRpcResponse {
return JsonRpcResponse(result, error, id, signature)
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@@ -42,7 +42,7 @@ class AlwaysQuorumSpec extends Specification {
def quorum = new AlwaysQuorum() def quorum = new AlwaysQuorum()
def up = Stub(Upstream) def up = Stub(Upstream)
when: when:
quorum.record("123".bytes, new ResponseSigner.Signature("sig1".bytes, "test", 100), up) quorum.record("123".bytes, new ResponseSigner.Signature("sig1".bytes, "test", 100), up, null)
then: then:
quorum.isResolved() quorum.isResolved()
quorum.getResult() == "123".bytes quorum.getResult() == "123".bytes

View File

@@ -43,21 +43,21 @@ class BroadcastQuorumSpec extends Specification {
!q.isResolved() !q.isResolved()
when: when:
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream1) q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream1, null)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _) 1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _, _)
when: when:
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2) q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2, null)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _) 1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _, _)
when: when:
q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3) q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3)
then: then:
1 * q.recordError(_, _, _, _) 1 * q.recordError(_, _, _, _, _)
q.isResolved() q.isResolved()
objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c" objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
} }
@@ -78,18 +78,18 @@ class BroadcastQuorumSpec extends Specification {
q.record(new JsonRpcException(1, "Internal error"), null, upstream1) q.record(new JsonRpcException(1, "Internal error"), null, upstream1)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordError(_, _, _, _) 1 * q.recordError(_, _, _, _, _)
when: when:
q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2) q.record('"0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"'.bytes, null, upstream2, null)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _) 1 * q.recordValue(_, "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c", _, _, _)
when: when:
q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3) q.record(new JsonRpcException(1, "Nonce too low"), null, upstream3)
then: then:
1 * q.recordError(_, _, _, _) 1 * q.recordError(_, _, _, _, _)
q.isResolved() q.isResolved()
objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c" objectMapper.readValue(q.result, Object) == "0xeaa972c0d8d1ecd3e34fbbef6d34e06670e745c788bdba31c4234a1762f0378c"
} }

View File

@@ -61,8 +61,6 @@ class NonEmptyQuorumSpec extends Specification {
setup: setup:
def q = Spy(new NonEmptyQuorum(3)) def q = Spy(new NonEmptyQuorum(3))
def upstream1 = Stub(Upstream) def upstream1 = Stub(Upstream)
def upstream2 = Stub(Upstream)
def upstream3 = Stub(Upstream)
when: when:
q.init(Stub(Head)) q.init(Stub(Head))
@@ -71,7 +69,7 @@ class NonEmptyQuorumSpec extends Specification {
!q.isFailed() !q.isFailed()
when: when:
q.record('"0x11"'.bytes, null, upstream1) q.record('"0x11"'.bytes, null, upstream1, null)
then: then:
q.isResolved() q.isResolved()
!q.isFailed() !q.isFailed()
@@ -82,7 +80,6 @@ class NonEmptyQuorumSpec extends Specification {
def q = Spy(new NonEmptyQuorum(3)) def q = Spy(new NonEmptyQuorum(3))
def upstream1 = Stub(Upstream) def upstream1 = Stub(Upstream)
def upstream2 = Stub(Upstream) def upstream2 = Stub(Upstream)
def upstream3 = Stub(Upstream)
when: when:
q.init(Stub(Head)) q.init(Stub(Head))
@@ -98,7 +95,7 @@ class NonEmptyQuorumSpec extends Specification {
q.signature == null q.signature == null
when: when:
q.record('"0x11"'.bytes, null, upstream2) q.record('"0x11"'.bytes, null, upstream2, null)
then: then:
q.isResolved() q.isResolved()
!q.isFailed() !q.isFailed()
@@ -108,8 +105,6 @@ class NonEmptyQuorumSpec extends Specification {
setup: setup:
def q = Spy(new NonEmptyQuorum(3)) def q = Spy(new NonEmptyQuorum(3))
def upstream1 = Stub(Upstream) def upstream1 = Stub(Upstream)
def upstream2 = Stub(Upstream)
def upstream3 = Stub(Upstream)
when: when:
q.init(Stub(Head)) q.init(Stub(Head))
@@ -118,14 +113,14 @@ class NonEmptyQuorumSpec extends Specification {
!q.isFailed() !q.isFailed()
when: when:
q.record('null'.bytes, null, upstream2) q.record('null'.bytes, null, upstream1, null)
then: then:
!q.isFailed() !q.isFailed()
!q.isResolved() !q.isResolved()
when: when:
q.record('"0x11"'.bytes, null, upstream2) q.record('"0x11"'.bytes, null, upstream1, null)
then: then:
q.isResolved() q.isResolved()
!q.isFailed() !q.isFailed()

View File

@@ -18,12 +18,9 @@ package io.emeraldpay.dshackle.quorum
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.quorum.NonceQuorum
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
import io.emeraldpay.etherjar.rpc.RpcException
import spock.lang.Specification import spock.lang.Specification
class NonceQuorumSpec extends Specification { class NonceQuorumSpec extends Specification {
@@ -43,21 +40,21 @@ class NonceQuorumSpec extends Specification {
!q.isResolved() !q.isResolved()
when: when:
q.record('"0x10"'.bytes, null, upstream1) q.record('"0x10"'.bytes, null, upstream1, null)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordValue(_, "0x10", _, _) 1 * q.recordValue(_, "0x10", _, _, _)
when: when:
q.record('"0x11"'.bytes, null, upstream2) q.record('"0x11"'.bytes, null, upstream2, null)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordValue(_, "0x11", _, _) 1 * q.recordValue(_, "0x11", _, _, _)
when: when:
q.record('"0x10"'.bytes, null, upstream3) q.record('"0x10"'.bytes, null, upstream3, null)
then: then:
1 * q.recordValue(_, "0x10", _, _) 1 * q.recordValue(_, "0x10", _, _, _)
q.isResolved() q.isResolved()
objectMapper.readValue(q.result, Object) == "0x11" objectMapper.readValue(q.result, Object) == "0x11"
} }
@@ -78,24 +75,24 @@ class NonceQuorumSpec extends Specification {
q.record(new JsonRpcException(1, "Internal"), null, upstream1) q.record(new JsonRpcException(1, "Internal"), null, upstream1)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordError(_, _, _, _) 1 * q.recordError(_, _, _, _, _)
when: when:
q.record('"0x11"'.bytes, null, upstream2) q.record('"0x11"'.bytes, null, upstream2, null)
then: then:
!q.isResolved() !q.isResolved()
1 * q.recordValue(_, "0x11", _, _) 1 * q.recordValue(_, "0x11", _, _, _)
when: when:
q.record('"0x10"'.bytes, null, upstream3) q.record('"0x10"'.bytes, null, upstream3, null)
then: then:
1 * q.recordValue(_, "0x10", _, _) 1 * q.recordValue(_, "0x10", _, _, _)
!q.isResolved() !q.isResolved()
when: when:
q.record('"0x11"'.bytes, null, upstream1) q.record('"0x11"'.bytes, null, upstream1, null)
then: then:
1 * q.recordValue(_, "0x11", _, _) 1 * q.recordValue(_, "0x11", _, _, _)
q.isResolved() q.isResolved()
objectMapper.readValue(q.result, Object) == "0x11" objectMapper.readValue(q.result, Object) == "0x11"
} }

View File

@@ -32,7 +32,7 @@ class NotLaggingQuorumSpec extends Specification {
def quorum = new NotLaggingQuorum(1) def quorum = new NotLaggingQuorum(1)
when: when:
quorum.record(value, null, up) quorum.record(value, null, up, null)
then: then:
1 * up.getLag() >> 0 1 * up.getLag() >> 0
quorum.isResolved() quorum.isResolved()
@@ -40,20 +40,21 @@ class NotLaggingQuorumSpec extends Specification {
quorum.result == value quorum.result == value
} }
def "Keeps signature"() { def "Keeps signature and upstream"() {
setup: setup:
def up = Mock(Upstream) def up = Mock(Upstream)
def value = "foo".getBytes() def value = "foo".getBytes()
def quorum = new NotLaggingQuorum(1) def quorum = new NotLaggingQuorum(1)
when: when:
quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up) quorum.record(value, new ResponseSigner.Signature("sig1".bytes, "test", 100), up, "test")
then: then:
1 * up.getLag() >> 0 1 * up.getLag() >> 0
quorum.isResolved() quorum.isResolved()
!quorum.isFailed() !quorum.isFailed()
quorum.result == value quorum.result == value
quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100) quorum.signature == new ResponseSigner.Signature("sig1".bytes, "test", 100)
quorum.providedUpstreamId == "test"
} }
def "Resolves if ok lag"() { def "Resolves if ok lag"() {
@@ -63,7 +64,7 @@ class NotLaggingQuorumSpec extends Specification {
def quorum = new NotLaggingQuorum(1) def quorum = new NotLaggingQuorum(1)
when: when:
quorum.record(value, null, up) quorum.record(value, null, up, null)
then: then:
1 * up.getLag() >> 1 1 * up.getLag() >> 1
quorum.isResolved() quorum.isResolved()
@@ -78,7 +79,7 @@ class NotLaggingQuorumSpec extends Specification {
def quorum = new NotLaggingQuorum(1) def quorum = new NotLaggingQuorum(1)
when: when:
quorum.record(value, null, up) quorum.record(value, null, up, null)
then: then:
1 * up.getLag() >> 2 1 * up.getLag() >> 2
!quorum.isResolved() !quorum.isResolved()

View File

@@ -66,14 +66,14 @@ class ValueAwareQuorumSpec extends Specification {
} }
@Override @Override
void recordValue(@NotNull byte[] response, @Nullable Object responseValue, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) { void recordValue(@NotNull byte[] response, @Nullable Object responseValue, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream, @Nullable String providedUpstreamId) {
} }
@Override @Override
void recordError(@Nullable byte[] response, @Nullable String errorMessage, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream) { void recordError(@Nullable byte[] response, @Nullable String errorMessage, @Nullable ResponseSigner.Signature signature, @NotNull Upstream upstream, @Nullable String providedUpstreamId) {
} }
@@ -101,5 +101,10 @@ class ValueAwareQuorumSpec extends Specification {
boolean isFailed() { boolean isFailed() {
return false return false
} }
@Override
String getProvidedUpstreamId() {
return null
}
} }
} }

View File

@@ -131,7 +131,7 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall() def nativeCall = nativeCall()
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"foo\"".bytes, null, 1, Collections.singletonList(ups))) 1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"foo\"".bytes, null, 1, Collections.singletonList(ups), null))
} }
} }
def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum,
@@ -613,7 +613,7 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall(multistreamHolder) def nativeCall = nativeCall(multistreamHolder)
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, Collections.singletonList(ups))) 1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, Collections.singletonList(ups), null))
} }
} }
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum, def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,
@@ -648,7 +648,7 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall(multistreamHolder) def nativeCall = nativeCall(multistreamHolder)
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, Collections.singletonList(ups))) 1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, Collections.singletonList(ups), null))
} }
} }
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum, def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,

View File

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

View File

@@ -55,7 +55,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers) Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers, null)
) )
} }
} }
@@ -85,7 +85,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolvers Global.objectMapper.writeValueAsBytes(null), null, 1, resolvers, null
) )
) )
} }
@@ -120,7 +120,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers, null
) )
) )
} }
@@ -156,7 +156,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers, null
) )
) )
} }
@@ -192,7 +192,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, new ArrayList<Byte>() Global.objectMapper.writeValueAsBytes(json), null, 1, new ArrayList<Byte>(), null
) )
) )
} }
@@ -229,7 +229,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, new ArrayList<Byte>() Global.objectMapper.writeValueAsBytes(json), null, 1, new ArrayList<Byte>(), null
) )
) )
} }
@@ -257,7 +257,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolvers Global.objectMapper.writeValueAsBytes(null), null, 1, resolvers, null
) )
) )
} }
@@ -288,7 +288,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolvers Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolvers, null
) )
) )
} }
@@ -320,7 +320,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_, _, _) >> Mock(Reader) { 1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "0xa8c9bb"])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBalance", [address1, "0xa8c9bb"])) >> Mono.just(
new QuorumRpcReader.Result( new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolvers Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolvers, null
) )
) )
} }

View File

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