problem: tx broadcast breaks call (and causes timeout) if it's already broadcasted
This commit is contained in:
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.quorum
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
|
||||
@@ -39,6 +40,9 @@ open class AlwaysQuorum: CallQuorum {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
}
|
||||
|
||||
override fun getResult(): ByteArray? {
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ open class BroadcastQuorum(
|
||||
}
|
||||
}
|
||||
|
||||
override fun recordError(response: ByteArray, errorMessage: String?, upstream: Upstream) {
|
||||
// can be "message: known transaction: TXID" or "message: Nonce too low"
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream) {
|
||||
// can be "message: known transaction: TXID", "Transaction with the same hash was already imported" or "message: Nonce too low"
|
||||
calls++
|
||||
if (result == null) {
|
||||
result = response
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.quorum
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import reactor.util.function.Tuple2
|
||||
@@ -30,6 +31,7 @@ interface CallQuorum {
|
||||
|
||||
fun isResolved(): Boolean
|
||||
fun record(response: ByteArray, upstream: Upstream): Boolean
|
||||
fun record(error: RpcException, upstream: Upstream)
|
||||
fun getResult(): ByteArray?
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -19,6 +19,7 @@ import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.JacksonRpcConverter
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
|
||||
@@ -48,7 +49,10 @@ open class NonEmptyQuorum(
|
||||
return result
|
||||
}
|
||||
|
||||
override fun recordError(response: ByteArray, errorMessage: String?, upstream: Upstream) {
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream) {
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.hex.HexQuantity
|
||||
import io.infinitape.etherjar.rpc.JacksonRpcConverter
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@@ -64,7 +65,11 @@ open class NonceQuorum(
|
||||
return result
|
||||
}
|
||||
|
||||
override fun recordError(response: ByteArray, errorMessage: String?, upstream: Upstream) {
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream) {
|
||||
errors++
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
errors++
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.quorum
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
@@ -42,6 +43,10 @@ class NotLaggingQuorum(val maxLag: Long = 0): CallQuorum {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
}
|
||||
|
||||
|
||||
override fun getResult(): ByteArray {
|
||||
return result.get()
|
||||
}
|
||||
|
||||
@@ -43,8 +43,12 @@ abstract class ValueAwareQuorum<T>(
|
||||
return isResolved();
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
recordError(null, error.rpcMessage, upstream)
|
||||
}
|
||||
|
||||
abstract fun recordValue(response: ByteArray, responseValue: T?, upstream: Upstream)
|
||||
|
||||
abstract fun recordError(response: ByteArray, errorMessage: String?, upstream: Upstream)
|
||||
abstract fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream)
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import io.emeraldpay.dshackle.upstream.*
|
||||
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
||||
import io.emeraldpay.dshackle.quorum.CallQuorum
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@@ -123,17 +124,28 @@ class NativeCall(
|
||||
var failures = 0
|
||||
return Flux.from(apis)
|
||||
.flatMap { api ->
|
||||
api.execute(ctx.id, ctx.payload.method, ctx.payload.params).map { Tuples.of(it, api.upstream!!) }
|
||||
val upstream = api.upstream!!
|
||||
api.execute(ctx.id, ctx.payload.method, ctx.payload.params)
|
||||
// on error notify quorum, it may use error message or other details
|
||||
.doOnError { err ->
|
||||
if (err is RpcException) {
|
||||
ctx.callQuorum.record(err, upstream)
|
||||
}
|
||||
}
|
||||
.map { Tuples.of(it, upstream) }
|
||||
}
|
||||
.retry {
|
||||
failures++
|
||||
if (failures <= 3) {
|
||||
if (ctx.callQuorum.isResolved()) {
|
||||
false
|
||||
} else if (failures < 3) {
|
||||
apis.request(1)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
// record all correct responses until quorum reached
|
||||
.reduce(ctx.callQuorum, {res, a ->
|
||||
if (res.record(a.t1, a.t2)) {
|
||||
apis.resolve()
|
||||
@@ -142,6 +154,14 @@ class NativeCall(
|
||||
}
|
||||
res
|
||||
})
|
||||
// if last call resulted in error it's still possible that request was resolved correctly. i.e. for BroadcastQuorum
|
||||
.onErrorResume { err ->
|
||||
if (ctx.callQuorum.isResolved()) {
|
||||
Mono.just(ctx.callQuorum)
|
||||
} else {
|
||||
Mono.error(err)
|
||||
}
|
||||
}
|
||||
.doOnNext {
|
||||
if (!it.isResolved()) {
|
||||
log.debug("No quorum for ${ctx.payload.method} as ${ctx.callQuorum}")
|
||||
|
||||
Reference in New Issue
Block a user