solution: newHeads subsription
This commit is contained in:
@@ -50,7 +50,7 @@ class NativeSubscribe(
|
||||
.onErrorMap(this@NativeSubscribe::convertToStatus)
|
||||
}
|
||||
|
||||
fun start(it: BlockchainOuterClass.NativeSubscribeRequest): Publisher<Any> {
|
||||
fun start(it: BlockchainOuterClass.NativeSubscribeRequest): Publisher<out Any> {
|
||||
val chain = Chain.byId(it.chainValue)
|
||||
if (BlockchainType.from(chain) != BlockchainType.ETHEREUM) {
|
||||
return Mono.error(UnsupportedOperationException("Native subscribe is not supported for ${chain.chainCode}"))
|
||||
@@ -81,7 +81,7 @@ class NativeSubscribe(
|
||||
}
|
||||
}
|
||||
|
||||
fun subscribe(chain: Chain, method: String, params: List<*>): Flux<Any> {
|
||||
fun subscribe(chain: Chain, method: String, params: List<*>): Flux<out Any> {
|
||||
val up = multistreamHolder.getUpstream(chain) ?: return Flux.error(SilentException.UnsupportedBlockchain(chain))
|
||||
return (up as EthereumMultistream)
|
||||
.getSubscribe()
|
||||
|
||||
@@ -41,7 +41,7 @@ open class EthereumMultistream(
|
||||
private var head: Head? = null
|
||||
|
||||
private val reader: EthereumReader = EthereumReader(this, this.caches, getMethodsFactory())
|
||||
private val subscribe = EthereumSubscribe()
|
||||
private val subscribe = EthereumSubscribe(this)
|
||||
|
||||
init {
|
||||
this.init()
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.ConnectNewHeads
|
||||
import org.slf4j.LoggerFactory
|
||||
import reactor.core.publisher.Flux
|
||||
|
||||
open class EthereumSubscribe {
|
||||
open class EthereumSubscribe(
|
||||
val upstream: EthereumMultistream
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(EthereumSubscribe::class.java)
|
||||
}
|
||||
|
||||
open fun subscribe(method: String, params: List<*>): Flux<Any> {
|
||||
private val newHeads = ConnectNewHeads(upstream)
|
||||
|
||||
open fun subscribe(method: String, params: List<*>): Flux<out Any> {
|
||||
if (method == "newHeads") {
|
||||
return newHeads.connect()
|
||||
}
|
||||
return Flux.error(UnsupportedOperationException("Method $method is not supported"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.NewHead
|
||||
import org.slf4j.LoggerFactory
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Connects/reconnects to the upstream to produce NewHeads messages
|
||||
*/
|
||||
class ConnectNewHeads(
|
||||
private val upstream: EthereumMultistream
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(ConnectNewHeads::class.java)
|
||||
}
|
||||
|
||||
private var connected: Flux<NewHead>? = null
|
||||
private val connectLock = ReentrantLock()
|
||||
|
||||
fun connect(): Flux<NewHead> {
|
||||
val current = connected
|
||||
if (current != null) {
|
||||
return current
|
||||
}
|
||||
connectLock.withLock {
|
||||
val currentRecheck = connected
|
||||
if (currentRecheck != null) {
|
||||
return currentRecheck
|
||||
}
|
||||
val created = ProduceNewHeads(upstream.getHead())
|
||||
.start()
|
||||
.publishOn(Schedulers.boundedElastic())
|
||||
.publish()
|
||||
.refCount(1, Duration.ofSeconds(60))
|
||||
.doFinally {
|
||||
//forget it on disconnect, so next time it's recreated
|
||||
connected = null
|
||||
}
|
||||
connected = created
|
||||
return created
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
||||
|
||||
import io.emeraldpay.dshackle.Global
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.NewHead
|
||||
import io.emeraldpay.etherjar.rpc.json.BlockJson
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import reactor.core.publisher.Flux
|
||||
|
||||
/**
|
||||
* Produces NewHead messages by transforming blocks received from Head
|
||||
* @see Head
|
||||
* @see NewHead
|
||||
*/
|
||||
class ProduceNewHeads(
|
||||
val head: Head
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(ProduceNewHeads::class.java)
|
||||
}
|
||||
|
||||
private val objectMapper = Global.objectMapper
|
||||
|
||||
fun start(): Flux<NewHead> {
|
||||
return head.getFlux()
|
||||
.map {
|
||||
if (it.parsed != null) {
|
||||
it.parsed as BlockJson<TransactionRefJson>
|
||||
} else {
|
||||
objectMapper.readValue(it.json, BlockJson::class.java)
|
||||
}
|
||||
}
|
||||
.map { block ->
|
||||
NewHead(
|
||||
block.number,
|
||||
block.hash,
|
||||
block.parentHash,
|
||||
block.timestamp,
|
||||
block.difficulty,
|
||||
block.gasLimit,
|
||||
block.gasUsed,
|
||||
block.logsBloom,
|
||||
block.miner,
|
||||
block.baseFeePerGas?.amount
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
import io.emeraldpay.etherjar.domain.Bloom
|
||||
import io.emeraldpay.etherjar.rpc.json.HexDataSerializer
|
||||
import java.math.BigInteger
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Common fields for newHeads event. IT's different from Block JSON and doesn't include many fields, most notable is
|
||||
* list of transactions. Also, our JSON doesn't include rarely used fields such as extraData, sha3uncles, stateRoot,
|
||||
* transactionRoot and some others.
|
||||
*/
|
||||
data class NewHead(
|
||||
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
||||
val number: Long,
|
||||
@get:JsonSerialize(using = HexDataSerializer::class)
|
||||
val hash: BlockHash,
|
||||
@get:JsonSerialize(using = HexDataSerializer::class)
|
||||
val parentHash: BlockHash,
|
||||
@get:JsonSerialize(using = TimestampSerializer::class)
|
||||
val timestamp: Instant,
|
||||
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
||||
val difficulty: BigInteger,
|
||||
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
||||
val gasLimit: Long,
|
||||
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
||||
val gasUsed: Long,
|
||||
@get:JsonSerialize(using = HexDataSerializer::class)
|
||||
val logsBloom: Bloom,
|
||||
@get:JsonSerialize(using = HexDataSerializer::class)
|
||||
val miner: Address,
|
||||
@get:JsonSerialize(using = NumberAsHexSerializer::class)
|
||||
@get:JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
val baseFeePerGas: BigInteger?
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import io.emeraldpay.etherjar.hex.HexQuantity
|
||||
import java.math.BigInteger
|
||||
|
||||
/**
|
||||
* Encodes numeric values as hex string prefixed with <code>0x</code>, per Ethereum standard.
|
||||
*/
|
||||
class NumberAsHexSerializer : JsonSerializer<Number>() {
|
||||
|
||||
override fun serialize(value: Number?, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
if (value == null) {
|
||||
gen.writeNull()
|
||||
return
|
||||
}
|
||||
val hex = if (value is BigInteger) {
|
||||
HexQuantity.from(value)
|
||||
} else {
|
||||
HexQuantity.from(value.toLong())
|
||||
}
|
||||
gen.writeString(hex.toHex())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe.json
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Encodes timestamps as seconds from epoch, written as hex string.
|
||||
* @see NumberAsHexSerializer
|
||||
*/
|
||||
class TimestampSerializer : JsonSerializer<Instant>() {
|
||||
|
||||
private val numberAsHex = NumberAsHexSerializer()
|
||||
|
||||
override fun serialize(value: Instant?, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
if (value == null) {
|
||||
gen.writeNull()
|
||||
return
|
||||
}
|
||||
numberAsHex.serialize(value.epochSecond, gen, serializers)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user