solution: update current head through RPC as well
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) }
|
||||||
@@ -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>> {
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/main/kotlin/io/emeraldpay/dshackle/upstream/Head.kt
Normal file
11
src/main/kotlin/io/emeraldpay/dshackle/upstream/Head.kt
Normal 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>
|
||||||
|
}
|
||||||
24
src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt
Normal file
24
src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt
Normal 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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user