solution: refactor upstreams to make them blockchain specific
This commit is contained in:
41
src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt
Normal file
41
src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2020 ETCDEV GmbH
|
||||
*
|
||||
* 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
|
||||
|
||||
import io.emeraldpay.grpc.Chain
|
||||
|
||||
/**
|
||||
* Type of the blockchain, architecture-wise
|
||||
*/
|
||||
enum class BlockchainType {
|
||||
|
||||
ETHEREUM,
|
||||
BITCOIN,
|
||||
OTHER;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromBlockchain(blockchain: Chain): BlockchainType {
|
||||
if (blockchain == Chain.ETHEREUM || blockchain == Chain.ETHEREUM_CLASSIC || blockchain == Chain.TESTNET_KOVAN || blockchain == Chain.TESTNET_MORDEN) {
|
||||
return ETHEREUM
|
||||
}
|
||||
if (blockchain == Chain.BITCOIN || blockchain == Chain.TESTNET_BITCOIN) {
|
||||
return BITCOIN
|
||||
}
|
||||
return OTHER
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package io.emeraldpay.dshackle.config
|
||||
|
||||
import io.emeraldpay.dshackle.Defaults
|
||||
import java.lang.ClassCastException
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
@@ -72,6 +73,14 @@ class UpstreamsConfig {
|
||||
var connection: T? = null
|
||||
val labels = Labels()
|
||||
var methods: Methods? = null
|
||||
|
||||
@Suppress("unchecked")
|
||||
fun <Z : UpstreamConnection> cast(type: Class<Z>): Upstream<Z> {
|
||||
if (connection == null || type.isAssignableFrom(connection!!.javaClass)) {
|
||||
return this as Upstream<Z>
|
||||
}
|
||||
throw ClassCastException("Cannot cast ${connection?.javaClass} to $type")
|
||||
}
|
||||
}
|
||||
|
||||
open class UpstreamConnection
|
||||
|
||||
@@ -34,13 +34,13 @@ open class AlwaysQuorum: CallQuorum {
|
||||
return resolved
|
||||
}
|
||||
|
||||
override fun record(response: ByteArray, upstream: Upstream): Boolean {
|
||||
override fun record(response: ByteArray, upstream: Upstream<*, *>): Boolean {
|
||||
result = response
|
||||
resolved = true
|
||||
return true
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
override fun record(error: RpcException, upstream: Upstream<*, *>) {
|
||||
}
|
||||
|
||||
override fun getResult(): ByteArray? {
|
||||
|
||||
@@ -42,7 +42,7 @@ open class BroadcastQuorum(
|
||||
return result
|
||||
}
|
||||
|
||||
override fun recordValue(response: ByteArray, responseValue: String?, upstream: Upstream) {
|
||||
override fun recordValue(response: ByteArray, responseValue: String?, upstream: Upstream<*, *>) {
|
||||
calls++
|
||||
if (txid == null && responseValue != null) {
|
||||
txid = responseValue
|
||||
@@ -50,7 +50,7 @@ open class BroadcastQuorum(
|
||||
}
|
||||
}
|
||||
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream) {
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream<*, *>) {
|
||||
// can be "message: known transaction: TXID", "Transaction with the same hash was already imported" or "message: Nonce too low"
|
||||
calls++
|
||||
if (result == null) {
|
||||
|
||||
@@ -30,8 +30,8 @@ interface CallQuorum {
|
||||
fun init(head: Head<BlockJson<TransactionRefJson>>)
|
||||
|
||||
fun isResolved(): Boolean
|
||||
fun record(response: ByteArray, upstream: Upstream): Boolean
|
||||
fun record(error: RpcException, upstream: Upstream)
|
||||
fun record(response: ByteArray, upstream: Upstream<*, *>): Boolean
|
||||
fun record(error: RpcException, upstream: Upstream<*, *>)
|
||||
fun getResult(): ByteArray?
|
||||
|
||||
companion object {
|
||||
@@ -41,8 +41,8 @@ interface CallQuorum {
|
||||
}
|
||||
}
|
||||
|
||||
fun asReducer(): BiFunction<CallQuorum, Tuple2<ByteArray, Upstream>, CallQuorum> {
|
||||
return BiFunction<CallQuorum, Tuple2<ByteArray, Upstream>, CallQuorum> { a, b ->
|
||||
fun asReducer(): BiFunction<CallQuorum, Tuple2<ByteArray, Upstream<*, *>>, CallQuorum> {
|
||||
return BiFunction<CallQuorum, Tuple2<ByteArray, Upstream<*, *>>, CallQuorum> { a, b ->
|
||||
a.record(b.t1, b.t2)
|
||||
return@BiFunction a
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ open class NonEmptyQuorum(
|
||||
return result != null || tries >= maxTries
|
||||
}
|
||||
|
||||
override fun recordValue(response: ByteArray, responseValue: Any?, upstream: Upstream) {
|
||||
override fun recordValue(response: ByteArray, responseValue: Any?, upstream: Upstream<*, *>) {
|
||||
tries++
|
||||
if (responseValue != null) {
|
||||
result = response
|
||||
@@ -49,10 +49,10 @@ open class NonEmptyQuorum(
|
||||
return result
|
||||
}
|
||||
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream) {
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream<*, *>) {
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
override fun record(error: RpcException, upstream: Upstream<*, *>) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,7 +46,7 @@ open class NonceQuorum(
|
||||
}
|
||||
}
|
||||
|
||||
override fun recordValue(response: ByteArray, responseValue: String?, upstream: Upstream) {
|
||||
override fun recordValue(response: ByteArray, responseValue: String?, upstream: Upstream<*, *>) {
|
||||
val value = responseValue?.let { str ->
|
||||
HexQuantity.from(str).value.toLong()
|
||||
}
|
||||
@@ -65,11 +65,11 @@ open class NonceQuorum(
|
||||
return result
|
||||
}
|
||||
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream) {
|
||||
override fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream<*, *>) {
|
||||
errors++
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
override fun record(error: RpcException, upstream: Upstream<*, *>) {
|
||||
errors++
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class NotLaggingQuorum(val maxLag: Long = 0): CallQuorum {
|
||||
return result.get() != null
|
||||
}
|
||||
|
||||
override fun record(response: ByteArray, upstream: Upstream): Boolean {
|
||||
override fun record(response: ByteArray, upstream: Upstream<*, *>): Boolean {
|
||||
val lagging = upstream.getLag() > maxLag
|
||||
if (!lagging) {
|
||||
result.set(response)
|
||||
@@ -43,7 +43,7 @@ class NotLaggingQuorum(val maxLag: Long = 0): CallQuorum {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
override fun record(error: RpcException, upstream: Upstream<*, *>) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ abstract class ValueAwareQuorum<T>(
|
||||
return jacksonRpcConverter.fromJson(response.inputStream(), clazz)
|
||||
}
|
||||
|
||||
override fun record(response: ByteArray, upstream: Upstream): Boolean {
|
||||
override fun record(response: ByteArray, upstream: Upstream<*, *>): Boolean {
|
||||
try {
|
||||
val value = extractValue(response, clazz)
|
||||
recordValue(response, value, upstream)
|
||||
@@ -43,12 +43,12 @@ abstract class ValueAwareQuorum<T>(
|
||||
return isResolved();
|
||||
}
|
||||
|
||||
override fun record(error: RpcException, upstream: Upstream) {
|
||||
override fun record(error: RpcException, upstream: Upstream<*, *>) {
|
||||
recordError(null, error.rpcMessage, upstream)
|
||||
}
|
||||
|
||||
abstract fun recordValue(response: ByteArray, responseValue: T?, upstream: Upstream)
|
||||
abstract fun recordValue(response: ByteArray, responseValue: T?, upstream: Upstream<*, *>)
|
||||
|
||||
abstract fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream)
|
||||
abstract fun recordError(response: ByteArray?, errorMessage: String?, upstream: Upstream<*, *>)
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 ETCDEV GmbH
|
||||
*
|
||||
* 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.reader
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.Selector
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.Commands
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.retry.Repeat
|
||||
import java.time.Duration
|
||||
|
||||
class BlockApiReader(
|
||||
val upstream: Upstream
|
||||
): Reader<BlockHash, BlockJson<TransactionRefJson>> {
|
||||
|
||||
override fun read(key: BlockHash): Mono<BlockJson<TransactionRefJson>> {
|
||||
return Mono.just(key)
|
||||
.flatMap {
|
||||
upstream.getApi(Selector.empty)
|
||||
.flatMap { api -> api.executeAndConvert(Commands.eth().getBlock(it)) }
|
||||
}.repeatWhenEmpty { n ->
|
||||
Repeat.times<Any>(3)
|
||||
.exponentialBackoff(Duration.ofMillis(100), Duration.ofMillis(500))
|
||||
.apply(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ package io.emeraldpay.dshackle.rpc
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
|
||||
import io.grpc.stub.StreamObserver
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -29,8 +30,8 @@ import reactor.core.publisher.Mono
|
||||
class BlockchainRpc(
|
||||
@Autowired private val nativeCall: NativeCall,
|
||||
@Autowired private val streamHead: StreamHead,
|
||||
@Autowired private val trackTx: TrackTx,
|
||||
@Autowired private val trackAddress: TrackAddress,
|
||||
@Autowired private val trackEthereumTx: TrackEthereumTx,
|
||||
@Autowired private val trackEthereumAddress: TrackEthereumAddress,
|
||||
@Autowired private val describe: Describe,
|
||||
@Autowired private val subscribeStatus: SubscribeStatus
|
||||
): ReactorBlockchainGrpc.BlockchainImplBase() {
|
||||
@@ -46,15 +47,15 @@ class BlockchainRpc(
|
||||
}
|
||||
|
||||
override fun subscribeTxStatus(request: Mono<BlockchainOuterClass.TxStatusRequest>): Flux<BlockchainOuterClass.TxStatus> {
|
||||
return trackTx.add(request)
|
||||
return trackEthereumTx.add(request)
|
||||
}
|
||||
|
||||
override fun subscribeBalance(request: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
|
||||
return trackAddress.subscribe(request)
|
||||
return trackEthereumAddress.subscribe(request)
|
||||
}
|
||||
|
||||
override fun getBalance(request: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
|
||||
return trackAddress.getBalance(request)
|
||||
return trackEthereumAddress.getBalance(request)
|
||||
}
|
||||
|
||||
override fun describe(request: Mono<BlockchainOuterClass.DescribeRequest>): Mono<BlockchainOuterClass.DescribeResponse> {
|
||||
|
||||
@@ -20,7 +20,7 @@ import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.upstream.*
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.dshackle.upstream.grpc.GrpcUpstream
|
||||
import io.emeraldpay.dshackle.upstream.grpc.EthereumGrpcUpstream
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Mono
|
||||
@@ -47,7 +47,7 @@ class Describe(
|
||||
val nodes = QuorumForLabels()
|
||||
if (up is EthereumUpstream) {
|
||||
nodes.add(up.node)
|
||||
} else if (up is GrpcUpstream) {
|
||||
} else if (up is EthereumGrpcUpstream) {
|
||||
nodes.add(up.getNodes())
|
||||
}
|
||||
nodes.getAll().forEach { node ->
|
||||
|
||||
@@ -18,12 +18,16 @@ package io.emeraldpay.dshackle.rpc
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.SilentException
|
||||
import io.emeraldpay.dshackle.upstream.*
|
||||
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
||||
import io.emeraldpay.dshackle.quorum.CallQuorum
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.RpcException
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@@ -84,13 +88,17 @@ open class NativeCall(
|
||||
if (chain == Chain.UNSPECIFIED) {
|
||||
return Flux.error(CallFailure(0, SilentException.UnsupportedBlockchain(request.chain.number)))
|
||||
}
|
||||
if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) {
|
||||
return Flux.error(CallFailure(0, SilentException.UnsupportedBlockchain(request.chain.number)))
|
||||
}
|
||||
|
||||
val upstream = upstreams.getUpstream(chain)
|
||||
?: return Flux.error(CallFailure(0, SilentException.UnsupportedBlockchain(chain)))
|
||||
|
||||
return prepareCall(request, upstream)
|
||||
return prepareCall(request, upstream as AggregatedUpstream<EthereumApi, BlockJson<TransactionRefJson>>)
|
||||
}
|
||||
|
||||
fun prepareCall(request: BlockchainOuterClass.NativeCallRequest, upstream: AggregatedUpstream): Flux<CallContext<RawCallDetails>> {
|
||||
fun prepareCall(request: BlockchainOuterClass.NativeCallRequest, upstream: AggregatedUpstream<EthereumApi, BlockJson<TransactionRefJson>>): Flux<CallContext<RawCallDetails>> {
|
||||
return request.itemsList.toFlux().map {
|
||||
val method = it.method
|
||||
val params = it.payload.toStringUtf8()
|
||||
@@ -197,7 +205,7 @@ open class NativeCall(
|
||||
}
|
||||
|
||||
open class CallContext<T>(val id: Int,
|
||||
val upstream: AggregatedUpstream,
|
||||
val upstream: AggregatedUpstream<EthereumApi, BlockJson<TransactionRefJson>>,
|
||||
val matcher: Selector.Matcher,
|
||||
val callQuorum: CallQuorum,
|
||||
val payload: T) {
|
||||
@@ -205,7 +213,7 @@ open class NativeCall(
|
||||
return CallContext(id, upstream, matcher, callQuorum, payload)
|
||||
}
|
||||
|
||||
fun getApis(): ApiSource {
|
||||
fun getApis(): ApiSource<EthereumApi> {
|
||||
return upstream.getApis(matcher)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ package io.emeraldpay.dshackle.rpc
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -44,11 +44,25 @@ class StreamHead(
|
||||
?: return@flatMapMany Flux.error<BlockchainOuterClass.ChainHead>(Exception("Unavailable chain: $chain"))
|
||||
up.getHead()
|
||||
.getFlux()
|
||||
.map { asProto(chain, it) }
|
||||
.map { asProto(chain, it!!) }
|
||||
.onErrorContinue { t, _ ->
|
||||
log.warn("Head error: ${t.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun asProto(chain: Chain, block: BlockJson<TransactionRefJson>): BlockchainOuterClass.ChainHead {
|
||||
fun asProto(chain: Chain, block: Any): BlockchainOuterClass.ChainHead {
|
||||
if (BlockchainType.fromBlockchain(chain) == BlockchainType.ETHEREUM) {
|
||||
if (BlockJson::class.java.isAssignableFrom(block.javaClass)) {
|
||||
return asEthereumProto(chain, block as BlockJson<TransactionRefJson>)
|
||||
} else {
|
||||
throw IllegalArgumentException("Invalid block type: ${block.javaClass}")
|
||||
}
|
||||
}
|
||||
throw IllegalArgumentException("Unsupported blockchain ${chain}")
|
||||
}
|
||||
|
||||
fun asEthereumProto(chain: Chain, block: BlockJson<TransactionRefJson>): BlockchainOuterClass.ChainHead {
|
||||
return BlockchainOuterClass.ChainHead.newBuilder()
|
||||
.setChainValue(chain.id)
|
||||
.setHeight(block.number)
|
||||
|
||||
@@ -45,7 +45,7 @@ class SubscribeStatus(
|
||||
}
|
||||
}
|
||||
|
||||
fun chainStatus(chain: Chain, ups: List<Upstream>): BlockchainOuterClass.ChainStatus {
|
||||
fun chainStatus(chain: Chain, ups: List<Upstream<*, *>>): BlockchainOuterClass.ChainStatus {
|
||||
val available = ups.map { u ->
|
||||
u.getStatus()
|
||||
}.min() ?: UpstreamAvailability.UNAVAILABLE
|
||||
@@ -59,6 +59,6 @@ class SubscribeStatus(
|
||||
.build()
|
||||
}
|
||||
|
||||
class ChainSubscription(val chain: Chain, val up: AggregatedUpstream, val avail: UpstreamAvailability)
|
||||
class ChainSubscription(val chain: Chain, val up: AggregatedUpstream<*, *>, val avail: UpstreamAvailability)
|
||||
|
||||
}
|
||||
@@ -17,15 +17,20 @@ package io.emeraldpay.dshackle.rpc
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.Defaults
|
||||
import io.emeraldpay.dshackle.SilentException
|
||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.Selector
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.Address
|
||||
import io.infinitape.etherjar.domain.Wei
|
||||
import io.infinitape.etherjar.rpc.Commands
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.BlockTag
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
@@ -43,12 +48,12 @@ import java.util.concurrent.atomic.AtomicLong
|
||||
import javax.annotation.PostConstruct
|
||||
|
||||
@Service
|
||||
class TrackAddress(
|
||||
class TrackEthereumAddress(
|
||||
@Autowired private val upstreams: Upstreams,
|
||||
@Autowired private val upstreamScheduler: Scheduler
|
||||
) {
|
||||
|
||||
private val log = LoggerFactory.getLogger(TrackAddress::class.java)
|
||||
private val log = LoggerFactory.getLogger(TrackEthereumAddress::class.java)
|
||||
private val clients = HashMap<Chain, ConcurrentLinkedQueue<TrackedAddress>>()
|
||||
private val seq = AtomicLong(0)
|
||||
|
||||
@@ -94,6 +99,9 @@ class TrackAddress(
|
||||
|
||||
private fun initializeSimple(request: BlockchainOuterClass.BalanceRequest): Flux<SimpleAddress> {
|
||||
val chain = Chain.byId(request.asset.chainValue)
|
||||
if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) {
|
||||
return Flux.error(SilentException.UnsupportedBlockchain(request.asset.chainValue))
|
||||
}
|
||||
if (!upstreams.isAvailable(chain)) {
|
||||
return Flux.error(SilentException.UnsupportedBlockchain(request.asset.chainValue))
|
||||
}
|
||||
@@ -122,6 +130,10 @@ class TrackAddress(
|
||||
|
||||
fun subscribe(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
|
||||
return requestMono.flatMapMany { request ->
|
||||
val chain = Chain.byId(request.asset.chainValue)
|
||||
if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) {
|
||||
return@flatMapMany Flux.error<BlockchainOuterClass.AddressBalance>(SilentException.UnsupportedBlockchain(request.asset.chainValue))
|
||||
}
|
||||
val bus = TopicProcessor.create<BlockchainOuterClass.AddressBalance>()
|
||||
initializeSubscription(request, bus)
|
||||
.flatMap { tracked ->
|
||||
@@ -175,7 +187,8 @@ class TrackAddress(
|
||||
}
|
||||
|
||||
fun getBalance(addr: SimpleAddress): Mono<Wei> {
|
||||
val up = upstreams.getUpstream(addr.chain) ?: return Mono.error(SilentException.UnsupportedBlockchain(addr.chain))
|
||||
val up = upstreams.getUpstream(addr.chain) as AggregatedUpstream<EthereumApi, BlockJson<TransactionRefJson>>?
|
||||
?: return Mono.error(SilentException.UnsupportedBlockchain(addr.chain))
|
||||
return up.getApi(Selector.empty)
|
||||
.flatMap { api -> api.executeAndConvert(Commands.eth().getBalance(addr.address, BlockTag.LATEST)) }
|
||||
.timeout(Defaults.timeout)
|
||||
@@ -18,10 +18,13 @@ package io.emeraldpay.dshackle.rpc
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.SilentException
|
||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.Selector
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
@@ -40,7 +43,6 @@ import reactor.core.publisher.TopicProcessor
|
||||
import reactor.core.publisher.toFlux
|
||||
import reactor.core.scheduler.Scheduler
|
||||
import reactor.util.function.Tuples
|
||||
import java.lang.Exception
|
||||
import java.math.BigInteger
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@@ -53,7 +55,7 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@Service
|
||||
class TrackTx(
|
||||
class TrackEthereumTx(
|
||||
@Autowired private val upstreams: Upstreams,
|
||||
@Autowired private val upstreamScheduler: Scheduler
|
||||
) {
|
||||
@@ -67,7 +69,7 @@ class TrackTx(
|
||||
private val PING_PERIOD = Duration.ofMinutes(5)
|
||||
}
|
||||
|
||||
private val log = LoggerFactory.getLogger(TrackTx::class.java)
|
||||
private val log = LoggerFactory.getLogger(TrackEthereumTx::class.java)
|
||||
private val clients = HashMap<Chain, ConcurrentLinkedQueue<TrackedTx>>()
|
||||
private val seq = AtomicLong(0)
|
||||
|
||||
@@ -142,6 +144,9 @@ class TrackTx(
|
||||
|
||||
fun prepareTracking(request: BlockchainOuterClass.TxStatusRequest): TxDetails {
|
||||
val chain = Chain.byId(request.chainValue)
|
||||
if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) {
|
||||
throw SilentException.UnsupportedBlockchain(request.chainValue)
|
||||
}
|
||||
if (!clients.containsKey(chain)) {
|
||||
throw SilentException.UnsupportedBlockchain(chain)
|
||||
}
|
||||
@@ -208,7 +213,7 @@ class TrackTx(
|
||||
}
|
||||
|
||||
private fun loadWeight(tx: TxDetails): Mono<TxDetails> {
|
||||
val upstream = upstreams.getUpstream(tx.chain)
|
||||
val upstream = upstreams.getUpstream(tx.chain) as AggregatedUpstream<EthereumApi, BlockJson<TransactionRefJson>>?
|
||||
?: return Mono.error(SilentException.UnsupportedBlockchain(tx.chain))
|
||||
return upstream.getApi(Selector.empty)
|
||||
.flatMap { api -> api.executeAndConvert(Commands.eth().getBlock(tx.status.blockHash)) }
|
||||
@@ -219,7 +224,7 @@ class TrackTx(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateFromBlock(upstream: Upstream, tx: TxDetails, it: TransactionJson): Mono<TxDetails> {
|
||||
fun updateFromBlock(upstream: Upstream<EthereumApi, BlockJson<TransactionRefJson>>, tx: TxDetails, it: TransactionJson): Mono<TxDetails> {
|
||||
return if (it.blockNumber != null && it.blockHash != null && it.blockHash != ZERO_BLOCK) {
|
||||
val updated = tx.withStatus(
|
||||
blockHash = it.blockHash,
|
||||
@@ -250,7 +255,8 @@ class TrackTx(
|
||||
|
||||
private fun checkForUpdate(tx: TxDetails): Mono<TxDetails> {
|
||||
val initialStatus = tx.status
|
||||
val upstream = upstreams.getUpstream(tx.chain) ?: return Mono.error(SilentException.UnsupportedBlockchain(tx.chain))
|
||||
val upstream = upstreams.getUpstream(tx.chain) as AggregatedUpstream<EthereumApi, BlockJson<TransactionRefJson>>?
|
||||
?: return Mono.error(SilentException.UnsupportedBlockchain(tx.chain))
|
||||
val execution = upstream.getApi(Selector.empty)
|
||||
.flatMap { api -> api.executeAndConvert(Commands.eth().getTransaction(tx.txid)) }
|
||||
return execution
|
||||
@@ -16,9 +16,9 @@
|
||||
package io.emeraldpay.dshackle.startup
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.FileResolver
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfigReader
|
||||
import io.emeraldpay.dshackle.upstream.CurrentUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
@@ -27,16 +27,13 @@ import io.emeraldpay.dshackle.upstream.ethereum.EthereumWs
|
||||
import io.emeraldpay.dshackle.upstream.grpc.GrpcUpstreams
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.http.ReactorHttpRpcClient
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.core.env.Environment
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import javax.annotation.PostConstruct
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@Repository
|
||||
open class ConfiguredUpstreams(
|
||||
@@ -59,21 +56,26 @@ open class ConfiguredUpstreams(
|
||||
|
||||
@PostConstruct
|
||||
fun start() {
|
||||
log.debug("Starting upstreams")
|
||||
val defaultOptions = buildDefaultOptions(config)
|
||||
config.upstreams.forEach { up ->
|
||||
|
||||
log.debug("Start upstream ${up.id}")
|
||||
if (up.connection is UpstreamsConfig.GrpcConnection) {
|
||||
val options = up.options ?: UpstreamsConfig.Options()
|
||||
buildGrpcUpstream(up as UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>, options)
|
||||
buildGrpcUpstream(up.cast(UpstreamsConfig.GrpcConnection::class.java), options)
|
||||
} else {
|
||||
val chain = chainNames[up.chain]
|
||||
if (chain == null) {
|
||||
log.error("Chain not supported: ${up.chain}")
|
||||
log.error("Chain is unknown: ${up.chain}")
|
||||
return@forEach
|
||||
}
|
||||
if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) {
|
||||
log.error("Chain is unsupported: ${up.chain}")
|
||||
return@forEach
|
||||
}
|
||||
val options = (up.options ?: UpstreamsConfig.Options())
|
||||
.merge(defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults())
|
||||
buildEthereumUpstream(up as UpstreamsConfig.Upstream<UpstreamsConfig.EthereumConnection>, chain, options)
|
||||
buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,38 @@ import io.emeraldpay.dshackle.cache.CachesEnabled
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
|
||||
/**
|
||||
* An update event to the list of currently available upstreams.
|
||||
*/
|
||||
class UpstreamChange(
|
||||
/**
|
||||
* Target blockchain
|
||||
*/
|
||||
val chain: Chain,
|
||||
val upstream: Upstream,
|
||||
/**
|
||||
* Corresponding upstream
|
||||
*/
|
||||
val upstream: Upstream<*, *>,
|
||||
/**
|
||||
* Type of the change
|
||||
*/
|
||||
val type: ChangeType
|
||||
): CachesEnabled {
|
||||
|
||||
enum class ChangeType {
|
||||
/**
|
||||
* Upstream just added
|
||||
*/
|
||||
ADDED,
|
||||
|
||||
/**
|
||||
* Upstream become available after being temporally off
|
||||
*/
|
||||
REVALIDATED,
|
||||
STALE,
|
||||
|
||||
/**
|
||||
* Upstream is removed (it still doesn't mean it wouldn't return again after some reconfiguration)
|
||||
*/
|
||||
REMOVED,
|
||||
}
|
||||
|
||||
|
||||
@@ -31,19 +31,22 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Predicate
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
abstract class AggregatedUpstream(
|
||||
/**
|
||||
* Aggregation of multiple upstreams responding to a single blockchain
|
||||
*/
|
||||
abstract class AggregatedUpstream<U : UpstreamApi, B>(
|
||||
private val objectMapper: ObjectMapper,
|
||||
val caches: Caches
|
||||
): Upstream, Lifecycle {
|
||||
) : Upstream<U, B>, Lifecycle {
|
||||
|
||||
private var cacheSubscription: Disposable? = null
|
||||
var cache: CachingEthereumApi = CachingEthereumApi.empty()
|
||||
private val reconfigLock = ReentrantLock()
|
||||
private var callMethods: CallMethods? = null
|
||||
|
||||
abstract fun getAll(): List<Upstream>
|
||||
abstract fun addUpstream(upstream: Upstream)
|
||||
abstract fun getApis(matcher: Selector.Matcher): ApiSource
|
||||
abstract fun getAll(): List<Upstream<U, B>>
|
||||
abstract fun addUpstream(upstream: Upstream<U, B>)
|
||||
abstract fun getApis(matcher: Selector.Matcher): ApiSource<U>
|
||||
|
||||
fun onUpstreamsUpdated() {
|
||||
reconfigLock.withLock {
|
||||
@@ -78,24 +81,6 @@ abstract class AggregatedUpstream(
|
||||
return callMethods ?: throw IllegalStateException("Methods are not initialized yet")
|
||||
}
|
||||
|
||||
class UpstreamStatus(val upstream: Upstream, val status: UpstreamAvailability, val ts: Instant = Instant.now())
|
||||
|
||||
class FilterBestAvailability(): Predicate<UpstreamStatus> {
|
||||
private val lastRef = AtomicReference<UpstreamStatus>()
|
||||
|
||||
override fun test(t: UpstreamStatus): Boolean {
|
||||
val last = lastRef.get()
|
||||
val changed = last == null
|
||||
|| t.status > last.status
|
||||
|| (last.upstream == t.upstream && t.status != last.status)
|
||||
|| last.ts.isBefore(Instant.now() - Duration.ofSeconds(60))
|
||||
if (changed) {
|
||||
lastRef.set(t)
|
||||
}
|
||||
return changed
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
}
|
||||
|
||||
@@ -114,4 +99,24 @@ abstract class AggregatedUpstream(
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
|
||||
class UpstreamStatus<H>(val upstream: Upstream<UpstreamApi, H>, val status: UpstreamAvailability, val ts: Instant = Instant.now())
|
||||
|
||||
class FilterBestAvailability() : Predicate<UpstreamStatus<*>> {
|
||||
private val lastRef = AtomicReference<UpstreamStatus<*>>()
|
||||
|
||||
override fun test(t: UpstreamStatus<*>): Boolean {
|
||||
val last = lastRef.get()
|
||||
val changed = last == null
|
||||
|| t.status > last.status
|
||||
|| (last.upstream == t.upstream && t.status != last.status)
|
||||
|| last.ts.isBefore(Instant.now() - Duration.ofSeconds(60))
|
||||
if (changed) {
|
||||
lastRef.set(t)
|
||||
}
|
||||
return changed
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ package io.emeraldpay.dshackle.upstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import org.reactivestreams.Publisher
|
||||
|
||||
interface ApiSource: Publisher<DirectEthereumApi> {
|
||||
interface ApiSource<U : UpstreamApi> : Publisher<U> {
|
||||
|
||||
fun resolve()
|
||||
fun request(tries: Int)
|
||||
|
||||
@@ -17,38 +17,34 @@ package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHeadMerge
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.Lifecycle
|
||||
import reactor.core.Disposable
|
||||
import reactor.core.publisher.Mono
|
||||
import java.lang.IllegalStateException
|
||||
import java.time.Duration
|
||||
|
||||
open class ChainUpstreams (
|
||||
/**
|
||||
* General interface to upstream(s) to a single chain
|
||||
*/
|
||||
abstract class ChainUpstreams<U : UpstreamApi, B>(
|
||||
val chain: Chain,
|
||||
private val upstreams: MutableList<Upstream>,
|
||||
private val upstreams: MutableList<Upstream<U, B>>,
|
||||
caches: Caches,
|
||||
objectMapper: ObjectMapper
|
||||
) : AggregatedUpstream(objectMapper, caches), Lifecycle {
|
||||
) : AggregatedUpstream<U, B>(objectMapper, caches), Lifecycle {
|
||||
|
||||
private val log = LoggerFactory.getLogger(ChainUpstreams::class.java)
|
||||
private var seq = 0
|
||||
private var head: EthereumHead? = null
|
||||
private var lagObserver: HeadLagObserver? = null
|
||||
protected var lagObserver: HeadLagObserver<U, B>? = null
|
||||
private var subscription: Disposable? = null
|
||||
|
||||
init {
|
||||
if (upstreams.size > 0) {
|
||||
head = updateHead()
|
||||
onUpstreamsUpdated()
|
||||
}
|
||||
open fun init() {
|
||||
onUpstreamsUpdated()
|
||||
}
|
||||
|
||||
abstract fun updateHead(): Head<B>
|
||||
abstract fun setHead(head: Head<B>)
|
||||
|
||||
override fun getId(): String {
|
||||
return "!all:${chain.chainCode}"
|
||||
}
|
||||
@@ -68,7 +64,7 @@ open class ChainUpstreams (
|
||||
super.stop()
|
||||
subscription?.dispose()
|
||||
subscription = null
|
||||
head?.let {
|
||||
getHead().let {
|
||||
if (it is Lifecycle) {
|
||||
it.stop()
|
||||
}
|
||||
@@ -76,50 +72,24 @@ open class ChainUpstreams (
|
||||
lagObserver?.stop()
|
||||
}
|
||||
|
||||
internal fun updateHead(): EthereumHead {
|
||||
head?.let {
|
||||
if (it is Lifecycle) {
|
||||
it.stop()
|
||||
}
|
||||
}
|
||||
lagObserver?.stop()
|
||||
lagObserver = null
|
||||
val head = if (upstreams.size == 1) {
|
||||
val upstream = upstreams.first()
|
||||
upstream.setLag(0)
|
||||
upstream.getHead()
|
||||
} else {
|
||||
val newHead = EthereumHeadMerge(upstreams.map { it.getHead() }).apply {
|
||||
this.start()
|
||||
}
|
||||
val lagObserver = HeadLagObserver(newHead, upstreams).apply {
|
||||
this.start()
|
||||
}
|
||||
this.lagObserver = lagObserver
|
||||
newHead
|
||||
}
|
||||
onHeadUpdated(head)
|
||||
return head
|
||||
}
|
||||
|
||||
override fun getAll(): List<Upstream> {
|
||||
override fun getAll(): List<Upstream<U, B>> {
|
||||
return upstreams
|
||||
}
|
||||
|
||||
override fun addUpstream(upstream: Upstream) {
|
||||
override fun addUpstream(upstream: Upstream<U, B>) {
|
||||
upstreams.add(upstream)
|
||||
head = updateHead()
|
||||
setHead(updateHead())
|
||||
onUpstreamsUpdated()
|
||||
}
|
||||
|
||||
fun removeUpstream(id: String) {
|
||||
if (upstreams.removeIf { it.getId() == id }) {
|
||||
head = updateHead()
|
||||
setHead(updateHead())
|
||||
onUpstreamsUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getApis(matcher: Selector.Matcher): ApiSource {
|
||||
override fun getApis(matcher: Selector.Matcher): ApiSource<U> {
|
||||
val i = seq++
|
||||
if (seq >= Int.MAX_VALUE / 2) {
|
||||
seq = 0
|
||||
@@ -127,15 +97,11 @@ open class ChainUpstreams (
|
||||
return FilteredApis(upstreams, matcher, i)
|
||||
}
|
||||
|
||||
override fun getApi(matcher: Selector.Matcher): Mono<DirectEthereumApi> {
|
||||
override fun getApi(matcher: Selector.Matcher): Mono<U> {
|
||||
val apis = getApis(matcher)
|
||||
apis.request(1)
|
||||
return Mono.from(apis)
|
||||
.switchIfEmpty(Mono.error<DirectEthereumApi>(Exception("No API available")))
|
||||
}
|
||||
|
||||
override fun getHead(): EthereumHead {
|
||||
return head!!
|
||||
.switchIfEmpty(Mono.error<U>(Exception("No API available")))
|
||||
}
|
||||
|
||||
override fun setLag(lag: Long) {
|
||||
@@ -145,28 +111,5 @@ open class ChainUpstreams (
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getLabels(): Collection<UpstreamsConfig.Labels> {
|
||||
return upstreams.flatMap { it.getLabels() }
|
||||
}
|
||||
|
||||
fun printStatus() {
|
||||
var height: Long? = null
|
||||
try {
|
||||
height = head!!.getFlux().next().block(Duration.ofSeconds(1))?.number
|
||||
} catch (e: IllegalStateException) {
|
||||
//timout
|
||||
} catch (e: Exception) {
|
||||
log.warn("Head processing error: ${e.javaClass} ${e.message}")
|
||||
}
|
||||
val statuses = upstreams.map { it.getStatus() }
|
||||
.groupBy { it }
|
||||
.map { "${it.key.name}/${it.value.size}" }
|
||||
.joinToString(",")
|
||||
val lag = upstreams.map { it.getLag() }
|
||||
.joinToString(", ")
|
||||
|
||||
log.info("State of ${chain.chainCode}: height=${height ?: '?'}, status=$statuses, lag=[$lag]")
|
||||
}
|
||||
|
||||
|
||||
abstract fun printStatus()
|
||||
}
|
||||
@@ -16,13 +16,17 @@
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.CachesEnabled
|
||||
import io.emeraldpay.dshackle.cache.CachesFactory
|
||||
import io.emeraldpay.dshackle.startup.UpstreamChange
|
||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.QuorumBasedMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
@@ -42,22 +46,23 @@ class CurrentUpstreams(
|
||||
|
||||
private val log = LoggerFactory.getLogger(CurrentUpstreams::class.java)
|
||||
|
||||
private val chainMapping = ConcurrentHashMap<Chain, ChainUpstreams>()
|
||||
private val chainMapping = ConcurrentHashMap<Chain, ChainUpstreams<*, *>>()
|
||||
private val chainsBus = TopicProcessor.create<Chain>()
|
||||
private val callTargets = HashMap<Chain, QuorumBasedMethods>()
|
||||
private val callTargets = HashMap<Chain, CallMethods>()
|
||||
private val updateLock = ReentrantLock()
|
||||
|
||||
fun update(change: UpstreamChange) {
|
||||
updateLock.withLock {
|
||||
val chain = change.chain
|
||||
val up = change.upstream
|
||||
val current = chainMapping[chain]
|
||||
.cast(EthereumUpstream::class.java, EthereumApi::class.java, BlockJson::class.java) as Upstream<EthereumApi, BlockJson<TransactionRefJson>>
|
||||
val current = chainMapping[chain] as ChainUpstreams<EthereumApi, BlockJson<TransactionRefJson>>?
|
||||
if (change.type == UpstreamChange.ChangeType.REMOVED) {
|
||||
current?.removeUpstream(up.getId())
|
||||
log.info("Upstream ${change.upstream.getId()} with chain $chain has been removed")
|
||||
} else {
|
||||
if (current == null) {
|
||||
val created = ChainUpstreams(chain, ArrayList<Upstream>(), cachesFactory.getCaches(chain), objectMapper)
|
||||
val created = EthereumChainUpstreams(chain, ArrayList(), cachesFactory.getCaches(chain), objectMapper)
|
||||
if (up is CachesEnabled) {
|
||||
up.setCaches(created.caches)
|
||||
}
|
||||
@@ -79,7 +84,7 @@ class CurrentUpstreams(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getUpstream(chain: Chain): AggregatedUpstream? {
|
||||
override fun getUpstream(chain: Chain): AggregatedUpstream<*, *>? {
|
||||
return chainMapping[chain]
|
||||
}
|
||||
|
||||
@@ -103,8 +108,8 @@ class CurrentUpstreams(
|
||||
return callTargets[chain] ?: return setupDefaultMethods(chain)
|
||||
}
|
||||
|
||||
fun setupDefaultMethods(chain: Chain): QuorumBasedMethods {
|
||||
val created = QuorumBasedMethods(objectMapper, chain)
|
||||
fun setupDefaultMethods(chain: Chain): CallMethods {
|
||||
val created = DefaultEthereumMethods(objectMapper, chain)
|
||||
callTargets[chain] = created
|
||||
return created
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.TopicProcessor
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
abstract class DefaultUpstream(
|
||||
abstract class DefaultUpstream<U : UpstreamApi, B>(
|
||||
defaultLag: Long,
|
||||
defaultAvail: UpstreamAvailability
|
||||
) : Upstream {
|
||||
) : Upstream<U, B> {
|
||||
|
||||
constructor() : this(Long.MAX_VALUE, UpstreamAvailability.UNAVAILABLE)
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import org.reactivestreams.Subscriber
|
||||
import reactor.core.publisher.EmitterProcessor
|
||||
import reactor.core.publisher.Flux
|
||||
@@ -26,28 +25,28 @@ import kotlin.math.pow
|
||||
import kotlin.math.roundToLong
|
||||
import kotlin.random.Random
|
||||
|
||||
class FilteredApis(
|
||||
allUpstreams: List<Upstream>,
|
||||
class FilteredApis<U : UpstreamApi>(
|
||||
allUpstreams: List<Upstream<U, *>>,
|
||||
private val matcher: Selector.Matcher,
|
||||
pos: Int,
|
||||
private val repeatLimit: Long,
|
||||
jitter: Int
|
||||
): ApiSource {
|
||||
) : ApiSource<U> {
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_DELAY_STEP = 100
|
||||
private const val MAX_WAIT_MILLIS = 5000L
|
||||
}
|
||||
|
||||
constructor(allUpstreams: List<Upstream>,
|
||||
constructor(allUpstreams: List<Upstream<U, *>>,
|
||||
matcher: Selector.Matcher,
|
||||
pos: Int): this(allUpstreams, matcher, pos, 10, 7)
|
||||
pos: Int) : this(allUpstreams, matcher, pos, 10, 7)
|
||||
|
||||
constructor(allUpstreams: List<Upstream>,
|
||||
matcher: Selector.Matcher): this(allUpstreams, matcher, 0, 10, 10)
|
||||
constructor(allUpstreams: List<Upstream<U, *>>,
|
||||
matcher: Selector.Matcher) : this(allUpstreams, matcher, 0, 10, 10)
|
||||
|
||||
private val delay: Int
|
||||
private val upstreams: List<Upstream>
|
||||
private val upstreams: List<Upstream<UpstreamApi, *>>
|
||||
|
||||
private val control = EmitterProcessor.create<Boolean>(32, false)
|
||||
|
||||
@@ -75,18 +74,18 @@ class FilteredApis(
|
||||
return Duration.ofMillis(time)
|
||||
}
|
||||
|
||||
override fun subscribe(subscriber: Subscriber<in DirectEthereumApi>) {
|
||||
override fun subscribe(subscriber: Subscriber<in U>) {
|
||||
val first = Flux.fromIterable(upstreams)
|
||||
val retries = (1 until repeatLimit).map { r ->
|
||||
Flux.fromIterable(upstreams).delaySubscription(waitDuration(r))
|
||||
}.let { Flux.concat(it) }
|
||||
|
||||
Flux.concat(first, retries)
|
||||
.filter(Upstream::isAvailable)
|
||||
.filter(Upstream<UpstreamApi, *>::isAvailable)
|
||||
.filter(matcher::matches)
|
||||
.flatMap { it.getApi(matcher) }
|
||||
.zipWith(control).map { it.t1 }
|
||||
.subscribe(subscriber)
|
||||
.subscribe(subscriber as Subscriber<in UpstreamApi>)
|
||||
}
|
||||
|
||||
override fun resolve() {
|
||||
|
||||
@@ -18,6 +18,6 @@ package io.emeraldpay.dshackle.upstream
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
interface Head<T> {
|
||||
fun getFlux(): Flux<T>
|
||||
interface Head<out T> {
|
||||
fun getFlux(): Flux<out T>
|
||||
}
|
||||
@@ -15,23 +15,21 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.Lifecycle
|
||||
import reactor.core.Disposable
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.toFlux
|
||||
import reactor.util.function.Tuple2
|
||||
import reactor.util.function.Tuples
|
||||
import java.time.Duration
|
||||
|
||||
class HeadLagObserver (
|
||||
private val master: EthereumHead,
|
||||
private val followers: Collection<Upstream>
|
||||
): Lifecycle {
|
||||
/**
|
||||
* Observer group of upstreams and defined a distance in blocks (lag) between a leader (best height/difficulty) and
|
||||
* other upstreams.
|
||||
*/
|
||||
abstract class HeadLagObserver<A : UpstreamApi, B>(
|
||||
private val master: Head<B>,
|
||||
private val followers: Collection<Upstream<A, B>>
|
||||
) : Lifecycle {
|
||||
|
||||
private val log = LoggerFactory.getLogger(HeadLagObserver::class.java)
|
||||
|
||||
@@ -50,7 +48,7 @@ class HeadLagObserver (
|
||||
current = null
|
||||
}
|
||||
|
||||
private fun subscription(): Flux<Unit> {
|
||||
fun subscription(): Flux<Unit> {
|
||||
return master.getFlux()
|
||||
.flatMap(this::probeFollowers)
|
||||
.map { item ->
|
||||
@@ -58,38 +56,29 @@ class HeadLagObserver (
|
||||
}
|
||||
}
|
||||
|
||||
fun probeFollowers(top: BlockJson<TransactionRefJson>): Flux<Tuple2<Long, Upstream>> {
|
||||
return followers.toFlux()
|
||||
fun probeFollowers(top: B): Flux<Tuple2<Long, Upstream<A, B>>> {
|
||||
return Flux.fromIterable(followers)
|
||||
.parallel(followers.size)
|
||||
.flatMap { mapLagging(top, it, getCurrentBlocks(it)) }
|
||||
.sequential()
|
||||
.onErrorContinue { t, _ -> log.warn("Failed to update lagging distance", t) }
|
||||
}
|
||||
|
||||
fun getCurrentBlocks(up: Upstream): Flux<BlockJson<TransactionRefJson>> {
|
||||
val head = up.getHead()
|
||||
return head.getFlux().take(Duration.ofSeconds(1))
|
||||
}
|
||||
abstract fun getCurrentBlocks(up: Upstream<A, B>): Flux<B>
|
||||
|
||||
fun mapLagging(top: BlockJson<TransactionRefJson>, up: Upstream, blocks: Flux<BlockJson<TransactionRefJson>>): Flux<Tuple2<Long, Upstream>> {
|
||||
fun mapLagging(top: B, up: Upstream<A, B>, blocks: Flux<B>): Flux<Tuple2<Long, Upstream<A, B>>> {
|
||||
return blocks
|
||||
.map { extractDistance(top, it) }
|
||||
.takeUntil{ lag -> lag <= 0L }
|
||||
.takeUntil { lag -> lag <= 0L }
|
||||
.map { Tuples.of(it, up) }
|
||||
.doOnError { t ->
|
||||
log.warn("Failed to find distance for $up", t)
|
||||
}
|
||||
}
|
||||
|
||||
fun extractDistance(top: BlockJson<TransactionRefJson>, curr: BlockJson<TransactionRefJson>): Long {
|
||||
return when {
|
||||
curr.number > top.number -> if (curr.totalDifficulty >= top.totalDifficulty) 0 else forkDistance(top, curr)
|
||||
curr.number == top.number -> if (curr.totalDifficulty == top.totalDifficulty) 0 else forkDistance(top, curr)
|
||||
else -> top.number - curr.number
|
||||
}
|
||||
}
|
||||
abstract fun extractDistance(top: B, curr: B): Long
|
||||
|
||||
fun forkDistance(top: BlockJson<TransactionRefJson>, curr: BlockJson<TransactionRefJson>): Long {
|
||||
fun forkDistance(top: B, curr: B): Long {
|
||||
//TODO look for common ancestor? though it may be a corruption
|
||||
return 6
|
||||
}
|
||||
|
||||
@@ -95,13 +95,13 @@ class Selector {
|
||||
}
|
||||
|
||||
interface Matcher {
|
||||
fun matches(up: Upstream): Boolean
|
||||
fun matches(up: Upstream<UpstreamApi, *>): Boolean
|
||||
}
|
||||
|
||||
class MultiMatcher(
|
||||
private val matchers: Collection<Matcher>
|
||||
): Matcher {
|
||||
override fun matches(up: Upstream): Boolean {
|
||||
override fun matches(up: Upstream<UpstreamApi, *>): Boolean {
|
||||
return matchers.all { it.matches(up) }
|
||||
}
|
||||
|
||||
@@ -113,21 +113,22 @@ class Selector {
|
||||
class MethodMatcher(
|
||||
val method: String
|
||||
): Matcher {
|
||||
override fun matches(up: Upstream): Boolean {
|
||||
override fun matches(up: Upstream<UpstreamApi, *>): Boolean {
|
||||
return up.getMethods().isAllowed(method)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class LabelSelectorMatcher: Matcher {
|
||||
override fun matches(up: Upstream): Boolean {
|
||||
override fun matches(up: Upstream<UpstreamApi, *>): Boolean {
|
||||
return up.getLabels().any(this::matches)
|
||||
}
|
||||
|
||||
abstract fun matches(labels: UpstreamsConfig.Labels): Boolean
|
||||
abstract fun asProto(): BlockchainOuterClass.Selector?
|
||||
}
|
||||
|
||||
class EmptyMatcher: Matcher {
|
||||
override fun matches(up: Upstream): Boolean {
|
||||
override fun matches(up: Upstream<UpstreamApi, *>): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ class Selector {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun matches(up: Upstream): Boolean {
|
||||
override fun matches(up: Upstream<UpstreamApi, *>): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,16 +22,18 @@ import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
interface Upstream {
|
||||
interface Upstream<out T : UpstreamApi, out B> {
|
||||
fun isAvailable(): Boolean
|
||||
fun getStatus(): UpstreamAvailability
|
||||
fun observeStatus(): Flux<UpstreamAvailability>
|
||||
fun getHead(): EthereumHead
|
||||
fun getApi(matcher: Selector.Matcher): Mono<DirectEthereumApi>
|
||||
fun getHead(): Head<B>
|
||||
fun getApi(matcher: Selector.Matcher): Mono<out T>
|
||||
fun getOptions(): UpstreamsConfig.Options
|
||||
fun setLag(lag: Long)
|
||||
fun getLag(): Long
|
||||
fun getLabels(): Collection<UpstreamsConfig.Labels>
|
||||
fun getMethods(): CallMethods
|
||||
fun getId(): String
|
||||
|
||||
fun <T : Upstream<TA, BA>, TA : UpstreamApi, BA> cast(selfType: Class<T>, upstreamType: Class<TA>, blockType: Class<BA>): T
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2020 ETCDEV GmbH
|
||||
*
|
||||
* 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
|
||||
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* A general interface to make a request to an Upstream API
|
||||
*/
|
||||
interface UpstreamApi {
|
||||
|
||||
/**
|
||||
* @param id an internal uniq id, if multiple requests are made in batch
|
||||
* @param method JSON RPC method name
|
||||
* @param params JSON RPC parameters, must be serializable into a JSON array
|
||||
*/
|
||||
fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray>
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import io.emeraldpay.grpc.Chain
|
||||
import reactor.core.publisher.Flux
|
||||
|
||||
interface Upstreams {
|
||||
fun getUpstream(chain: Chain): AggregatedUpstream?
|
||||
fun getUpstream(chain: Chain): AggregatedUpstream<*, *>?
|
||||
fun getAvailable(): List<Chain>
|
||||
fun observeChains(): Flux<Chain>
|
||||
fun getDefaultMethods(chain: Chain): CallMethods
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.*
|
||||
* Default configuration for Ethereum based RPC. Defines optimal Quorum strategies for different methods, and provides
|
||||
* hardcoded results for base methods, such as `net_version`, `web3_clientVersion` and similar
|
||||
*/
|
||||
class QuorumBasedMethods(
|
||||
class DefaultEthereumMethods(
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val chain: Chain
|
||||
) : CallMethods {
|
||||
@@ -102,6 +102,7 @@ class QuorumBasedMethods(
|
||||
override fun isAllowed(method: String): Boolean {
|
||||
return allowedMethods.contains(method)
|
||||
}
|
||||
|
||||
override fun isHardcoded(method: String): Boolean {
|
||||
return hardcodedMethods.contains(method)
|
||||
}
|
||||
@@ -17,23 +17,24 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamApi
|
||||
import io.infinitape.etherjar.rpc.*
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import reactor.core.publisher.Mono
|
||||
import java.io.InputStream
|
||||
|
||||
abstract class EthereumApi(
|
||||
objectMapper: ObjectMapper
|
||||
) {
|
||||
) : UpstreamApi {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(EthereumApi::class.java)
|
||||
}
|
||||
|
||||
private val jacksonRpcConverter = JacksonRpcConverter(objectMapper)
|
||||
var upstream: Upstream? = null
|
||||
|
||||
abstract fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray>
|
||||
var upstream: Upstream<EthereumApi, BlockJson<TransactionRefJson>>? = null
|
||||
|
||||
fun <JS, RS> execute(rpcCall: RpcCall<JS, RS>): Mono<ByteArray> {
|
||||
return execute(0, rpcCall.method, rpcCall.params as List<Any>)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2020 ETCDEV GmbH
|
||||
*
|
||||
* 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
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.upstream.*
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.Lifecycle
|
||||
import java.lang.IllegalStateException
|
||||
import java.time.Duration
|
||||
|
||||
class EthereumChainUpstreams(
|
||||
chain: Chain,
|
||||
val upstreams: MutableList<EthereumUpstream>,
|
||||
caches: Caches,
|
||||
objectMapper: ObjectMapper
|
||||
) : ChainUpstreams<EthereumApi, BlockJson<TransactionRefJson>>(chain, upstreams as MutableList<Upstream<EthereumApi, BlockJson<TransactionRefJson>>>, caches, objectMapper) {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(EthereumChainUpstreams::class.java)
|
||||
}
|
||||
|
||||
private var head: EthereumHead? = null
|
||||
|
||||
init {
|
||||
this.init()
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
if (upstreams.size > 0) {
|
||||
head = updateHead()
|
||||
}
|
||||
super.init()
|
||||
}
|
||||
|
||||
override fun getHead(): EthereumHead {
|
||||
return head!!
|
||||
}
|
||||
|
||||
override fun setHead(head: Head<BlockJson<TransactionRefJson>>) {
|
||||
this.head = head as EthereumHead
|
||||
}
|
||||
|
||||
override fun updateHead(): EthereumHead {
|
||||
head?.let {
|
||||
if (it is Lifecycle) {
|
||||
it.stop()
|
||||
}
|
||||
}
|
||||
lagObserver?.stop()
|
||||
lagObserver = null
|
||||
val head = if (upstreams.size == 1) {
|
||||
val upstream = upstreams.first()
|
||||
upstream.setLag(0)
|
||||
upstream.getHead()
|
||||
} else {
|
||||
val newHead = EthereumHeadMerge(upstreams.map { it.getHead() }).apply {
|
||||
this.start()
|
||||
}
|
||||
val lagObserver = EthereumHeadLagObserver(newHead, upstreams).apply {
|
||||
this.start()
|
||||
}
|
||||
this.lagObserver = lagObserver
|
||||
newHead
|
||||
}
|
||||
onHeadUpdated(head)
|
||||
return head
|
||||
}
|
||||
|
||||
override fun getLabels(): Collection<UpstreamsConfig.Labels> {
|
||||
return upstreams.flatMap { it.getLabels() }
|
||||
}
|
||||
|
||||
override fun printStatus() {
|
||||
var height: Long? = null
|
||||
try {
|
||||
height = getHead().getFlux().next().block(Duration.ofSeconds(1))?.number
|
||||
} catch (e: IllegalStateException) {
|
||||
//timout
|
||||
} catch (e: Exception) {
|
||||
log.warn("Head processing error: ${e.javaClass} ${e.message}")
|
||||
}
|
||||
val statuses = upstreams.map { it.getStatus() }
|
||||
.groupBy { it }
|
||||
.map { "${it.key.name}/${it.value.size}" }
|
||||
.joinToString(",")
|
||||
val lag = upstreams.map { it.getLag() }
|
||||
.joinToString(", ")
|
||||
|
||||
log.info("State of ${chain.chainCode}: height=${height ?: '?'}, status=$statuses, lag=[$lag]")
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
override fun <T : Upstream<TA, BA>, TA : UpstreamApi, BA> cast(selfType: Class<T>, upstreamType: Class<TA>, blockType: Class<BA>): T {
|
||||
if (!selfType.isAssignableFrom(this.javaClass)) {
|
||||
throw ClassCastException("Cannot cast ${this.javaClass} to $selfType")
|
||||
}
|
||||
if (!upstreamType.isAssignableFrom(EthereumApi::class.java)) {
|
||||
throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $upstreamType")
|
||||
}
|
||||
if (!blockType.isAssignableFrom(BlockJson::class.java)) {
|
||||
throw ClassCastException("Cannot cast ${BlockJson::class.java} to $blockType")
|
||||
}
|
||||
return this as T
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2020 ETCDEV GmbH
|
||||
*
|
||||
* 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
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import reactor.core.publisher.Flux
|
||||
import java.time.Duration
|
||||
|
||||
class EthereumHeadLagObserver(
|
||||
master: EthereumHead,
|
||||
followers: Collection<Upstream<EthereumApi, BlockJson<TransactionRefJson>>>
|
||||
) : HeadLagObserver<EthereumApi, BlockJson<TransactionRefJson>>(master, followers) {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(EthereumHeadLagObserver::class.java)
|
||||
}
|
||||
|
||||
override fun getCurrentBlocks(up: Upstream<EthereumApi, BlockJson<TransactionRefJson>>): Flux<BlockJson<TransactionRefJson>> {
|
||||
val head = up.getHead()
|
||||
return Flux.from(head.getFlux()).take(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
override fun extractDistance(top: BlockJson<TransactionRefJson>, curr: BlockJson<TransactionRefJson>): Long {
|
||||
return when {
|
||||
curr.number > top.number -> if (curr.totalDifficulty >= top.totalDifficulty) 0 else forkDistance(top, curr)
|
||||
curr.number == top.number -> if (curr.totalDifficulty == top.totalDifficulty) 0 else forkDistance(top, curr)
|
||||
else -> top.number - curr.number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import io.emeraldpay.dshackle.upstream.*
|
||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.Lifecycle
|
||||
import reactor.core.Disposable
|
||||
@@ -37,9 +39,9 @@ open class EthereumUpstream(
|
||||
private val options: UpstreamsConfig.Options,
|
||||
val node: QuorumForLabels.QuorumItem,
|
||||
private val targets: CallMethods
|
||||
): DefaultUpstream(), CachesEnabled, Lifecycle {
|
||||
) : DefaultUpstream<EthereumApi, BlockJson<TransactionRefJson>>(), Upstream<EthereumApi, BlockJson<TransactionRefJson>>, CachesEnabled, Lifecycle {
|
||||
|
||||
constructor(id: String, chain: Chain, api: DirectEthereumApi): this(id, chain, api, null,
|
||||
constructor(id: String, chain: Chain, api: DirectEthereumApi) : this(id, chain, api, null,
|
||||
UpstreamsConfig.Options.getDefaults(), QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels()),
|
||||
DirectCallMethods())
|
||||
|
||||
@@ -133,4 +135,18 @@ open class EthereumUpstream(
|
||||
return targets
|
||||
}
|
||||
|
||||
@Suppress("unchecked")
|
||||
override fun <T : Upstream<TA, BA>, TA : UpstreamApi, BA> cast(selfType: Class<T>, upstreamType: Class<TA>, blockType: Class<BA>): T {
|
||||
if (!selfType.isAssignableFrom(this.javaClass)) {
|
||||
throw ClassCastException("Cannot cast ${this.javaClass} to $selfType")
|
||||
}
|
||||
if (!upstreamType.isAssignableFrom(EthereumApi::class.java)) {
|
||||
throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $upstreamType")
|
||||
}
|
||||
if (!blockType.isAssignableFrom(BlockJson::class.java)) {
|
||||
throw ClassCastException("Cannot cast ${BlockJson::class.java} to $blockType")
|
||||
}
|
||||
return this as T
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DefaultEthereumHead
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
@@ -51,16 +52,16 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.function.Function
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
open class GrpcUpstream(
|
||||
open class EthereumGrpcUpstream(
|
||||
private val parentId: String,
|
||||
private val chain: Chain,
|
||||
private val blockchainStub: ReactorBlockchainGrpc.ReactorBlockchainStub,
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val rpcClient: ReactorEmeraldClient
|
||||
): DefaultUpstream(), CachesEnabled, Lifecycle {
|
||||
) : DefaultUpstream<EthereumApi, BlockJson<TransactionRefJson>>(), CachesEnabled, Lifecycle {
|
||||
|
||||
private var allLabels: Collection<UpstreamsConfig.Labels> = ArrayList<UpstreamsConfig.Labels>()
|
||||
private val log = LoggerFactory.getLogger(GrpcUpstream::class.java)
|
||||
private val log = LoggerFactory.getLogger(EthereumGrpcUpstream::class.java)
|
||||
private var caches: Caches? = null
|
||||
|
||||
private val options = UpstreamsConfig.Options.getDefaults()
|
||||
@@ -214,4 +215,18 @@ open class GrpcUpstream(
|
||||
this.caches = caches
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
override fun <T : Upstream<TA, BA>, TA : UpstreamApi, BA> cast(selfType: Class<T>, upstreamType: Class<TA>, blockType: Class<BA>): T {
|
||||
if (!selfType.isAssignableFrom(this.javaClass)) {
|
||||
throw ClassCastException("Cannot cast ${this.javaClass} to $selfType")
|
||||
}
|
||||
if (!upstreamType.isAssignableFrom(EthereumApi::class.java)) {
|
||||
throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $upstreamType")
|
||||
}
|
||||
if (!blockType.isAssignableFrom(BlockJson::class.java)) {
|
||||
throw ClassCastException("Cannot cast ${BlockJson::class.java} to $blockType")
|
||||
}
|
||||
return this as T
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,10 +18,10 @@ package io.emeraldpay.dshackle.upstream.grpc
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
|
||||
import io.emeraldpay.dshackle.BlockchainType
|
||||
import io.emeraldpay.dshackle.Defaults
|
||||
import io.emeraldpay.dshackle.FileResolver
|
||||
import io.emeraldpay.dshackle.config.AuthConfig
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
import io.emeraldpay.dshackle.startup.UpstreamChange
|
||||
import io.emeraldpay.grpc.Chain
|
||||
@@ -54,7 +54,7 @@ class GrpcUpstreams(
|
||||
var timeout = Defaults.timeout
|
||||
|
||||
private var client: ReactorBlockchainGrpc.ReactorBlockchainStub? = null
|
||||
private val known = HashMap<Chain, GrpcUpstream>()
|
||||
private val known = HashMap<Chain, EthereumGrpcUpstream>()
|
||||
private val lock = ReentrantLock()
|
||||
private var grpcTransport: ReactorEmeraldClient? = null
|
||||
|
||||
@@ -117,7 +117,7 @@ class GrpcUpstreams(
|
||||
}.map { chainDetails ->
|
||||
val chain = Chain.byId(chainDetails.chain.number)
|
||||
val up = getOrCreate(chain)
|
||||
(up.upstream as GrpcUpstream).init(chainDetails)
|
||||
(up.upstream as EthereumGrpcUpstream).init(chainDetails)
|
||||
up
|
||||
}
|
||||
|
||||
@@ -155,10 +155,13 @@ class GrpcUpstreams(
|
||||
}
|
||||
|
||||
fun getOrCreate(chain: Chain): UpstreamChange {
|
||||
if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) {
|
||||
throw IllegalArgumentException("Unsupported blockchain: $chain")
|
||||
}
|
||||
lock.withLock {
|
||||
val current = known[chain]
|
||||
return if (current == null) {
|
||||
val created = GrpcUpstream(id, chain, client!!, objectMapper, grpcTransport!!.copyForChain(chain))
|
||||
val created = EthereumGrpcUpstream(id, chain, client!!, objectMapper, grpcTransport!!.copyForChain(chain))
|
||||
created.timeout = this.timeout
|
||||
known[chain] = created
|
||||
created.start()
|
||||
@@ -169,7 +172,7 @@ class GrpcUpstreams(
|
||||
}
|
||||
}
|
||||
|
||||
fun get(chain: Chain): GrpcUpstream {
|
||||
fun get(chain: Chain): EthereumGrpcUpstream {
|
||||
return known[chain]!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.emeraldpay.dshackle
|
||||
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import spock.lang.Specification
|
||||
|
||||
class BlockchainTypeSpec extends Specification {
|
||||
|
||||
def "Correct type for ethereum"() {
|
||||
expect:
|
||||
BlockchainType.fromBlockchain(chain) == BlockchainType.ETHEREUM
|
||||
where:
|
||||
chain << [Chain.ETHEREUM, Chain.ETHEREUM_CLASSIC, Chain.TESTNET_KOVAN, Chain.TESTNET_MORDEN]
|
||||
}
|
||||
|
||||
def "Correct type for bitcoin"() {
|
||||
expect:
|
||||
BlockchainType.fromBlockchain(chain) == BlockchainType.BITCOIN
|
||||
where:
|
||||
chain << [Chain.BITCOIN, Chain.TESTNET_BITCOIN]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
@@ -38,7 +39,7 @@ class StreamHeadSpec extends Specification {
|
||||
|
||||
def "Errors on unavailable chain"() {
|
||||
setup:
|
||||
def upstreams = new UpstreamsMock(Chain.ETHEREUM, Stub(Upstream))
|
||||
def upstreams = new UpstreamsMock(Chain.ETHEREUM, Stub(EthereumUpstream))
|
||||
def streamHead = new StreamHead(upstreams)
|
||||
when:
|
||||
def flux = streamHead.add(
|
||||
|
||||
@@ -33,7 +33,7 @@ import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class TrackAddressSpec extends Specification {
|
||||
class TrackEthereumAddressSpec extends Specification {
|
||||
|
||||
def chain = Common.ChainRef.CHAIN_ETHEREUM
|
||||
def address1 = "0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f"
|
||||
@@ -59,7 +59,7 @@ class TrackAddressSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackAddress trackAddress = new TrackAddress(upstreams, Schedulers.immediate())
|
||||
TrackEthereumAddress trackAddress = new TrackEthereumAddress(upstreams, Schedulers.immediate())
|
||||
trackAddress.init()
|
||||
|
||||
apiMock.answer("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0x499602D2")
|
||||
@@ -101,7 +101,7 @@ class TrackAddressSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackAddress trackAddress = new TrackAddress(upstreams, Schedulers.immediate())
|
||||
TrackEthereumAddress trackAddress = new TrackEthereumAddress(upstreams, Schedulers.immediate())
|
||||
trackAddress.init()
|
||||
|
||||
apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0x499602D2")
|
||||
@@ -36,7 +36,7 @@ import spock.lang.Specification
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class TrackTxSpec extends Specification {
|
||||
class TrackEthereumTxSpec extends Specification {
|
||||
|
||||
def chain = Common.ChainRef.CHAIN_ETHEREUM
|
||||
def txId = "0xba61ce4672751fd6086a9ac2b55547a5555af17535b6c0334ede2ecb6d64070a"
|
||||
@@ -45,8 +45,8 @@ class TrackTxSpec extends Specification {
|
||||
def "Gives details for an old transaction"() {
|
||||
setup:
|
||||
def req = BlockchainOuterClass.TxStatusRequest.newBuilder()
|
||||
.setChain(chain)
|
||||
.setConfirmationLimit(6)
|
||||
.setChain(chain)
|
||||
.setConfirmationLimit(6)
|
||||
.setTxId(txId)
|
||||
.build()
|
||||
|
||||
@@ -91,7 +91,7 @@ class TrackTxSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackTx trackTx = new TrackTx(upstreams, Schedulers.immediate())
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.immediate())
|
||||
trackTx.init()
|
||||
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], txJson)
|
||||
@@ -123,7 +123,7 @@ class TrackTxSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackTx trackTx = new TrackTx(upstreams, Schedulers.immediate())
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.immediate())
|
||||
trackTx.init()
|
||||
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], null)
|
||||
@@ -180,7 +180,7 @@ class TrackTxSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackTx trackTx = new TrackTx(upstreams, Schedulers.immediate())
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.immediate())
|
||||
trackTx.init()
|
||||
|
||||
apiMock.answerOnce("eth_getTransactionByHash", [txId], null)
|
||||
@@ -279,7 +279,7 @@ class TrackTxSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackTx trackTx = new TrackTx(upstreams, Schedulers.immediate())
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.immediate())
|
||||
trackTx.init()
|
||||
|
||||
apiMock.answerOnce("eth_getTransactionByHash", [txId], null)
|
||||
@@ -320,7 +320,7 @@ class TrackTxSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackTx trackTx = new TrackTx(upstreams, Schedulers.immediate())
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.immediate())
|
||||
trackTx.init()
|
||||
|
||||
def req = BlockchainOuterClass.TxStatusRequest.newBuilder()
|
||||
@@ -341,7 +341,7 @@ class TrackTxSpec extends Specification {
|
||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||
TrackTx trackTx = new TrackTx(upstreams, Schedulers.immediate())
|
||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams, Schedulers.immediate())
|
||||
trackTx.init()
|
||||
|
||||
def req = BlockchainOuterClass.TxStatusRequest.newBuilder()
|
||||
@@ -18,7 +18,7 @@ package io.emeraldpay.dshackle.test
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.upstream.calls.QuorumBasedMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
@@ -33,11 +33,11 @@ class EthereumUpstreamMock extends EthereumUpstream {
|
||||
EthereumHeadMock ethereumHeadMock = new EthereumHeadMock()
|
||||
|
||||
EthereumUpstreamMock(@NotNull Chain chain, @NotNull DirectEthereumApi api) {
|
||||
this(chain, api, new QuorumBasedMethods(TestingCommons.objectMapper(), chain))
|
||||
this(chain, api, new DefaultEthereumMethods(TestingCommons.objectMapper(), chain))
|
||||
}
|
||||
|
||||
EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull DirectEthereumApi api) {
|
||||
this(id, chain, api, new QuorumBasedMethods(TestingCommons.objectMapper(), chain))
|
||||
this(id, chain, api, new DefaultEthereumMethods(TestingCommons.objectMapper(), chain))
|
||||
}
|
||||
|
||||
EthereumUpstreamMock(@NotNull Chain chain, @NotNull DirectEthereumApi api, CallMethods methods) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ChainUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.infinitape.etherjar.rpc.JacksonRpcConverter
|
||||
@@ -75,7 +76,7 @@ class TestingCommons {
|
||||
}
|
||||
|
||||
static AggregatedUpstream aggregatedUpstream(EthereumUpstream up) {
|
||||
return new ChainUpstreams(Chain.ETHEREUM, [up], Caches.default(), objectMapper())
|
||||
return new EthereumChainUpstreams(Chain.ETHEREUM, [up], Caches.default(), objectMapper())
|
||||
}
|
||||
|
||||
static CachesFactory emptyCaches() {
|
||||
|
||||
@@ -18,16 +18,17 @@ package io.emeraldpay.dshackle.test
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ChainUpstreams
|
||||
import io.emeraldpay.dshackle.upstream.calls.QuorumBasedMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import reactor.core.publisher.Flux
|
||||
|
||||
class UpstreamsMock implements Upstreams {
|
||||
|
||||
private Map<Chain, QuorumBasedMethods> target = [:]
|
||||
private Map<Chain, DefaultEthereumMethods> target = [:]
|
||||
private Map<Chain, AggregatedUpstream> upstreams = [:]
|
||||
|
||||
UpstreamsMock(Chain chain, Upstream up) {
|
||||
@@ -40,7 +41,7 @@ class UpstreamsMock implements Upstreams {
|
||||
|
||||
AggregatedUpstream addUpstream(@NotNull Chain chain, @NotNull Upstream up) {
|
||||
if (!upstreams.containsKey(chain)) {
|
||||
upstreams[chain] = new ChainUpstreams(chain, [up], Caches.default(), TestingCommons.objectMapper())
|
||||
upstreams[chain] = new EthereumChainUpstreams(chain, [up], Caches.default(), TestingCommons.objectMapper())
|
||||
} else {
|
||||
upstreams[chain].addUpstream(up)
|
||||
}
|
||||
@@ -63,9 +64,9 @@ class UpstreamsMock implements Upstreams {
|
||||
}
|
||||
|
||||
@Override
|
||||
QuorumBasedMethods getDefaultMethods(@NotNull Chain chain) {
|
||||
DefaultEthereumMethods getDefaultMethods(@NotNull Chain chain) {
|
||||
if (target[chain] == null) {
|
||||
QuorumBasedMethods targets = new QuorumBasedMethods(TestingCommons.objectMapper(), chain)
|
||||
DefaultEthereumMethods targets = new DefaultEthereumMethods(TestingCommons.objectMapper(), chain)
|
||||
target[chain] = targets
|
||||
}
|
||||
return target[chain]
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -30,7 +31,7 @@ class AggregatedUpstreamSpec extends Specification {
|
||||
setup:
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||
def up2 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
||||
def aggr = new ChainUpstreams(Chain.ETHEREUM, [up1, up2], Caches.default(), TestingCommons.objectMapper())
|
||||
def aggr = new EthereumChainUpstreams(Chain.ETHEREUM, [up1, up2], Caches.default(), TestingCommons.objectMapper())
|
||||
when:
|
||||
aggr.onUpstreamsUpdated()
|
||||
def act = aggr.getMethods()
|
||||
|
||||
@@ -19,7 +19,7 @@ import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.test.EthereumApiStub
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.calls.QuorumBasedMethods
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWs
|
||||
@@ -35,7 +35,7 @@ class FilteredApisSpec extends Specification {
|
||||
|
||||
def rpcClient = Stub(ReactorRpcClient)
|
||||
def objectMapper = TestingCommons.objectMapper()
|
||||
def ethereumTargets = new QuorumBasedMethods(objectMapper, Chain.ETHEREUM)
|
||||
def ethereumTargets = new DefaultEthereumMethods(objectMapper, Chain.ETHEREUM)
|
||||
|
||||
def "Verifies labels"() {
|
||||
setup:
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.TopicProcessor
|
||||
@@ -25,7 +26,7 @@ import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class HeadLagObserverSpec extends Specification {
|
||||
class EthereumHeadLagObserverSpec extends Specification {
|
||||
|
||||
def "Updates lag distance"() {
|
||||
setup:
|
||||
@@ -64,7 +65,7 @@ class HeadLagObserverSpec extends Specification {
|
||||
1 * up2.setLag(1)
|
||||
1 * up2.setLag(0)
|
||||
|
||||
HeadLagObserver observer = new HeadLagObserver(master, [up1, up2])
|
||||
HeadLagObserver observer = new EthereumHeadLagObserver(master, [up1, up2])
|
||||
when:
|
||||
def act = observer.subscription().take(Duration.ofMillis(1200))
|
||||
|
||||
@@ -78,7 +79,7 @@ class HeadLagObserverSpec extends Specification {
|
||||
def "Probes until there is no difference"() {
|
||||
setup:
|
||||
EthereumHead master = Mock()
|
||||
HeadLagObserver observer = new HeadLagObserver(master, [])
|
||||
HeadLagObserver observer = new EthereumHeadLagObserver(master, [])
|
||||
Upstream up = Mock()
|
||||
|
||||
def blocks = [100, 101, 102].collect { i ->
|
||||
@@ -103,7 +104,7 @@ class HeadLagObserverSpec extends Specification {
|
||||
def "Correct distance"() {
|
||||
setup:
|
||||
EthereumHead master = Mock()
|
||||
HeadLagObserver observer = new HeadLagObserver(master, [])
|
||||
HeadLagObserver observer = new EthereumHeadLagObserver(master, [])
|
||||
expect:
|
||||
def top = new BlockJson().with {
|
||||
it.number = topHeight
|
||||
@@ -34,7 +34,7 @@ import spock.lang.Specification
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class GrpcUpstreamSpec extends Specification {
|
||||
class EthereumGrpcUpstreamSpec extends Specification {
|
||||
|
||||
MockServer mockServer = new MockServer()
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
@@ -70,7 +70,7 @@ class GrpcUpstreamSpec extends Specification {
|
||||
}
|
||||
})
|
||||
def transport = ReactorEmeraldClient.newBuilder().connectUsing(client.channel).build()
|
||||
def upstream = new GrpcUpstream("test", chain, client, objectMapper, transport)
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, objectMapper, transport)
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
@@ -126,7 +126,7 @@ class GrpcUpstreamSpec extends Specification {
|
||||
}
|
||||
})
|
||||
def transport = ReactorEmeraldClient.newBuilder().connectUsing(client.channel).build()
|
||||
def upstream = new GrpcUpstream("test", Chain.ETHEREUM, client, objectMapper, transport)
|
||||
def upstream = new EthereumGrpcUpstream("test", Chain.ETHEREUM, client, objectMapper, transport)
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
@@ -186,7 +186,7 @@ class GrpcUpstreamSpec extends Specification {
|
||||
}
|
||||
})
|
||||
def transport = ReactorEmeraldClient.newBuilder().connectUsing(client.channel).build()
|
||||
def upstream = new GrpcUpstream("test", chain, client, objectMapper, transport)
|
||||
def upstream = new EthereumGrpcUpstream("test", chain, client, objectMapper, transport)
|
||||
upstream.setLag(0)
|
||||
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
|
||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
||||
Reference in New Issue
Block a user