Resubscribe to newHeads immediately after reconnection (#189)
* Resubscribe to newHeads immediately after reconnection
This commit is contained in:
@@ -26,6 +26,7 @@ import io.emeraldpay.etherjar.rpc.json.BlockJson
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.publisher.Sinks
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -60,7 +61,9 @@ class EthereumWsHeadSpec extends Specification {
|
||||
def apiMock = TestingCommons.api()
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], block)
|
||||
|
||||
def ws = Mock(WsSubscriptions)
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
|
||||
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, false)
|
||||
|
||||
@@ -73,9 +76,9 @@ class EthereumWsHeadSpec extends Specification {
|
||||
act.transactions[0].toHexWithPrefix() == "0x29229361dc5aa1ec66c323dc7a299e2b61a8c8dd2a3522d41255ec10eca25dd8"
|
||||
act.transactions[1].toHexWithPrefix() == "0xebe8f22a55a9e26892a8545b93cbb2bfa4fd81c3184e50e5cf6276025bb42b93"
|
||||
|
||||
1 * ws.subscribe("newHeads") >> Flux.fromIterable([
|
||||
headBlock
|
||||
])
|
||||
1 * ws.subscribe("newHeads") >> new WsSubscriptions.SubscribeData(
|
||||
Flux.fromIterable([headBlock]), "id"
|
||||
)
|
||||
}
|
||||
|
||||
def "Restart ethereum ws head"() {
|
||||
@@ -105,7 +108,11 @@ class EthereumWsHeadSpec extends Specification {
|
||||
apiMock.answerOnce("eth_blockNumber", [], Mono.empty())
|
||||
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
2 * subscribe("newHeads") >>> [Flux.fromIterable([firstHeadBlock]), Flux.fromIterable([secondHeadBlock])]
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
2 * subscribe("newHeads") >>> [
|
||||
new WsSubscriptions.SubscribeData(Flux.fromIterable([firstHeadBlock]), "id"),
|
||||
new WsSubscriptions.SubscribeData(Flux.fromIterable([secondHeadBlock]), "id")
|
||||
]
|
||||
}
|
||||
|
||||
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true)
|
||||
@@ -122,4 +129,55 @@ class EthereumWsHeadSpec extends Specification {
|
||||
.thenCancel()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
def "Restart ethereum ws head immediately after reconnection"() {
|
||||
setup:
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block.number = 103
|
||||
block.parentHash = parent
|
||||
block.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
||||
def secondBlock = new BlockJson<TransactionRefJson>()
|
||||
secondBlock.parentHash = parent
|
||||
secondBlock.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
secondBlock.number = 105
|
||||
secondBlock.hash = BlockHash.from("0x29229361dc5aa1ec66c323dc7a299e2b61a8c8dd2a3522d41255ec10eca25dd8")
|
||||
|
||||
def firstHeadBlock = block.with {
|
||||
Global.objectMapper.writeValueAsBytes(it)
|
||||
}
|
||||
def secondHeadBlock = secondBlock.with {
|
||||
Global.objectMapper.writeValueAsBytes(it)
|
||||
}
|
||||
|
||||
def apiMock = TestingCommons.api()
|
||||
def connectionInfoSink = Sinks.many().multicast().directBestEffort()
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], null)
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x29229361dc5aa1ec66c323dc7a299e2b61a8c8dd2a3522d41255ec10eca25dd8", false], null)
|
||||
apiMock.answerOnce("eth_blockNumber", [], Mono.empty())
|
||||
apiMock.answerOnce("eth_blockNumber", [], Mono.empty())
|
||||
|
||||
def ws = Mock(WsSubscriptions) {
|
||||
1 * it.connectionInfoFlux() >> connectionInfoSink.asFlux()
|
||||
2 * subscribe("newHeads") >>> [
|
||||
new WsSubscriptions.SubscribeData(Flux.fromIterable([firstHeadBlock]), "id"),
|
||||
new WsSubscriptions.SubscribeData(Flux.fromIterable([secondHeadBlock]), "id")
|
||||
]
|
||||
}
|
||||
|
||||
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true)
|
||||
|
||||
when:
|
||||
def act = head.getFlux()
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.then { head.start() }
|
||||
.expectNext(BlockContainer.from(block))
|
||||
.then { connectionInfoSink.tryEmitNext(new WsConnection.ConnectionInfo("id", WsConnection.ConnectionState.DISCONNECTED)) }
|
||||
.then { connectionInfoSink.tryEmitNext(new WsConnection.ConnectionInfo("id", WsConnection.ConnectionState.CONNECTED)) }
|
||||
.expectNext(BlockContainer.from(secondBlock))
|
||||
.thenCancel()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||
import reactor.core.publisher.Flux
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
@@ -24,7 +25,9 @@ class WsConnectionMultiPoolSpec extends Specification {
|
||||
|
||||
def "create connection when less than required"() {
|
||||
setup:
|
||||
def conn = Mock(WsConnection)
|
||||
def conn = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def up = Mock(DefaultUpstream)
|
||||
def factory = Mock(EthereumWsConnectionFactory)
|
||||
def pool = new WsConnectionMultiPool(factory, up, 3)
|
||||
@@ -40,9 +43,15 @@ class WsConnectionMultiPoolSpec extends Specification {
|
||||
|
||||
def "create connection until target"() {
|
||||
setup:
|
||||
def conn1 = Mock(WsConnection)
|
||||
def conn2 = Mock(WsConnection)
|
||||
def conn3 = Mock(WsConnection)
|
||||
def conn1 = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def conn2 = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def conn3 = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def up = Mock(DefaultUpstream)
|
||||
def factory = Mock(EthereumWsConnectionFactory)
|
||||
def pool = new WsConnectionMultiPool(factory, up, 3)
|
||||
@@ -84,10 +93,18 @@ class WsConnectionMultiPoolSpec extends Specification {
|
||||
|
||||
def "recreate connection after failure"() {
|
||||
setup:
|
||||
def conn1 = Mock(WsConnection)
|
||||
def conn2 = Mock(WsConnection)
|
||||
def conn3 = Mock(WsConnection)
|
||||
def conn4 = Mock(WsConnection)
|
||||
def conn1 = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def conn2 = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def conn3 = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def conn4 = Mock(WsConnection) {
|
||||
1 * it.connectionInfoFlux() >> Flux.empty()
|
||||
}
|
||||
def up = Mock(DefaultUpstream)
|
||||
def factory = Mock(EthereumWsConnectionFactory)
|
||||
def pool = new WsConnectionMultiPool(factory, up, 3)
|
||||
|
||||
@@ -36,7 +36,9 @@ class WsSubscriptionsImplSpec extends Specification {
|
||||
]
|
||||
)
|
||||
|
||||
def conn = Mock(WsConnection)
|
||||
def conn = Mock(WsConnection) {
|
||||
1 * it.connectionId() >> "id"
|
||||
}
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> conn
|
||||
}
|
||||
@@ -44,6 +46,7 @@ class WsSubscriptionsImplSpec extends Specification {
|
||||
|
||||
when:
|
||||
def act = ws.subscribe("foo_bar")
|
||||
.data
|
||||
.map { new String(it) }
|
||||
.take(3)
|
||||
.collectList().block(Duration.ofSeconds(1))
|
||||
@@ -71,7 +74,9 @@ class WsSubscriptionsImplSpec extends Specification {
|
||||
]
|
||||
)
|
||||
|
||||
def conn = Mock(WsConnection)
|
||||
def conn = Mock(WsConnection) {
|
||||
1 * it.connectionId() >> "id"
|
||||
}
|
||||
def pool = Mock(WsConnectionPool) {
|
||||
getConnection() >> conn
|
||||
}
|
||||
@@ -79,6 +84,7 @@ class WsSubscriptionsImplSpec extends Specification {
|
||||
|
||||
when:
|
||||
def act = ws.subscribe("foo_bar")
|
||||
.data
|
||||
.map { new String(it) }
|
||||
.take(3)
|
||||
.collectList().block(Duration.ofSeconds(1))
|
||||
|
||||
@@ -40,7 +40,9 @@ class WebsocketPendingTxesSpec extends Specification {
|
||||
.collectList().block(Duration.ofSeconds(1))
|
||||
|
||||
then:
|
||||
1 * ws.subscribe("newPendingTransactions") >> Flux.fromIterable(responses)
|
||||
1 * ws.subscribe("newPendingTransactions") >> new WsSubscriptions.SubscribeData(
|
||||
Flux.fromIterable(responses), "id"
|
||||
)
|
||||
txes.collect {it.toHex() } == [
|
||||
"0xa61bab14fc9720ea8725622688c2f964666d7c2afdae38af7dad53f12f242d5c",
|
||||
"0x911548eb0f3bf353a54e03a3506c7c3e747470d6c201f03babbc07ff6e14cd6e",
|
||||
|
||||
Reference in New Issue
Block a user