solution: mark Upstream status right after WS connect/disconnect
This commit is contained in:
@@ -61,7 +61,7 @@ abstract class DefaultUpstream(
|
|||||||
return status.get().status
|
return status.get().status
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setStatus(avail: UpstreamAvailability) {
|
open fun setStatus(avail: UpstreamAvailability) {
|
||||||
status.updateAndGet { curr ->
|
status.updateAndGet { curr ->
|
||||||
Status(curr.lag, avail, statusByLag(curr.lag, avail))
|
Status(curr.lag, avail, statusByLag(curr.lag, avail))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ open class EthereumRpcUpstream(
|
|||||||
|
|
||||||
open fun createHead(): Head {
|
open fun createHead(): Head {
|
||||||
return if (ethereumWsFactory != null) {
|
return if (ethereumWsFactory != null) {
|
||||||
val ws = ethereumWsFactory.create(null).apply {
|
// do not set upstream to the WS, since it doesn't control the RPC upstream
|
||||||
|
val ws = ethereumWsFactory.create(null, null, null).apply {
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
val wsHead = EthereumWsHead(ws).apply {
|
val wsHead = EthereumWsHead(ws).apply {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import reactor.core.scheduler.Schedulers
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class EthereumUpstreamValidator(
|
open class EthereumUpstreamValidator(
|
||||||
private val upstream: EthereumUpstream,
|
private val upstream: EthereumUpstream,
|
||||||
private val options: UpstreamsConfig.Options
|
private val options: UpstreamsConfig.Options
|
||||||
) {
|
) {
|
||||||
@@ -43,7 +43,7 @@ class EthereumUpstreamValidator(
|
|||||||
|
|
||||||
private val objectMapper: ObjectMapper = Global.objectMapper
|
private val objectMapper: ObjectMapper = Global.objectMapper
|
||||||
|
|
||||||
fun validate(): Mono<UpstreamAvailability> {
|
open fun validate(): Mono<UpstreamAvailability> {
|
||||||
return upstream
|
return upstream
|
||||||
.getApi()
|
.getApi()
|
||||||
.read(JsonRpcRequest("eth_syncing", listOf()))
|
.read(JsonRpcRequest("eth_syncing", listOf()))
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import io.emeraldpay.dshackle.Global
|
|||||||
import io.emeraldpay.dshackle.SilentException
|
import io.emeraldpay.dshackle.SilentException
|
||||||
import io.emeraldpay.dshackle.config.AuthConfig
|
import io.emeraldpay.dshackle.config.AuthConfig
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.ResponseWSParser
|
import io.emeraldpay.dshackle.upstream.rpcclient.ResponseWSParser
|
||||||
@@ -64,15 +66,17 @@ class EthereumWsFactory(
|
|||||||
|
|
||||||
var basicAuth: AuthConfig.ClientBasicAuth? = null
|
var basicAuth: AuthConfig.ClientBasicAuth? = null
|
||||||
|
|
||||||
fun create(rpcMetrics: RpcMetrics?): EthereumWs {
|
fun create(upstream: DefaultUpstream?, validator: EthereumUpstreamValidator?, rpcMetrics: RpcMetrics?): EthereumWs {
|
||||||
return EthereumWs(uri, origin, basicAuth, rpcMetrics)
|
return EthereumWs(uri, origin, basicAuth, rpcMetrics, upstream, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
class EthereumWs(
|
class EthereumWs(
|
||||||
private val uri: URI,
|
private val uri: URI,
|
||||||
private val origin: URI,
|
private val origin: URI,
|
||||||
private val basicAuth: AuthConfig.ClientBasicAuth?,
|
private val basicAuth: AuthConfig.ClientBasicAuth?,
|
||||||
private val rpcMetrics: RpcMetrics?
|
private val rpcMetrics: RpcMetrics?,
|
||||||
|
private val upstream: DefaultUpstream?,
|
||||||
|
private val validator: EthereumUpstreamValidator?
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -154,6 +158,8 @@ class EthereumWsFactory(
|
|||||||
connection = HttpClient.create()
|
connection = HttpClient.create()
|
||||||
.doOnDisconnected {
|
.doOnDisconnected {
|
||||||
log.info("Disconnected from $uri")
|
log.info("Disconnected from $uri")
|
||||||
|
// mark upstream as UNAVAIL
|
||||||
|
upstream?.setStatus(UpstreamAvailability.UNAVAILABLE)
|
||||||
if (keepConnection) {
|
if (keepConnection) {
|
||||||
tryReconnectLater()
|
tryReconnectLater()
|
||||||
}
|
}
|
||||||
@@ -203,6 +209,9 @@ class EthereumWsFactory(
|
|||||||
//restart backoff after connection
|
//restart backoff after connection
|
||||||
currentBackOff = reconnectBackoff.start()
|
currentBackOff = reconnectBackoff.start()
|
||||||
|
|
||||||
|
//validate the connection, it can also be UNAVAIL if market as such after disconnect
|
||||||
|
validator?.validate()
|
||||||
|
|
||||||
val consumer = inbound
|
val consumer = inbound
|
||||||
// Accept up to 15Mb messages, same config is used by Geth
|
// Accept up to 15Mb messages, same config is used by Geth
|
||||||
.aggregateFrames(15 * 1024 * 1024)
|
.aggregateFrames(15 * 1024 * 1024)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class EthereumWsUpstream(
|
|||||||
private val api: JsonRpcWsClient
|
private val api: JsonRpcWsClient
|
||||||
|
|
||||||
private var validatorSubscription: Disposable? = null
|
private var validatorSubscription: Disposable? = null
|
||||||
|
private val validator: EthereumUpstreamValidator
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val metricsTags = listOf(
|
val metricsTags = listOf(
|
||||||
@@ -72,7 +73,9 @@ class EthereumWsUpstream(
|
|||||||
.register(Metrics.globalRegistry)
|
.register(Metrics.globalRegistry)
|
||||||
)
|
)
|
||||||
|
|
||||||
connection = ethereumWsFactory.create(metrics)
|
validator = EthereumUpstreamValidator(this, getOptions())
|
||||||
|
|
||||||
|
connection = ethereumWsFactory.create(this, validator, metrics)
|
||||||
head = EthereumWsHead(connection)
|
head = EthereumWsHead(connection)
|
||||||
api = JsonRpcWsClient(connection)
|
api = JsonRpcWsClient(connection)
|
||||||
}
|
}
|
||||||
@@ -102,7 +105,6 @@ class EthereumWsUpstream(
|
|||||||
head.start()
|
head.start()
|
||||||
|
|
||||||
log.debug("Start validation for upstream ${this.getId()}")
|
log.debug("Start validation for upstream ${this.getId()}")
|
||||||
val validator = EthereumUpstreamValidator(this, getOptions())
|
|
||||||
validatorSubscription = validator.start()
|
validatorSubscription = validator.start()
|
||||||
.subscribe(this::setStatus)
|
.subscribe(this::setStatus)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.test.MockWSServer
|
import io.emeraldpay.dshackle.test.MockWSServer
|
||||||
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import reactor.test.StepVerifier
|
import reactor.test.StepVerifier
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
@@ -28,7 +30,7 @@ class EthereumWsFactoryRealSpec extends Specification {
|
|||||||
server = new MockWSServer(port)
|
server = new MockWSServer(port)
|
||||||
server.start()
|
server.start()
|
||||||
Thread.sleep(SLEEP)
|
Thread.sleep(SLEEP)
|
||||||
conn = new EthereumWsFactory("ws://localhost:${port}".toURI(), "http://localhost:${port}".toURI()).create(null)
|
conn = new EthereumWsFactory("ws://localhost:${port}".toURI(), "http://localhost:${port}".toURI()).create(null, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
def cleanup() {
|
def cleanup() {
|
||||||
@@ -91,6 +93,33 @@ class EthereumWsFactoryRealSpec extends Specification {
|
|||||||
act[0].value.contains("\"params\":[\"newHeads\"]")
|
act[0].value.contains("\"params\":[\"newHeads\"]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "Gets UNAVAIL status right after disconnect"() {
|
||||||
|
setup:
|
||||||
|
def up = Mock(DefaultUpstream)
|
||||||
|
conn = new EthereumWsFactory("ws://localhost:${port}".toURI(), "http://localhost:${port}".toURI()).create(up, null, null)
|
||||||
|
when:
|
||||||
|
conn.connect()
|
||||||
|
conn.reconnectIntervalSeconds = 10
|
||||||
|
Thread.sleep(SLEEP)
|
||||||
|
server.stop()
|
||||||
|
Thread.sleep(100)
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * up.setStatus(UpstreamAvailability.UNAVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Validates after connect"() {
|
||||||
|
setup:
|
||||||
|
def validator = Mock(EthereumUpstreamValidator)
|
||||||
|
conn = new EthereumWsFactory("ws://localhost:${port}".toURI(), "http://localhost:${port}".toURI()).create(null, validator, null)
|
||||||
|
when:
|
||||||
|
conn.connect()
|
||||||
|
Thread.sleep(100)
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * validator.validate()
|
||||||
|
}
|
||||||
|
|
||||||
def "Try to connects to server until it's available"() {
|
def "Try to connects to server until it's available"() {
|
||||||
when:
|
when:
|
||||||
server.stop()
|
server.stop()
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class EthereumWsFactorySpec extends Specification {
|
|||||||
|
|
||||||
def apiMock = TestingCommons.api()
|
def apiMock = TestingCommons.api()
|
||||||
def wsApiMock = apiMock.asWebsocket()
|
def wsApiMock = apiMock.asWebsocket()
|
||||||
def ws = wsf.create()
|
def ws = wsf.create(null, null, null)
|
||||||
|
|
||||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], block)
|
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], block)
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class EthereumWsFactorySpec extends Specification {
|
|||||||
def wsf = new EthereumWsFactory(new URI("http://localhost"), new URI("http://localhost"))
|
def wsf = new EthereumWsFactory(new URI("http://localhost"), new URI("http://localhost"))
|
||||||
def apiMock = TestingCommons.api()
|
def apiMock = TestingCommons.api()
|
||||||
def wsApiMock = apiMock.asWebsocket()
|
def wsApiMock = apiMock.asWebsocket()
|
||||||
def ws = wsf.create()
|
def ws = wsf.create(null, null, null)
|
||||||
|
|
||||||
def tx = new TransactionJson().tap {
|
def tx = new TransactionJson().tap {
|
||||||
hash = TransactionId.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
hash = TransactionId.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
||||||
@@ -99,7 +99,7 @@ class EthereumWsFactorySpec extends Specification {
|
|||||||
def wsf = new EthereumWsFactory(new URI("http://localhost"), new URI("http://localhost"))
|
def wsf = new EthereumWsFactory(new URI("http://localhost"), new URI("http://localhost"))
|
||||||
def apiMock = TestingCommons.api()
|
def apiMock = TestingCommons.api()
|
||||||
def wsApiMock = apiMock.asWebsocket()
|
def wsApiMock = apiMock.asWebsocket()
|
||||||
def ws = wsf.create()
|
def ws = wsf.create(null, null, null)
|
||||||
|
|
||||||
apiMock.answerOnce("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], null)
|
apiMock.answerOnce("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"], null)
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ class EthereumWsFactorySpec extends Specification {
|
|||||||
def wsf = new EthereumWsFactory(new URI("http://localhost"), new URI("http://localhost"))
|
def wsf = new EthereumWsFactory(new URI("http://localhost"), new URI("http://localhost"))
|
||||||
def apiMock = TestingCommons.api()
|
def apiMock = TestingCommons.api()
|
||||||
def wsApiMock = apiMock.asWebsocket()
|
def wsApiMock = apiMock.asWebsocket()
|
||||||
def ws = wsf.create()
|
def ws = wsf.create(null, null, null)
|
||||||
|
|
||||||
apiMock.answerOnce("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"],
|
apiMock.answerOnce("eth_getTransactionByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200"],
|
||||||
new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "test"))
|
new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "test"))
|
||||||
|
|||||||
Reference in New Issue
Block a user