solution: update current head through RPC as well

This commit is contained in:
Igor Artamonov
2019-06-10 18:52:32 -04:00
parent 59ab8b9010
commit e06ce56742
10 changed files with 171 additions and 29 deletions

View File

@@ -30,7 +30,7 @@ class NativeCall(
if (chain == Chain.UNSPECIFIED) { if (chain == Chain.UNSPECIFIED) {
throw Exception("Invalid chain id: ${request.chain.number}") throw Exception("Invalid chain id: ${request.chain.number}")
} }
val upstream = upstreams.ethereumUpstream(chain) ?: throw Exception("Chain ${chain.id} is unavailable") val upstream = upstreams.ethereumUpstream(chain)?.api ?: throw Exception("Chain ${chain.id} is unavailable")
request.itemsList.toFlux() request.itemsList.toFlux()
.map { .map {
val method = it.target val method = it.target

View File

@@ -27,7 +27,7 @@ class StreamHead(
@PostConstruct @PostConstruct
fun init() { fun init() {
listOf(Chain.ETHEREUM, Chain.ETHEREUM_CLASSIC, Chain.MORDEN).forEach { chain -> listOf(Chain.ETHEREUM, Chain.ETHEREUM_CLASSIC, Chain.MORDEN).forEach { chain ->
if (upstreams.ethereumUpstream(chain)?.ws != null) { if (upstreams.ethereumUpstream(chain)?.head != null) {
clients[chain] = ConcurrentLinkedQueue() clients[chain] = ConcurrentLinkedQueue()
subscribe(chain) subscribe(chain)
} }
@@ -35,7 +35,7 @@ class StreamHead(
} }
private fun subscribe(chain: Chain) { private fun subscribe(chain: Chain) {
upstreams.ethereumUpstream(chain)!!.ws!!.getFlux() upstreams.ethereumUpstream(chain)!!.head.getFlux()
.doOnComplete { .doOnComplete {
log.info("Closing streams for ${chain.chainCode}") log.info("Closing streams for ${chain.chainCode}")
clients.replace(chain, ConcurrentLinkedQueue())!!.forEach { client -> clients.replace(chain, ConcurrentLinkedQueue())!!.forEach { client ->
@@ -68,9 +68,10 @@ class StreamHead(
fun process(chain: Chain, client: StreamSender<BlockchainOuterClass.ChainHead>): Boolean { fun process(chain: Chain, client: StreamSender<BlockchainOuterClass.ChainHead>): Boolean {
val upstream = upstreams.ethereumUpstream(chain) ?: return false val upstream = upstreams.ethereumUpstream(chain) ?: return false
val ws = upstream.ws ?: return false val head = upstream.head.getHead()
val head = ws.getHead() ?: return false return head.map {
return notify(chain, head, client) notify(chain, it, client)
}.defaultIfEmpty(false).block()!!
} }
fun notify(chain: Chain, block: BlockJson<TransactionId>, client: StreamSender<BlockchainOuterClass.ChainHead>): Boolean { fun notify(chain: Chain, block: BlockJson<TransactionId>, client: StreamSender<BlockchainOuterClass.ChainHead>): Boolean {

View File

@@ -3,24 +3,27 @@ package io.emeraldpay.dshackle.upstream
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.grpc.Chain import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.hex.HexQuantity import io.infinitape.etherjar.hex.HexQuantity
import io.infinitape.etherjar.rpc.Batch
import io.infinitape.etherjar.rpc.RpcCall import io.infinitape.etherjar.rpc.RpcCall
import io.infinitape.etherjar.rpc.RpcClient import io.infinitape.etherjar.rpc.RpcClient
import io.infinitape.etherjar.rpc.RpcException import io.infinitape.etherjar.rpc.RpcException
import io.infinitape.etherjar.rpc.json.ResponseJson import io.infinitape.etherjar.rpc.json.ResponseJson
import io.infinitape.etherjar.rpc.transport.BatchStatus
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import java.time.Duration import java.time.Duration
import java.util.* import java.util.*
import java.util.concurrent.CompletableFuture
class EthereumUpstream( class EthereumApi(
private val rpcClient: RpcClient, private val rpcClient: RpcClient,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val chain: Chain private val chain: Chain
) { ) {
private val timeout = Duration.ofSeconds(5) private val timeout = Duration.ofSeconds(5)
private val log = LoggerFactory.getLogger(EthereumUpstream::class.java) private val log = LoggerFactory.getLogger(EthereumApi::class.java)
var ws: EthereumWsUpstream? = null var ws: EthereumWs? = null
set(value) { set(value) {
field = value field = value
} }
@@ -62,6 +65,10 @@ class EthereumUpstream(
"eth_accounts" "eth_accounts"
) )
fun execute(batch: Batch): CompletableFuture<BatchStatus> {
return rpcClient.execute(batch)
}
fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray> { fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray> {
val result: Mono<Any> = if (hardcodedMethods.contains(method)) { val result: Mono<Any> = if (hardcodedMethods.contains(method)) {
Mono.just(method).map { hardcoded(it) } Mono.just(method).map { hardcoded(it) }

View File

@@ -0,0 +1,7 @@
package io.emeraldpay.dshackle.upstream
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.json.BlockJson
interface EthereumHead: Head<BlockJson<TransactionId>> {
}

View File

@@ -0,0 +1,64 @@
package io.emeraldpay.dshackle.upstream
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.Batch
import io.infinitape.etherjar.rpc.Commands
import io.infinitape.etherjar.rpc.json.BlockJson
import org.slf4j.LoggerFactory
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.publisher.TopicProcessor
import java.time.Duration
import java.util.concurrent.atomic.AtomicReference
class EthereumRpcHead(
private val api: EthereumApi
): EthereumHead {
private val log = LoggerFactory.getLogger(EthereumRpcHead::class.java)
private val head = AtomicReference<BlockJson<TransactionId>>(null)
private val stream: TopicProcessor<BlockJson<TransactionId>> = TopicProcessor.create()
fun start() {
Flux.interval(Duration.ofSeconds(7))
.flatMap {
val batch = Batch()
val f = batch.add(Commands.eth().blockNumber)
api.execute(batch)
Mono.fromCompletionStage(f)
}
.flatMap {
val batch = Batch()
val f = batch.add(Commands.eth().getBlock(it))
api.execute(batch)
Mono.fromCompletionStage(f)
}
.onErrorContinue { err, _ ->
log.warn("RPC error ${err.message}")
}
.filter { block ->
val curr = head.get()
curr == null || curr.difficulty < block.difficulty
}
.subscribe { block ->
stream.onNext(block)
}
Flux.from(this.stream)
.subscribe { head.set(it) }
}
override fun getHead(): Mono<BlockJson<TransactionId>> {
val current = head.get()
if (current != null) {
return Mono.just(current)
}
return Mono.from(stream)
}
override fun getFlux(): Flux<BlockJson<TransactionId>> {
return Flux.from(stream)
}
}

View File

@@ -10,12 +10,12 @@ import java.net.URI
import java.time.Duration import java.time.Duration
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
class EthereumWsUpstream( class EthereumWs(
private val uri: URI, private val uri: URI,
private val origin: URI private val origin: URI
) { ) {
private val log = LoggerFactory.getLogger(EthereumWsUpstream::class.java) private val log = LoggerFactory.getLogger(EthereumWs::class.java)
private val topic = TopicProcessor private val topic = TopicProcessor
.builder<BlockJson<TransactionId>>() .builder<BlockJson<TransactionId>>()
.name("new-blocks") .name("new-blocks")

View File

@@ -0,0 +1,28 @@
package io.emeraldpay.dshackle.upstream
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.json.BlockJson
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.concurrent.atomic.AtomicReference
class EthereumWsHead(
private val ws: EthereumWs
): EthereumHead {
private val head = AtomicReference<BlockJson<TransactionId>>(null)
private val stream: Flux<BlockJson<TransactionId>> = ws.getFlux()
override fun getHead(): Mono<BlockJson<TransactionId>> {
val current = head.get()
if (current != null) {
return Mono.just(current)
}
return Mono.from(stream)
}
override fun getFlux(): Flux<BlockJson<TransactionId>> {
return Flux.from(stream)
}
}

View File

@@ -0,0 +1,11 @@
package io.emeraldpay.dshackle.upstream
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
interface Head<T> {
fun getHead(): Mono<T>
fun getFlux(): Flux<T>
}

View File

@@ -0,0 +1,24 @@
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionJson
class Upstream(
val chain: Chain,
val api: EthereumApi,
private val ethereumWs: EthereumWs? = null
) {
val head: EthereumHead = if (ethereumWs != null) {
EthereumWsHead(ethereumWs)
} else {
EthereumRpcHead(api).apply {
this.start()
}
}
init {
}
}

View File

@@ -17,34 +17,39 @@ class Upstreams(
) { ) {
private var seq = 0 private var seq = 0
private val chainMapping = HashMap<Chain, List<EthereumUpstream>>() private val chainMapping = HashMap<Chain, Upstream>()
@PostConstruct @PostConstruct
fun start() { fun start() {
env.getProperty("upstream.ethereum")?.let { env.getProperty("upstream.ethereum")?.let {
chainMapping[Chain.ETHEREUM] = listOf(buildClient(it, Chain.ETHEREUM)) val api = buildClient(it, Chain.ETHEREUM)
chainMapping[Chain.ETHEREUM] = Upstream(Chain.ETHEREUM, api)
} }
env.getProperty("upstream.ethereumclassic")?.let { env.getProperty("upstream.ethereumclassic")?.let {
chainMapping[Chain.ETHEREUM_CLASSIC] = listOf(buildClient(it, Chain.ETHEREUM_CLASSIC)) val api = buildClient(it, Chain.ETHEREUM_CLASSIC)
} val ws = if (env.containsProperty("upstream.ethereumclassic.ws")) {
env.getProperty("upstream.ethereumclassic.ws")?.let { buildWs(env.getProperty("upstream.ethereumclassic.ws")!!, Chain.ETHEREUM_CLASSIC)
chainMapping[Chain.ETHEREUM_CLASSIC]!![0].ws = buildWs(it, Chain.ETHEREUM_CLASSIC) } else {
null
}
chainMapping[Chain.ETHEREUM_CLASSIC] = Upstream(Chain.ETHEREUM_CLASSIC, api, ws)
} }
env.getProperty("upstream.morden")?.let { env.getProperty("upstream.morden")?.let {
chainMapping[Chain.MORDEN] = listOf(buildClient(it, Chain.MORDEN)) val api = buildClient(it, Chain.MORDEN)
chainMapping[Chain.MORDEN] = Upstream(Chain.MORDEN, api)
} }
} }
private fun buildClient(url: String, chain: Chain): EthereumUpstream { private fun buildClient(url: String, chain: Chain): EthereumApi {
return EthereumUpstream( return EthereumApi(
DefaultRpcClient(DefaultRpcTransport(URI(url))), DefaultRpcClient(DefaultRpcTransport(URI(url))),
objectMapper, objectMapper,
chain chain
) )
} }
private fun buildWs(url: String, chain: Chain): EthereumWsUpstream { private fun buildWs(url: String, chain: Chain): EthereumWs {
val ws = EthereumWsUpstream( val ws = EthereumWs(
URI(url), URI(url),
URI("http://localhost") URI("http://localhost")
) )
@@ -52,12 +57,7 @@ class Upstreams(
return ws return ws
} }
fun validateUpstream(upstream: EthereumUpstream): Boolean { fun ethereumUpstream(chain: Chain): Upstream? {
return true return chainMapping[chain]
}
fun ethereumUpstream(chain: Chain): EthereumUpstream? {
val all = chainMapping[chain] ?: return null
return all[seq++ % all.size]
} }
} }