ethereum as generic upstream (#331)
- merge all Ethereum implementations to Generic upstreams and multistreams - merge evm-pos and pow to ethereum
This commit is contained in:
@@ -33,7 +33,10 @@ data class ChainsConfig(private val chains: List<ChainConfig>) : Iterable<Chains
|
|||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun default() = ChainConfig(
|
fun default() = defaultWithContract(null)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun defaultWithContract(callLimitContract: String?) = ChainConfig(
|
||||||
Duration.ofSeconds(12),
|
Duration.ofSeconds(12),
|
||||||
6,
|
6,
|
||||||
1,
|
1,
|
||||||
@@ -43,11 +46,14 @@ data class ChainsConfig(private val chains: List<ChainConfig>) : Iterable<Chains
|
|||||||
0,
|
0,
|
||||||
"UNKNOWN",
|
"UNKNOWN",
|
||||||
emptyList(),
|
emptyList(),
|
||||||
null,
|
callLimitContract,
|
||||||
"undefined",
|
"undefined",
|
||||||
"undefined",
|
"undefined",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resolve(chain: String): ChainConfig {
|
fun resolve(chain: String): ChainConfig {
|
||||||
|
|||||||
@@ -1,27 +1,21 @@
|
|||||||
package io.emeraldpay.dshackle
|
package io.emeraldpay.dshackle
|
||||||
|
|
||||||
enum class BlockchainType {
|
enum class BlockchainType {
|
||||||
BITCOIN, EVM_POW, EVM_POS, STARKNET;
|
BITCOIN, ETHEREUM, STARKNET;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val pow = setOf(
|
|
||||||
Chain.ETHEREUM_CLASSIC__MAINNET,
|
|
||||||
)
|
|
||||||
val bitcoin = setOf(Chain.BITCOIN__MAINNET, Chain.BITCOIN__TESTNET)
|
val bitcoin = setOf(Chain.BITCOIN__MAINNET, Chain.BITCOIN__TESTNET)
|
||||||
|
|
||||||
val starknet = setOf(Chain.STARKNET__MAINNET, Chain.STARKNET__TESTNET, Chain.STARKNET__TESTNET_2)
|
val starknet = setOf(Chain.STARKNET__MAINNET, Chain.STARKNET__TESTNET, Chain.STARKNET__TESTNET_2)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun from(chain: Chain): BlockchainType {
|
fun from(chain: Chain): BlockchainType {
|
||||||
return if (pow.contains(chain)) {
|
return if (bitcoin.contains(chain)) {
|
||||||
EVM_POW
|
|
||||||
} else if (bitcoin.contains(chain)) {
|
|
||||||
BITCOIN
|
BITCOIN
|
||||||
} else if (starknet.contains(chain)) {
|
} else if (starknet.contains(chain)) {
|
||||||
STARKNET
|
STARKNET
|
||||||
} else {
|
} else {
|
||||||
EVM_POS
|
ETHEREUM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ open class CachesFactory(
|
|||||||
caches.setReceipts(ReceiptRedisCache(redis.reactive(), chain))
|
caches.setReceipts(ReceiptRedisCache(redis.reactive(), chain))
|
||||||
caches.setHeightByHash(HeightByHashRedisCache(redis.reactive(), chain))
|
caches.setHeightByHash(HeightByHashRedisCache(redis.reactive(), chain))
|
||||||
}
|
}
|
||||||
caches.setCacheEnabled(cacheConfig.requestsCacheEnabled)
|
|
||||||
return caches.build()
|
return caches.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ package io.emeraldpay.dshackle.config
|
|||||||
|
|
||||||
class CacheConfig {
|
class CacheConfig {
|
||||||
|
|
||||||
var requestsCacheEnabled = true
|
|
||||||
|
|
||||||
var redis: Redis? = null
|
var redis: Redis? = null
|
||||||
|
|
||||||
class Redis(
|
class Redis(
|
||||||
|
|||||||
@@ -16,21 +16,13 @@
|
|||||||
package io.emeraldpay.dshackle.config
|
package io.emeraldpay.dshackle.config
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.foundation.YamlConfigReader
|
import io.emeraldpay.dshackle.foundation.YamlConfigReader
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.yaml.snakeyaml.nodes.MappingNode
|
import org.yaml.snakeyaml.nodes.MappingNode
|
||||||
|
|
||||||
class CacheConfigReader : YamlConfigReader<CacheConfig>() {
|
class CacheConfigReader : YamlConfigReader<CacheConfig>() {
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(CacheConfigReader::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(input: MappingNode?): CacheConfig? {
|
override fun read(input: MappingNode?): CacheConfig? {
|
||||||
return getMapping(input, "cache")?.let { node ->
|
return getMapping(input, "cache")?.let { node ->
|
||||||
val config = CacheConfig()
|
val config = CacheConfig()
|
||||||
getValueAsBool(node, "requests-cache-enabled")?.let {
|
|
||||||
config.requestsCacheEnabled = it
|
|
||||||
}
|
|
||||||
getMapping(node, "redis")?.let { redisNode ->
|
getMapping(node, "redis")?.let { redisNode ->
|
||||||
val redis = CacheConfig.Redis()
|
val redis = CacheConfig.Redis()
|
||||||
val enabled = getValueAsBool(redisNode, "enabled") ?: true
|
val enabled = getValueAsBool(redisNode, "enabled") ?: true
|
||||||
@@ -50,7 +42,7 @@ class CacheConfigReader : YamlConfigReader<CacheConfig>() {
|
|||||||
config.redis = redis
|
config.redis = redis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.redis == null && config.requestsCacheEnabled) {
|
if (config.redis == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
config
|
config
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class TokensConfig(
|
|||||||
type == null -> type
|
type == null -> type
|
||||||
address.isNullOrBlank() -> "address"
|
address.isNullOrBlank() -> "address"
|
||||||
blockchain != null &&
|
blockchain != null &&
|
||||||
(BlockchainType.from(blockchain!!) == BlockchainType.EVM_POS || BlockchainType.from(blockchain!!) == BlockchainType.EVM_POW) &&
|
(BlockchainType.from(blockchain!!) == BlockchainType.ETHEREUM) &&
|
||||||
!Address.isValidAddress(address) -> "address"
|
!Address.isValidAddress(address) -> "address"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,12 @@ package io.emeraldpay.dshackle.config.context
|
|||||||
|
|
||||||
import io.emeraldpay.dshackle.BlockchainType
|
import io.emeraldpay.dshackle.BlockchainType
|
||||||
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
|
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
|
||||||
import io.emeraldpay.dshackle.BlockchainType.EVM_POS
|
|
||||||
import io.emeraldpay.dshackle.BlockchainType.EVM_POW
|
|
||||||
import io.emeraldpay.dshackle.BlockchainType.STARKNET
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.cache.CachesFactory
|
import io.emeraldpay.dshackle.cache.CachesFactory
|
||||||
import io.emeraldpay.dshackle.upstream.CallTargetsHolder
|
import io.emeraldpay.dshackle.upstream.CallTargetsHolder
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
import io.emeraldpay.dshackle.upstream.generic.ChainSpecificRegistry
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
|
||||||
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
|
||||||
@@ -31,14 +27,13 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory)
|
|||||||
headScheduler: Scheduler,
|
headScheduler: Scheduler,
|
||||||
tracer: Tracer,
|
tracer: Tracer,
|
||||||
): List<Multistream> {
|
): List<Multistream> {
|
||||||
return Chain.values()
|
return Chain.entries
|
||||||
.filterNot { it == Chain.UNSPECIFIED }
|
.filterNot { it == Chain.UNSPECIFIED }
|
||||||
.map { chain ->
|
.map { chain ->
|
||||||
when (BlockchainType.from(chain)) {
|
if (BlockchainType.from(chain) == BITCOIN) {
|
||||||
EVM_POS -> ethereumPosMultistream(chain, cachesFactory, headScheduler, tracer)
|
bitcoinMultistream(chain, cachesFactory, headScheduler)
|
||||||
EVM_POW -> ethereumMultistream(chain, cachesFactory, headScheduler, tracer)
|
} else {
|
||||||
BITCOIN -> bitcoinMultistream(chain, cachesFactory, headScheduler)
|
genericMultistream(chain, cachesFactory, headScheduler, tracer)
|
||||||
STARKNET -> genericMultistream(chain, cachesFactory, headScheduler)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,47 +42,18 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory)
|
|||||||
chain: Chain,
|
chain: Chain,
|
||||||
cachesFactory: CachesFactory,
|
cachesFactory: CachesFactory,
|
||||||
headScheduler: Scheduler,
|
headScheduler: Scheduler,
|
||||||
|
tracer: Tracer,
|
||||||
): Multistream {
|
): Multistream {
|
||||||
val name = "multi-$chain"
|
val name = "multi-$chain"
|
||||||
|
val cs = ChainSpecificRegistry.resolve(chain)
|
||||||
return GenericMultistream(
|
return GenericMultistream(
|
||||||
chain,
|
chain,
|
||||||
CopyOnWriteArrayList(),
|
CopyOnWriteArrayList(),
|
||||||
cachesFactory.getCaches(chain),
|
cachesFactory.getCaches(chain),
|
||||||
headScheduler,
|
headScheduler,
|
||||||
).also { register(it, name) }
|
cs.makeCachingReaderBuilder(tracer),
|
||||||
}
|
cs::localReaderBuilder,
|
||||||
|
cs.subscriptionBuilder(headScheduler),
|
||||||
private fun ethereumMultistream(
|
|
||||||
chain: Chain,
|
|
||||||
cachesFactory: CachesFactory,
|
|
||||||
headScheduler: Scheduler,
|
|
||||||
tracer: Tracer,
|
|
||||||
): EthereumMultistream {
|
|
||||||
val name = "multi-ethereum-$chain"
|
|
||||||
|
|
||||||
return EthereumMultistream(
|
|
||||||
chain,
|
|
||||||
CopyOnWriteArrayList(),
|
|
||||||
cachesFactory.getCaches(chain),
|
|
||||||
headScheduler,
|
|
||||||
tracer,
|
|
||||||
).also { register(it, name) }
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun ethereumPosMultistream(
|
|
||||||
chain: Chain,
|
|
||||||
cachesFactory: CachesFactory,
|
|
||||||
headScheduler: Scheduler,
|
|
||||||
tracer: Tracer,
|
|
||||||
): EthereumPosMultiStream {
|
|
||||||
val name = "multi-ethereum-pos-$chain"
|
|
||||||
|
|
||||||
return EthereumPosMultiStream(
|
|
||||||
chain,
|
|
||||||
CopyOnWriteArrayList(),
|
|
||||||
cachesFactory.getCaches(chain),
|
|
||||||
headScheduler,
|
|
||||||
tracer,
|
|
||||||
).also { register(it, name) }
|
).also { register(it, name) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||||
import io.emeraldpay.dshackle.BlockchainType
|
import io.emeraldpay.dshackle.BlockchainType
|
||||||
import io.emeraldpay.dshackle.BlockchainType.EVM_POS
|
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.Global.Companion.nullValue
|
import io.emeraldpay.dshackle.Global.Companion.nullValue
|
||||||
@@ -76,7 +76,6 @@ open class NativeCall(
|
|||||||
private val log = LoggerFactory.getLogger(NativeCall::class.java)
|
private val log = LoggerFactory.getLogger(NativeCall::class.java)
|
||||||
private val objectMapper: ObjectMapper = Global.objectMapper
|
private val objectMapper: ObjectMapper = Global.objectMapper
|
||||||
|
|
||||||
private val localRouterEnabled = config.cache?.requestsCacheEnabled ?: true
|
|
||||||
private val passthrough = config.passthrough
|
private val passthrough = config.passthrough
|
||||||
|
|
||||||
var rpcReaderFactory: RpcReaderFactory = RpcReaderFactory.default()
|
var rpcReaderFactory: RpcReaderFactory = RpcReaderFactory.default()
|
||||||
@@ -85,7 +84,7 @@ open class NativeCall(
|
|||||||
@EventListener
|
@EventListener
|
||||||
fun onUpstreamChangeEvent(event: UpstreamChangeEvent) {
|
fun onUpstreamChangeEvent(event: UpstreamChangeEvent) {
|
||||||
multistreamHolder.getUpstream(event.chain).let { up ->
|
multistreamHolder.getUpstream(event.chain).let { up ->
|
||||||
if (BlockchainType.from(up.chain) == EVM_POS) {
|
if (BlockchainType.from(up.chain) == ETHEREUM) {
|
||||||
ethereumCallSelectors.putIfAbsent(
|
ethereumCallSelectors.putIfAbsent(
|
||||||
event.chain,
|
event.chain,
|
||||||
EthereumCallSelector(up.caches),
|
EthereumCallSelector(up.caches),
|
||||||
@@ -307,7 +306,7 @@ open class NativeCall(
|
|||||||
}
|
}
|
||||||
// for ethereum the actual block needed for the call may be specified in the call parameters
|
// for ethereum the actual block needed for the call may be specified in the call parameters
|
||||||
val callSpecificMatcher: Mono<Selector.Matcher> =
|
val callSpecificMatcher: Mono<Selector.Matcher> =
|
||||||
if (BlockchainType.from(upstream.chain) == BlockchainType.EVM_POS || BlockchainType.from(upstream.chain) == BlockchainType.EVM_POW) {
|
if (BlockchainType.from(upstream.chain) == ETHEREUM) {
|
||||||
ethereumCallSelectors[chain]?.getMatcher(method, params, upstream.getHead(), passthrough)
|
ethereumCallSelectors[chain]?.getMatcher(method, params, upstream.getHead(), passthrough)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@@ -359,7 +358,7 @@ open class NativeCall(
|
|||||||
if (method in DefaultEthereumMethods.newFilterMethods) CreateFilterDecorator() else NoneResultDecorator()
|
if (method in DefaultEthereumMethods.newFilterMethods) CreateFilterDecorator() else NoneResultDecorator()
|
||||||
|
|
||||||
fun fetch(ctx: ValidCallContext<ParsedCallDetails>): Mono<CallResult> {
|
fun fetch(ctx: ValidCallContext<ParsedCallDetails>): Mono<CallResult> {
|
||||||
return ctx.upstream.getLocalReader(localRouterEnabled)
|
return ctx.upstream.getLocalReader()
|
||||||
.flatMap { api ->
|
.flatMap { api ->
|
||||||
SpannedReader(api, tracer, LOCAL_READER)
|
SpannedReader(api, tracer, LOCAL_READER)
|
||||||
.read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce, ctx.forwardedSelector))
|
.read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce, ctx.forwardedSelector))
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ open class NativeSubscribe(
|
|||||||
|
|
||||||
fun start(request: BlockchainOuterClass.NativeSubscribeRequest): Publisher<ResponseHolder> {
|
fun start(request: BlockchainOuterClass.NativeSubscribeRequest): Publisher<ResponseHolder> {
|
||||||
val chain = Chain.byId(request.chainValue)
|
val chain = Chain.byId(request.chainValue)
|
||||||
if (BlockchainType.from(chain) != BlockchainType.EVM_POS && BlockchainType.from(chain) != BlockchainType.EVM_POW) {
|
if (BlockchainType.from(chain) != BlockchainType.ETHEREUM) {
|
||||||
return Mono.error(UnsupportedOperationException("Native subscribe is not supported for ${chain.chainCode}"))
|
return Mono.error(UnsupportedOperationException("Native subscribe is not supported for ${chain.chainCode}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import brave.grpc.GrpcTracing
|
|||||||
import com.google.common.annotations.VisibleForTesting
|
import com.google.common.annotations.VisibleForTesting
|
||||||
import io.emeraldpay.dshackle.BlockchainType
|
import io.emeraldpay.dshackle.BlockchainType
|
||||||
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
|
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
|
||||||
import io.emeraldpay.dshackle.BlockchainType.EVM_POS
|
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
|
||||||
import io.emeraldpay.dshackle.BlockchainType.EVM_POW
|
|
||||||
import io.emeraldpay.dshackle.BlockchainType.STARKNET
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Defaults
|
import io.emeraldpay.dshackle.Defaults
|
||||||
import io.emeraldpay.dshackle.FileResolver
|
import io.emeraldpay.dshackle.FileResolver
|
||||||
@@ -52,13 +50,12 @@ import io.emeraldpay.dshackle.upstream.bitcoin.ExtractBlock
|
|||||||
import io.emeraldpay.dshackle.upstream.bitcoin.ZMQServer
|
import io.emeraldpay.dshackle.upstream.bitcoin.ZMQServer
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods
|
import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumBlockValidator
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionFactory
|
import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionFactory
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionPoolFactory
|
import io.emeraldpay.dshackle.upstream.ethereum.WsConnectionPoolFactory
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice
|
import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
|
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.NoChoiceWithPriorityForkChoice
|
import io.emeraldpay.dshackle.upstream.forkchoice.NoChoiceWithPriorityForkChoice
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.ChainSpecificRegistry
|
||||||
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory
|
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory.ConnectorMode.RPC_REQUESTS_WITH_MIXED_HEAD
|
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory.ConnectorMode.RPC_REQUESTS_WITH_MIXED_HEAD
|
||||||
@@ -75,6 +72,7 @@ import org.springframework.context.ApplicationEventPublisher
|
|||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import reactor.core.scheduler.Scheduler
|
import reactor.core.scheduler.Scheduler
|
||||||
import reactor.core.scheduler.Schedulers
|
import reactor.core.scheduler.Schedulers
|
||||||
|
import java.lang.IllegalStateException
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@@ -138,24 +136,6 @@ open class ConfiguredUpstreams(
|
|||||||
.merge(up.options ?: ChainOptions.PartialOptions())
|
.merge(up.options ?: ChainOptions.PartialOptions())
|
||||||
.buildOptions()
|
.buildOptions()
|
||||||
val upstream = when (BlockchainType.from(chain)) {
|
val upstream = when (BlockchainType.from(chain)) {
|
||||||
EVM_POS -> {
|
|
||||||
buildEthereumPosUpstream(
|
|
||||||
up.nodeId,
|
|
||||||
up.cast(EthereumPosConnection::class.java),
|
|
||||||
chain,
|
|
||||||
options,
|
|
||||||
chainConfig,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
EVM_POW -> {
|
|
||||||
buildEthereumUpstream(
|
|
||||||
up.nodeId,
|
|
||||||
up.cast(RpcConnection::class.java),
|
|
||||||
chain,
|
|
||||||
options,
|
|
||||||
chainConfig,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
BITCOIN -> {
|
BITCOIN -> {
|
||||||
buildBitcoinUpstream(
|
buildBitcoinUpstream(
|
||||||
up.cast(BitcoinConnection::class.java),
|
up.cast(BitcoinConnection::class.java),
|
||||||
@@ -165,13 +145,29 @@ open class ConfiguredUpstreams(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
STARKNET -> {
|
ETHEREUM -> {
|
||||||
buildStarknetUpstream(
|
val posConn = up.cast(EthereumPosConnection::class.java)
|
||||||
|
|
||||||
|
buildGenericUpstream(
|
||||||
up.nodeId,
|
up.nodeId,
|
||||||
up.cast(RpcConnection::class.java),
|
up,
|
||||||
|
posConn.connection?.execution ?: throw IllegalStateException("Empty execution config"),
|
||||||
chain,
|
chain,
|
||||||
options,
|
options,
|
||||||
chainConfig,
|
chainConfig,
|
||||||
|
posConn.connection?.upstreamRating ?: 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
buildGenericUpstream(
|
||||||
|
up.nodeId,
|
||||||
|
up,
|
||||||
|
up.connection as RpcConnection,
|
||||||
|
chain,
|
||||||
|
options,
|
||||||
|
chainConfig,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,31 +220,30 @@ open class ConfiguredUpstreams(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildStarknetUpstream(
|
private fun buildGenericUpstream(
|
||||||
nodeId: Int?,
|
nodeId: Int?,
|
||||||
config: UpstreamsConfig.Upstream<RpcConnection>,
|
config: UpstreamsConfig.Upstream<*>,
|
||||||
|
connection: RpcConnection,
|
||||||
chain: Chain,
|
chain: Chain,
|
||||||
options: Options,
|
options: Options,
|
||||||
chainConfig: ChainConfig,
|
chainConfig: ChainConfig,
|
||||||
|
nodeRating: Int,
|
||||||
): Upstream? {
|
): Upstream? {
|
||||||
if (config.connection == null) {
|
if (config.connection == null) {
|
||||||
log.warn("Upstream doesn't have connection configuration")
|
log.warn("Upstream doesn't have connection configuration")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val connection = config.connection!!
|
val cs = ChainSpecificRegistry.resolve(chain)
|
||||||
|
|
||||||
val connectorFactory = buildConnectorFactory(
|
val connectorFactory = buildConnectorFactory(
|
||||||
config.id!!,
|
config.id!!,
|
||||||
connection,
|
connection,
|
||||||
chain,
|
chain,
|
||||||
NoChoiceWithPriorityForkChoice(0, config.id!!),
|
NoChoiceWithPriorityForkChoice(nodeRating, config.id!!),
|
||||||
BlockValidator.ALWAYS_VALID,
|
BlockValidator.ALWAYS_VALID,
|
||||||
chainConfig,
|
chainConfig,
|
||||||
)
|
) ?: return null
|
||||||
if (connectorFactory == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val methods = buildMethods(config, chain)
|
val methods = buildMethods(config, chain)
|
||||||
|
|
||||||
@@ -268,6 +263,9 @@ open class ConfiguredUpstreams(
|
|||||||
chainConfig,
|
chainConfig,
|
||||||
connectorFactory,
|
connectorFactory,
|
||||||
eventPublisher,
|
eventPublisher,
|
||||||
|
cs::validator,
|
||||||
|
cs::labelDetector,
|
||||||
|
cs::subscriptionTopics,
|
||||||
)
|
)
|
||||||
|
|
||||||
upstream.start()
|
upstream.start()
|
||||||
@@ -278,58 +276,6 @@ open class ConfiguredUpstreams(
|
|||||||
return upstream
|
return upstream
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildEthereumPosUpstream(
|
|
||||||
nodeId: Int?,
|
|
||||||
config: UpstreamsConfig.Upstream<EthereumPosConnection>,
|
|
||||||
chain: Chain,
|
|
||||||
options: Options,
|
|
||||||
chainConf: ChainConfig,
|
|
||||||
): Upstream? {
|
|
||||||
val conn = config.connection!!
|
|
||||||
val execution = conn.execution
|
|
||||||
if (execution == null) {
|
|
||||||
log.warn("Upstream doesn't have execution layer configuration")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val urls = ArrayList<URI>()
|
|
||||||
val connectorFactory = buildConnectorFactory(
|
|
||||||
config.id!!,
|
|
||||||
execution,
|
|
||||||
chain,
|
|
||||||
NoChoiceWithPriorityForkChoice(conn.upstreamRating, config.id!!),
|
|
||||||
BlockValidator.ALWAYS_VALID,
|
|
||||||
chainConf,
|
|
||||||
)
|
|
||||||
val methods = buildMethods(config, chain)
|
|
||||||
if (connectorFactory == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val hashUrl = conn.execution!!.let {
|
|
||||||
if (it.connectorMode == RPC_REQUESTS_WITH_MIXED_HEAD.name) it.rpc?.url ?: it.ws?.url else it.ws?.url ?: it.rpc?.url
|
|
||||||
}
|
|
||||||
val hash = getHash(nodeId, hashUrl!!)
|
|
||||||
val upstream = EthereumLikeRpcUpstream(
|
|
||||||
config.id!!,
|
|
||||||
hash,
|
|
||||||
chain,
|
|
||||||
options,
|
|
||||||
config.role,
|
|
||||||
methods,
|
|
||||||
QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(config.labels)),
|
|
||||||
connectorFactory,
|
|
||||||
chainConf,
|
|
||||||
true,
|
|
||||||
eventPublisher,
|
|
||||||
)
|
|
||||||
upstream.start()
|
|
||||||
if (!upstream.isRunning) {
|
|
||||||
log.debug("Upstream ${upstream.getId()} is not running, it can't be added")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return upstream
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildBitcoinUpstream(
|
private fun buildBitcoinUpstream(
|
||||||
config: UpstreamsConfig.Upstream<BitcoinConnection>,
|
config: UpstreamsConfig.Upstream<BitcoinConnection>,
|
||||||
chain: Chain,
|
chain: Chain,
|
||||||
@@ -373,45 +319,6 @@ open class ConfiguredUpstreams(
|
|||||||
return upstream
|
return upstream
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildEthereumUpstream(
|
|
||||||
nodeId: Int?,
|
|
||||||
config: UpstreamsConfig.Upstream<RpcConnection>,
|
|
||||||
chain: Chain,
|
|
||||||
options: Options,
|
|
||||||
chainConf: ChainConfig,
|
|
||||||
): Upstream? {
|
|
||||||
val conn = config.connection!!
|
|
||||||
val methods = buildMethods(config, chain)
|
|
||||||
|
|
||||||
val connectorFactory = buildConnectorFactory(
|
|
||||||
config.id!!,
|
|
||||||
conn,
|
|
||||||
chain,
|
|
||||||
MostWorkForkChoice(),
|
|
||||||
EthereumBlockValidator(),
|
|
||||||
chainConf,
|
|
||||||
)
|
|
||||||
if (connectorFactory == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val hashUrl = if (conn.connectorMode == RPC_REQUESTS_WITH_MIXED_HEAD.name) conn.rpc?.url ?: conn.ws?.url else conn.ws?.url ?: conn.rpc?.url
|
|
||||||
val upstream = EthereumLikeRpcUpstream(
|
|
||||||
config.id!!,
|
|
||||||
getHash(nodeId, hashUrl!!),
|
|
||||||
chain,
|
|
||||||
options, config.role,
|
|
||||||
methods,
|
|
||||||
QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(config.labels)),
|
|
||||||
connectorFactory,
|
|
||||||
chainConf,
|
|
||||||
false,
|
|
||||||
eventPublisher,
|
|
||||||
)
|
|
||||||
upstream.start()
|
|
||||||
return upstream
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildGrpcUpstream(
|
private fun buildGrpcUpstream(
|
||||||
nodeId: Int?,
|
nodeId: Int?,
|
||||||
config: UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>,
|
config: UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>,
|
||||||
@@ -460,13 +367,13 @@ open class ConfiguredUpstreams(
|
|||||||
|
|
||||||
private fun buildHttpFactory(conn: HttpEndpoint?, urls: ArrayList<URI>? = null): HttpRpcFactory? {
|
private fun buildHttpFactory(conn: HttpEndpoint?, urls: ArrayList<URI>? = null): HttpRpcFactory? {
|
||||||
return conn?.let { endpoint ->
|
return conn?.let { endpoint ->
|
||||||
val tls = conn?.tls?.let { tls ->
|
val tls = conn.tls?.let { tls ->
|
||||||
tls.ca?.let { ca ->
|
tls.ca?.let { ca ->
|
||||||
fileResolver.resolve(ca).readBytes()
|
fileResolver.resolve(ca).readBytes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
urls?.add(endpoint.url)
|
urls?.add(endpoint.url)
|
||||||
HttpRpcFactory(endpoint.url.toString(), conn?.basicAuth, tls)
|
HttpRpcFactory(endpoint.url.toString(), conn.basicAuth, tls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
package io.emeraldpay.dshackle.upstream
|
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.EnumMap
|
|
||||||
import java.util.function.Function
|
|
||||||
|
|
||||||
abstract class AbstractChainFees<F, B, TR, T>(
|
|
||||||
private val heightLimit: Int,
|
|
||||||
private val upstreams: Multistream,
|
|
||||||
extractTx: (B) -> List<TR>?,
|
|
||||||
) : ChainFees {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(AbstractChainFees::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val txSource = EnumMap<ChainFees.Mode, TxAt<B, TR>>(ChainFees.Mode::class.java)
|
|
||||||
|
|
||||||
init {
|
|
||||||
txSource[ChainFees.Mode.AVG_TOP] = TxAtTop(extractTx)
|
|
||||||
txSource[ChainFees.Mode.MIN_ALWAYS] = TxAtBottom(extractTx)
|
|
||||||
txSource[ChainFees.Mode.AVG_MIDDLE] = TxAtMiddle(extractTx)
|
|
||||||
txSource[ChainFees.Mode.AVG_LAST] = TxAtBottom(extractTx)
|
|
||||||
txSource[ChainFees.Mode.AVG_T5] = TxAtPos(extractTx, 5)
|
|
||||||
txSource[ChainFees.Mode.AVG_T20] = TxAtPos(extractTx, 20)
|
|
||||||
txSource[ChainFees.Mode.AVG_T50] = TxAtPos(extractTx, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun estimate(mode: ChainFees.Mode, blocks: Int): Mono<BlockchainOuterClass.EstimateFeeResponse> {
|
|
||||||
return usingBlocks(blocks)
|
|
||||||
.flatMap { readFeesAt(it, mode) }
|
|
||||||
.transform(feeAggregation(mode))
|
|
||||||
.next()
|
|
||||||
.map(getResponseBuilder())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---
|
|
||||||
|
|
||||||
private val feeCache = Caffeine.newBuilder()
|
|
||||||
.expireAfterWrite(Duration.ofMinutes(60))
|
|
||||||
.build<Pair<Long, ChainFees.Mode>, F>()
|
|
||||||
|
|
||||||
fun usingBlocks(exp: Int): Flux<Long> {
|
|
||||||
val useBlocks = exp.coerceAtMost(heightLimit).coerceAtLeast(1)
|
|
||||||
|
|
||||||
val height = upstreams.getHead().getCurrentHeight()
|
|
||||||
?: return Mono.fromCallable { log.warn("Upstream is not ready. No current height") }.thenMany(Mono.empty()) // TODO or throw an exception to build a gRPC error?
|
|
||||||
val startBlock: Int = height.toInt() - useBlocks + 1
|
|
||||||
if (startBlock < 0) {
|
|
||||||
log.warn("Blockchain doesn't have enough blocks. Height: $height")
|
|
||||||
return Flux.empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
return Flux.range(startBlock, useBlocks).map { it.toLong() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readFeesAt(height: Long, mode: ChainFees.Mode): Mono<F> {
|
|
||||||
val current = feeCache.getIfPresent(Pair(height, mode))
|
|
||||||
if (current != null) {
|
|
||||||
return Mono.just(current)
|
|
||||||
}
|
|
||||||
val txSelector = txSourceFor(mode)
|
|
||||||
return readFeesAt(height, txSelector).doOnNext {
|
|
||||||
// TODO it may be EMPTY for some blocks (ex. a no tx block), so nothing gets cached and goes to do the same call each time. so do cache empty values to avoid useless requests
|
|
||||||
feeCache.put(Pair(height, mode), it!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun txSourceFor(mode: ChainFees.Mode): TxAt<B, TR> {
|
|
||||||
return txSource[mode] ?: throw IllegalStateException("No TS Source for mode $mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun readFeesAt(height: Long, selector: TxAt<B, TR>): Mono<F>
|
|
||||||
abstract fun feeAggregation(mode: ChainFees.Mode): Function<Flux<F>, Mono<F>>
|
|
||||||
abstract fun getResponseBuilder(): Function<F, BlockchainOuterClass.EstimateFeeResponse>
|
|
||||||
|
|
||||||
abstract class TxAt<B, TR>(private val extractTx: Function<B, List<TR>?>) {
|
|
||||||
fun get(block: B): TR? {
|
|
||||||
val txes = extractTx.apply(block) ?: return null
|
|
||||||
return get(txes)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun get(transactions: List<TR>): TR?
|
|
||||||
}
|
|
||||||
|
|
||||||
class TxAtPos<B, TR>(extractTx: Function<B, List<TR>?>, private val pos: Int) : TxAt<B, TR>(extractTx) {
|
|
||||||
|
|
||||||
override fun get(transactions: List<TR>): TR? {
|
|
||||||
val index = pos.coerceAtMost(transactions.size - 1)
|
|
||||||
if (index < 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return transactions[transactions.size - index - 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TxAtTop<B, TR>(extractTx: Function<B, List<TR>?>) : TxAt<B, TR>(extractTx) {
|
|
||||||
override fun get(transactions: List<TR>): TR? {
|
|
||||||
if (transactions.isEmpty()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return transactions[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TxAtBottom<B, TR>(extractTx: Function<B, List<TR>?>) : TxAt<B, TR>(extractTx) {
|
|
||||||
override fun get(transactions: List<TR>): TR? {
|
|
||||||
if (transactions.isEmpty()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return transactions.last()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TxAtMiddle<B, TR>(extractTx: Function<B, List<TR>?>) : TxAt<B, TR>(extractTx) {
|
|
||||||
override fun get(transactions: List<TR>): TR? {
|
|
||||||
if (transactions.isEmpty()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (transactions.size == 1) {
|
|
||||||
return transactions[0]
|
|
||||||
}
|
|
||||||
return transactions[transactions.size / 2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
package io.emeraldpay.dshackle.upstream
|
package io.emeraldpay.dshackle.upstream
|
||||||
|
|
||||||
interface CachingReader
|
interface CachingReader : Lifecycle
|
||||||
|
|
||||||
|
object NoopCachingReader : CachingReader {
|
||||||
|
override fun start() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRunning(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ class CallTargetsHolder {
|
|||||||
|
|
||||||
private fun setupDefaultMethods(chain: Chain): CallMethods {
|
private fun setupDefaultMethods(chain: Chain): CallMethods {
|
||||||
val created = when (BlockchainType.from(chain)) {
|
val created = when (BlockchainType.from(chain)) {
|
||||||
BlockchainType.EVM_POW -> DefaultEthereumMethods(chain)
|
|
||||||
BlockchainType.BITCOIN -> DefaultBitcoinMethods()
|
BlockchainType.BITCOIN -> DefaultBitcoinMethods()
|
||||||
BlockchainType.EVM_POS -> DefaultEthereumMethods(chain)
|
BlockchainType.ETHEREUM -> DefaultEthereumMethods(chain)
|
||||||
BlockchainType.STARKNET -> DefaultStarknetMethods(chain)
|
BlockchainType.STARKNET -> DefaultStarknetMethods(chain)
|
||||||
}
|
}
|
||||||
callTargets[chain] = created
|
callTargets[chain] = created
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package io.emeraldpay.dshackle.upstream
|
|
||||||
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
|
|
||||||
interface ChainFees {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun extractMode(req: BlockchainOuterClass.EstimateFeeRequest): Mode? {
|
|
||||||
return when (req.mode!!) {
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.INVALID -> null
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.AVG_LAST -> Mode.AVG_LAST
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.AVG_T5 -> Mode.AVG_T5
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.AVG_T20 -> Mode.AVG_T20
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.AVG_T50 -> Mode.AVG_T50
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.MIN_ALWAYS -> Mode.MIN_ALWAYS
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.AVG_MIDDLE -> Mode.AVG_MIDDLE
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.AVG_TOP -> Mode.AVG_TOP
|
|
||||||
BlockchainOuterClass.FeeEstimationMode.UNRECOGNIZED -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun estimate(mode: Mode, blocks: Int): Mono<BlockchainOuterClass.EstimateFeeResponse>
|
|
||||||
|
|
||||||
enum class Mode {
|
|
||||||
AVG_LAST,
|
|
||||||
AVG_T5,
|
|
||||||
AVG_T20,
|
|
||||||
AVG_T50,
|
|
||||||
MIN_ALWAYS,
|
|
||||||
AVG_MIDDLE,
|
|
||||||
AVG_TOP,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@ interface EgressSubscription {
|
|||||||
fun subscribe(topic: String, params: Any?, matcher: Selector.Matcher): Flux<out Any>
|
fun subscribe(topic: String, params: Any?, matcher: Selector.Matcher): Flux<out Any>
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmptyEgressSubscription : EgressSubscription {
|
object EmptyEgressSubscription : EgressSubscription {
|
||||||
override fun getAvailableTopics(): List<String> {
|
override fun getAvailableTopics(): List<String> {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
|
||||||
|
typealias LabelsDetectorBuilder = (Chain, JsonRpcReader) -> LabelsDetector?
|
||||||
|
interface LabelsDetector {
|
||||||
|
fun detectLabels(): Flux<Pair<String, String>>
|
||||||
|
}
|
||||||
@@ -51,10 +51,12 @@ import kotlin.concurrent.withLock
|
|||||||
*/
|
*/
|
||||||
abstract class Multistream(
|
abstract class Multistream(
|
||||||
val chain: Chain,
|
val chain: Chain,
|
||||||
private val upstreams: MutableList<Upstream>,
|
|
||||||
val caches: Caches,
|
val caches: Caches,
|
||||||
) : Upstream, Lifecycle, HasEgressSubscription {
|
) : Upstream, Lifecycle, HasEgressSubscription {
|
||||||
|
|
||||||
|
abstract fun getUpstreams(): MutableList<out Upstream>
|
||||||
|
abstract fun addUpstreamInternal(u: Upstream)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val metrics = "upstreams"
|
private const val metrics = "upstreams"
|
||||||
}
|
}
|
||||||
@@ -93,30 +95,6 @@ abstract class Multistream(
|
|||||||
.multicast()
|
.multicast()
|
||||||
.directBestEffort<Upstream>()
|
.directBestEffort<Upstream>()
|
||||||
|
|
||||||
init {
|
|
||||||
UpstreamAvailability.values().forEach { status ->
|
|
||||||
Metrics.gauge(
|
|
||||||
"$metrics.availability",
|
|
||||||
listOf(Tag.of("chain", chain.chainCode), Tag.of("status", status.name.lowercase())),
|
|
||||||
this,
|
|
||||||
) {
|
|
||||||
upstreams.count { it.getStatus() == status }.toDouble()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Metrics.gauge(
|
|
||||||
"$metrics.connected",
|
|
||||||
listOf(Tag.of("chain", chain.chainCode)),
|
|
||||||
this,
|
|
||||||
) {
|
|
||||||
upstreams.size.toDouble()
|
|
||||||
}
|
|
||||||
|
|
||||||
upstreams.forEach { up ->
|
|
||||||
monitorUpstream(up)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSubscriptionTopics(): List<String> {
|
override fun getSubscriptionTopics(): List<String> {
|
||||||
return getEgressSubscription().getAvailableTopics()
|
return getEgressSubscription().getAvailableTopics()
|
||||||
}
|
}
|
||||||
@@ -153,29 +131,49 @@ abstract class Multistream(
|
|||||||
onUpstreamsUpdated()
|
onUpstreamsUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
UpstreamAvailability.entries.forEach { status ->
|
||||||
|
Metrics.gauge(
|
||||||
|
"$metrics.availability",
|
||||||
|
listOf(Tag.of("chain", chain.chainCode), Tag.of("status", status.name.lowercase())),
|
||||||
|
this,
|
||||||
|
) {
|
||||||
|
getAll().count { it.getStatus() == status }.toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Metrics.gauge(
|
||||||
|
"$metrics.connected",
|
||||||
|
listOf(Tag.of("chain", chain.chainCode)),
|
||||||
|
this,
|
||||||
|
) {
|
||||||
|
getAll().size.toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of all underlying upstreams
|
* Get list of all underlying upstreams
|
||||||
*/
|
*/
|
||||||
open fun getAll(): List<Upstream> {
|
open fun getAll(): List<Upstream> {
|
||||||
return upstreams
|
return getUpstreams()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an upstream
|
* Add an upstream
|
||||||
*/
|
*/
|
||||||
fun addUpstream(upstream: Upstream): Boolean =
|
fun addUpstream(upstream: Upstream): Boolean =
|
||||||
upstreams.none {
|
getUpstreams().none {
|
||||||
it.getId() == upstream.getId()
|
it.getId() == upstream.getId()
|
||||||
}.also {
|
}.also {
|
||||||
if (it) {
|
if (it) {
|
||||||
upstreams.add(upstream)
|
addUpstreamInternal(upstream)
|
||||||
addHead(upstream)
|
addHead(upstream)
|
||||||
monitorUpstream(upstream)
|
monitorUpstream(upstream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeUpstream(id: String): Boolean =
|
fun removeUpstream(id: String): Boolean =
|
||||||
upstreams.removeIf { up ->
|
getUpstreams().removeIf { up ->
|
||||||
(up.getId() == id).also {
|
(up.getId() == id).also {
|
||||||
if (it) {
|
if (it) {
|
||||||
up.stop()
|
up.stop()
|
||||||
@@ -196,13 +194,13 @@ abstract class Multistream(
|
|||||||
if (seq >= Int.MAX_VALUE / 2) {
|
if (seq >= Int.MAX_VALUE / 2) {
|
||||||
seq = 0
|
seq = 0
|
||||||
}
|
}
|
||||||
return FilteredApis(chain, upstreams, matcher, i)
|
return FilteredApis(chain, getUpstreams(), matcher, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds an API that leverages caches and other optimizations/transformations of the request.
|
* Finds an API that leverages caches and other optimizations/transformations of the request.
|
||||||
*/
|
*/
|
||||||
abstract fun getLocalReader(localEnabled: Boolean): Mono<JsonRpcReader>
|
abstract fun getLocalReader(): Mono<JsonRpcReader>
|
||||||
|
|
||||||
override fun getIngressReader(): JsonRpcReader {
|
override fun getIngressReader(): JsonRpcReader {
|
||||||
throw NotImplementedError("Immediate direct API is not implemented for Aggregated Upstream")
|
throw NotImplementedError("Immediate direct API is not implemented for Aggregated Upstream")
|
||||||
@@ -235,6 +233,7 @@ abstract class Multistream(
|
|||||||
lagObserver = null
|
lagObserver = null
|
||||||
upstreams[0].setLag(0)
|
upstreams[0].setLag(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
upstreams.size > 1 -> if (lagObserver == null) lagObserver = makeLagObserver()
|
upstreams.size > 1 -> if (lagObserver == null) lagObserver = makeLagObserver()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,17 +388,17 @@ abstract class Multistream(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.warn("Head processing error: ${e.javaClass} ${e.message}")
|
log.warn("Head processing error: ${e.javaClass} ${e.message}")
|
||||||
}
|
}
|
||||||
val statuses = upstreams.asSequence().map { it.getStatus() }
|
val statuses = getUpstreams().asSequence().map { it.getStatus() }
|
||||||
.groupBy { it }
|
.groupBy { it }
|
||||||
.map { "${it.key.name}/${it.value.size}" }
|
.map { "${it.key.name}/${it.value.size}" }
|
||||||
.joinToString(",")
|
.joinToString(",")
|
||||||
val lag = upstreams.joinToString(", ") {
|
val lag = getUpstreams().joinToString(", ") {
|
||||||
// by default, when no lag is available it uses Long.MAX_VALUE, and it doesn't make sense to print
|
// by default, when no lag is available it uses Long.MAX_VALUE, and it doesn't make sense to print
|
||||||
// status with such value. use NA (as Not Available) instead
|
// status with such value. use NA (as Not Available) instead
|
||||||
val value = it.getLag()
|
val value = it.getLag()
|
||||||
value?.toString() ?: "NA"
|
value?.toString() ?: "NA"
|
||||||
}
|
}
|
||||||
val weak = upstreams
|
val weak = getUpstreams()
|
||||||
.filter { it.getStatus() != UpstreamAvailability.OK }
|
.filter { it.getStatus() != UpstreamAvailability.OK }
|
||||||
.joinToString(", ") { it.getId() }
|
.joinToString(", ") { it.getId() }
|
||||||
|
|
||||||
@@ -424,6 +423,7 @@ abstract class Multistream(
|
|||||||
onUpstreamsUpdated()
|
onUpstreamsUpdated()
|
||||||
updateUpstreams.emitNext(event.upstream) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED }
|
updateUpstreams.emitNext(event.upstream) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpstreamChangeEvent.ChangeType.ADDED -> {
|
UpstreamChangeEvent.ChangeType.ADDED -> {
|
||||||
if (!started) {
|
if (!started) {
|
||||||
start()
|
start()
|
||||||
@@ -441,6 +441,7 @@ abstract class Multistream(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpstreamChangeEvent.ChangeType.REMOVED -> {
|
UpstreamChangeEvent.ChangeType.REMOVED -> {
|
||||||
removeUpstream(event.upstream.getId()).takeIf { it }?.let {
|
removeUpstream(event.upstream.getId()).takeIf { it }?.let {
|
||||||
try {
|
try {
|
||||||
@@ -458,10 +459,10 @@ abstract class Multistream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun haveUpstreams(): Boolean =
|
fun haveUpstreams(): Boolean =
|
||||||
upstreams.isNotEmpty()
|
getUpstreams().isNotEmpty()
|
||||||
|
|
||||||
fun hasMatchingUpstream(matcher: Selector.LabelSelectorMatcher): Boolean {
|
fun hasMatchingUpstream(matcher: Selector.LabelSelectorMatcher): Boolean {
|
||||||
return upstreams.any { matcher.matches(it) }
|
return getUpstreams().any { matcher.matches(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subscribeAddedUpstreams(): Flux<Upstream> =
|
fun subscribeAddedUpstreams(): Flux<Upstream> =
|
||||||
@@ -469,12 +470,16 @@ abstract class Multistream(
|
|||||||
|
|
||||||
fun subscribeRemovedUpstreams(): Flux<Upstream> =
|
fun subscribeRemovedUpstreams(): Flux<Upstream> =
|
||||||
removedUpstreams.asFlux()
|
removedUpstreams.asFlux()
|
||||||
|
|
||||||
fun subscribeUpdatedUpstreams(): Flux<Upstream> =
|
fun subscribeUpdatedUpstreams(): Flux<Upstream> =
|
||||||
updateUpstreams.asFlux()
|
updateUpstreams.asFlux()
|
||||||
|
|
||||||
abstract fun makeLagObserver(): HeadLagObserver
|
abstract fun makeLagObserver(): HeadLagObserver
|
||||||
|
|
||||||
open fun tryProxySubscribe(matcher: Selector.Matcher, request: BlockchainOuterClass.NativeSubscribeRequest): Flux<out Any>? = null
|
open fun tryProxySubscribe(
|
||||||
|
matcher: Selector.Matcher,
|
||||||
|
request: BlockchainOuterClass.NativeSubscribeRequest,
|
||||||
|
): Flux<out Any>? = null
|
||||||
|
|
||||||
abstract fun getCachingReader(): CachingReader?
|
abstract fun getCachingReader(): CachingReader?
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
|
||||||
|
import io.emeraldpay.dshackle.foundation.ChainOptions
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
typealias UpstreamValidatorBuilder = (Chain, Upstream, ChainOptions.Options, ChainConfig) -> UpstreamValidator?
|
||||||
|
|
||||||
|
interface UpstreamValidator {
|
||||||
|
fun start(): Flux<UpstreamAvailability>
|
||||||
|
|
||||||
|
fun validateUpstreamSettings(): Mono<ValidateUpstreamSettingsResult>
|
||||||
|
|
||||||
|
fun validateUpstreamSettingsOnStartup(): ValidateUpstreamSettingsResult {
|
||||||
|
return validateUpstreamSettings().block() ?: ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ValidateUpstreamSettingsResult {
|
||||||
|
UPSTREAM_VALID,
|
||||||
|
UPSTREAM_SETTINGS_ERROR,
|
||||||
|
UPSTREAM_FATAL_SETTINGS_ERROR,
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ open class BitcoinMultistream(
|
|||||||
private val sourceUpstreams: MutableList<BitcoinUpstream>,
|
private val sourceUpstreams: MutableList<BitcoinUpstream>,
|
||||||
caches: Caches,
|
caches: Caches,
|
||||||
private val headScheduler: Scheduler,
|
private val headScheduler: Scheduler,
|
||||||
) : Multistream(chain, sourceUpstreams as MutableList<Upstream>, caches), Lifecycle {
|
) : Multistream(chain, caches), Lifecycle {
|
||||||
|
|
||||||
private var head: Head = EmptyHead()
|
private var head: Head = EmptyHead()
|
||||||
private var esplora = sourceUpstreams.find { it.esploraClient != null }?.esploraClient
|
private var esplora = sourceUpstreams.find { it.esploraClient != null }?.esploraClient
|
||||||
@@ -51,6 +51,13 @@ open class BitcoinMultistream(
|
|||||||
private var addressActiveCheck: AddressActiveCheck? = null
|
private var addressActiveCheck: AddressActiveCheck? = null
|
||||||
private var xpubAddresses: XpubAddresses? = null
|
private var xpubAddresses: XpubAddresses? = null
|
||||||
private var callRouter: LocalCallRouter = LocalCallRouter(DefaultBitcoinMethods(), reader)
|
private var callRouter: LocalCallRouter = LocalCallRouter(DefaultBitcoinMethods(), reader)
|
||||||
|
override fun getUpstreams(): MutableList<out Upstream> {
|
||||||
|
return sourceUpstreams
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addUpstreamInternal(u: Upstream) {
|
||||||
|
sourceUpstreams.add(u as BitcoinUpstream)
|
||||||
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
if (sourceUpstreams.size > 0) {
|
if (sourceUpstreams.size > 0) {
|
||||||
@@ -59,11 +66,6 @@ open class BitcoinMultistream(
|
|||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
open val upstreams: List<BitcoinUpstream>
|
|
||||||
get() {
|
|
||||||
return sourceUpstreams
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getXpubAddresses(): XpubAddresses? {
|
open fun getXpubAddresses(): XpubAddresses? {
|
||||||
return xpubAddresses
|
return xpubAddresses
|
||||||
}
|
}
|
||||||
@@ -103,7 +105,7 @@ open class BitcoinMultistream(
|
|||||||
.switchIfEmpty(Mono.error(Exception("No API available for $chain")))
|
.switchIfEmpty(Mono.error(Exception("No API available for $chain")))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLocalReader(localEnabled: Boolean): Mono<JsonRpcReader> {
|
override fun getLocalReader(): Mono<JsonRpcReader> {
|
||||||
return Mono.just(callRouter)
|
return Mono.just(callRouter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +148,7 @@ open class BitcoinMultistream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getEgressSubscription(): EgressSubscription {
|
override fun getEgressSubscription(): EgressSubscription {
|
||||||
return EmptyEgressSubscription()
|
return EmptyEgressSubscription
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isRunning(): Boolean {
|
override fun isRunning(): Boolean {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ open class BitcoinReader(
|
|||||||
|
|
||||||
private val unspentReader: UnspentReader = if (esploraClient != null) {
|
private val unspentReader: UnspentReader = if (esploraClient != null) {
|
||||||
EsploraUnspentReader(esploraClient, head)
|
EsploraUnspentReader(esploraClient, head)
|
||||||
} else if (upstreams.upstreams.any { it.isGrpc() && it.getCapabilities().contains(Capability.BALANCE) }) {
|
} else if (upstreams.getUpstreams().any { it.isGrpc() && it.getCapabilities().contains(Capability.BALANCE) }) {
|
||||||
RemoteUnspentReader(upstreams)
|
RemoteUnspentReader(upstreams)
|
||||||
} else {
|
} else {
|
||||||
RpcUnspentReader(upstreams)
|
RpcUnspentReader(upstreams)
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.upstream.ApiSource
|
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
|
||||||
import io.emeraldpay.etherjar.erc20.ERC20Token
|
|
||||||
import io.emeraldpay.etherjar.hex.Hex32
|
|
||||||
import io.emeraldpay.etherjar.hex.HexQuantity
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import java.math.BigInteger
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query for a ERC20 token balance for an address
|
|
||||||
*/
|
|
||||||
open class ERC20Balance {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(ERC20Balance::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getBalance(upstreams: EthereumPosMultiStream, token: ERC20Token, address: Address): Mono<BigInteger> {
|
|
||||||
return upstreams
|
|
||||||
// use only up-to-date upstreams
|
|
||||||
.getApiSource(Selector.HeightMatcher(upstreams.getHead().getCurrentHeight() ?: 0))
|
|
||||||
.let { getBalance(it, token, address) }
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getBalance(apis: ApiSource, token: ERC20Token, address: Address): Mono<BigInteger> {
|
|
||||||
apis.request(1)
|
|
||||||
return Flux.from(apis)
|
|
||||||
.flatMap {
|
|
||||||
getBalance(it.cast(EthereumLikeRpcUpstream::class.java), token, address)
|
|
||||||
}
|
|
||||||
.doOnNext {
|
|
||||||
apis.resolve()
|
|
||||||
}
|
|
||||||
.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getBalance(upstream: EthereumLikeRpcUpstream, token: ERC20Token, address: Address): Mono<BigInteger> {
|
|
||||||
return upstream
|
|
||||||
.getIngressReader()
|
|
||||||
.read(prepareEthCall(token, address, upstream.getHead()))
|
|
||||||
.flatMap(JsonRpcResponse::requireStringResult)
|
|
||||||
.map { Hex32.from(it).asQuantity().value }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun prepareEthCall(token: ERC20Token, target: Address, head: Head): JsonRpcRequest {
|
|
||||||
val call = token
|
|
||||||
.readBalanceOf(target)
|
|
||||||
.toJson()
|
|
||||||
val height = head.getCurrentHeight()?.let { HexQuantity.from(it).toHex() } ?: "latest"
|
|
||||||
return JsonRpcRequest("eth_call", listOf(call, height))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,6 @@ import io.emeraldpay.dshackle.reader.RekeyingReader
|
|||||||
import io.emeraldpay.dshackle.reader.SpannedReader
|
import io.emeraldpay.dshackle.reader.SpannedReader
|
||||||
import io.emeraldpay.dshackle.reader.TransformingReader
|
import io.emeraldpay.dshackle.reader.TransformingReader
|
||||||
import io.emeraldpay.dshackle.upstream.CachingReader
|
import io.emeraldpay.dshackle.upstream.CachingReader
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumDirectReader.Result
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumDirectReader.Result
|
||||||
@@ -58,7 +57,7 @@ open class EthereumCachingReader(
|
|||||||
private val caches: Caches,
|
private val caches: Caches,
|
||||||
callMethodsFactory: Factory<CallMethods>,
|
callMethodsFactory: Factory<CallMethods>,
|
||||||
private val tracer: Tracer,
|
private val tracer: Tracer,
|
||||||
) : Lifecycle, CachingReader {
|
) : CachingReader {
|
||||||
|
|
||||||
private val objectMapper: ObjectMapper = Global.objectMapper
|
private val objectMapper: ObjectMapper = Global.objectMapper
|
||||||
private val balanceCache = CurrentBlockCache<Address, Wei>()
|
private val balanceCache = CurrentBlockCache<Address, Wei>()
|
||||||
|
|||||||
@@ -1,9 +1,31 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.foundation.ChainOptions.Options
|
||||||
|
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.CachingReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.Capability
|
||||||
|
import io.emeraldpay.dshackle.upstream.EgressSubscription
|
||||||
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.LabelsDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamValidator
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.AggregatedPendingTxes
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.EthereumLabelsDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.NoPendingTxes
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.CachingReaderBuilder
|
||||||
import io.emeraldpay.dshackle.upstream.generic.ChainSpecific
|
import io.emeraldpay.dshackle.upstream.generic.ChainSpecific
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
|
import org.springframework.cloud.sleuth.Tracer
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.core.scheduler.Scheduler
|
||||||
|
|
||||||
object EthereumChainSpecific : ChainSpecific {
|
object EthereumChainSpecific : ChainSpecific {
|
||||||
override fun parseBlock(data: JsonRpcResponse, upstreamId: String): BlockContainer {
|
override fun parseBlock(data: JsonRpcResponse, upstreamId: String): BlockContainer {
|
||||||
@@ -11,4 +33,57 @@ object EthereumChainSpecific : ChainSpecific {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun latestBlockRequest() = JsonRpcRequest("eth_getBlockByNumber", listOf("latest", false))
|
override fun latestBlockRequest() = JsonRpcRequest("eth_getBlockByNumber", listOf("latest", false))
|
||||||
|
override fun localReaderBuilder(
|
||||||
|
cachingReader: CachingReader,
|
||||||
|
methods: CallMethods,
|
||||||
|
head: Head,
|
||||||
|
): Mono<JsonRpcReader> {
|
||||||
|
return Mono.just(EthereumLocalReader(cachingReader as EthereumCachingReader, methods, head))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun subscriptionBuilder(headScheduler: Scheduler): (Multistream) -> EgressSubscription {
|
||||||
|
return { ms ->
|
||||||
|
val pendingTxes: PendingTxesSource = (ms.getAll())
|
||||||
|
.filter { it is GenericUpstream }
|
||||||
|
.map { it as GenericUpstream }
|
||||||
|
.mapNotNull {
|
||||||
|
(it.getIngressSubscription() as EthereumIngressSubscription).getPendingTxes()
|
||||||
|
}.let {
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
NoPendingTxes()
|
||||||
|
} else if (it.size == 1) {
|
||||||
|
it.first()
|
||||||
|
} else {
|
||||||
|
AggregatedPendingTxes(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EthereumEgressSubscription(ms, headScheduler, pendingTxes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun makeCachingReaderBuilder(tracer: Tracer): CachingReaderBuilder {
|
||||||
|
return { ms, caches, methodsFactory -> EthereumCachingReader(ms, caches, methodsFactory, tracer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validator(
|
||||||
|
chain: Chain,
|
||||||
|
upstream: Upstream,
|
||||||
|
options: Options,
|
||||||
|
config: ChainConfig,
|
||||||
|
): UpstreamValidator? {
|
||||||
|
return EthereumUpstreamValidator(chain, upstream, options, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun labelDetector(chain: Chain, reader: JsonRpcReader): LabelsDetector? {
|
||||||
|
return EthereumLabelsDetector(reader, chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun subscriptionTopics(upstream: GenericUpstream): List<String> {
|
||||||
|
val subs = if (upstream.getCapabilities().contains(Capability.WS_HEAD)) {
|
||||||
|
listOf(EthereumEgressSubscription.METHOD_NEW_HEADS, EthereumEgressSubscription.METHOD_LOGS)
|
||||||
|
} else {
|
||||||
|
listOf()
|
||||||
|
}
|
||||||
|
return upstream.getIngressSubscription().getAvailableTopics().plus(subs).toSet().toList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.upstream.AbstractChainFees
|
|
||||||
import io.emeraldpay.dshackle.upstream.ChainFees
|
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionJsonSnapshot
|
|
||||||
import io.emeraldpay.etherjar.domain.Wei
|
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.util.function.Tuples
|
|
||||||
import java.util.function.Function
|
|
||||||
|
|
||||||
abstract class EthereumFees(
|
|
||||||
upstreams: Multistream,
|
|
||||||
private val reader: EthereumCachingReader,
|
|
||||||
heightLimit: Int,
|
|
||||||
) : AbstractChainFees<EthereumFees.EthereumFee, BlockJson<TransactionRefJson>, TransactionRefJson, TransactionJsonSnapshot>(heightLimit, upstreams, extractTx), ChainFees {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(EthereumFees::class.java)
|
|
||||||
|
|
||||||
private val extractTx = { block: BlockJson<TransactionRefJson> ->
|
|
||||||
block.transactions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun extractFee(block: BlockJson<TransactionRefJson>, tx: TransactionJsonSnapshot): EthereumFee
|
|
||||||
|
|
||||||
override fun readFeesAt(height: Long, selector: TxAt<BlockJson<TransactionRefJson>, TransactionRefJson>): Mono<EthereumFee> {
|
|
||||||
return reader.blocksByHeightParsed().read(height)
|
|
||||||
.flatMap { block ->
|
|
||||||
Mono.justOrEmpty(selector.get(block))
|
|
||||||
.cast(TransactionRefJson::class.java)
|
|
||||||
.flatMap { reader.txByHash().read(it.hash) }
|
|
||||||
.map { tx -> extractFee(block, tx) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun feeAggregation(mode: ChainFees.Mode): Function<Flux<EthereumFee>, Mono<EthereumFee>> {
|
|
||||||
if (mode == ChainFees.Mode.MIN_ALWAYS) {
|
|
||||||
return Function { src ->
|
|
||||||
src.reduce { a, b ->
|
|
||||||
EthereumFee(
|
|
||||||
a.max.coerceAtLeast(b.max),
|
|
||||||
a.priority.coerceAtLeast(b.priority),
|
|
||||||
a.paid.coerceAtLeast(b.paid),
|
|
||||||
Wei.ZERO,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Function { src ->
|
|
||||||
src.map { Tuples.of(1, it) }
|
|
||||||
.reduce { a, b ->
|
|
||||||
Tuples.of(a.t1 + b.t1, a.t2.plus(b.t2))
|
|
||||||
}.map {
|
|
||||||
EthereumFee(it.t2.max / it.t1, it.t2.priority / it.t1, it.t2.paid / it.t1, it.t2.base / it.t1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---
|
|
||||||
|
|
||||||
data class EthereumFee(val max: Wei, val priority: Wei, val paid: Wei, val base: Wei) {
|
|
||||||
fun plus(o: EthereumFee): EthereumFee {
|
|
||||||
return EthereumFee(max + o.max, priority + o.priority, paid + o.paid, base + o.base)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionJsonSnapshot
|
|
||||||
import io.emeraldpay.etherjar.domain.Wei
|
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.util.function.Function
|
|
||||||
|
|
||||||
class EthereumLegacyFees(upstreams: EthereumMultistream, reader: EthereumCachingReader, heightLimit: Int) :
|
|
||||||
EthereumFees(upstreams, reader, heightLimit) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(EthereumLegacyFees::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val toGrpc: Function<EthereumFee, BlockchainOuterClass.EstimateFeeResponse> = Function {
|
|
||||||
BlockchainOuterClass.EstimateFeeResponse.newBuilder()
|
|
||||||
.setEthereumStd(
|
|
||||||
BlockchainOuterClass.EthereumStdFees.newBuilder()
|
|
||||||
.setFee(it.paid.amount.toString()),
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun extractFee(block: BlockJson<TransactionRefJson>, tx: TransactionJsonSnapshot): EthereumFee {
|
|
||||||
return EthereumFee(tx.gasPrice, tx.gasPrice, tx.gasPrice, Wei.ZERO)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getResponseBuilder(): Function<EthereumFee, BlockchainOuterClass.EstimateFeeResponse> {
|
|
||||||
return toGrpc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2020 EmeraldPay, Inc
|
|
||||||
* 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.upstream.ethereum
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
|
||||||
import io.emeraldpay.dshackle.cache.CachesEnabled
|
|
||||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import io.emeraldpay.dshackle.foundation.ChainOptions
|
|
||||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
|
||||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
|
||||||
import io.emeraldpay.dshackle.upstream.Capability
|
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_VALID
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.EthereumLabelsDetector
|
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.ConnectorFactory
|
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
|
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
|
||||||
import org.springframework.context.Lifecycle
|
|
||||||
import reactor.core.Disposable
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
open class EthereumLikeRpcUpstream(
|
|
||||||
id: String,
|
|
||||||
hash: Byte,
|
|
||||||
val chain: Chain,
|
|
||||||
options: ChainOptions.Options,
|
|
||||||
role: UpstreamsConfig.UpstreamRole,
|
|
||||||
targets: CallMethods?,
|
|
||||||
private val node: QuorumForLabels.QuorumItem?,
|
|
||||||
connectorFactory: ConnectorFactory,
|
|
||||||
chainConfig: ChainsConfig.ChainConfig,
|
|
||||||
skipEnhance: Boolean,
|
|
||||||
private val eventPublisher: ApplicationEventPublisher?,
|
|
||||||
) : EthereumLikeUpstream(id, hash, options, role, targets, node, chainConfig), Lifecycle, Upstream, CachesEnabled {
|
|
||||||
private val validator: EthereumUpstreamValidator = EthereumUpstreamValidator(chain, this, getOptions(), chainConfig.callLimitContract)
|
|
||||||
protected val connector: GenericConnector = connectorFactory.create(this, chain, skipEnhance)
|
|
||||||
private val labelsDetector = EthereumLabelsDetector(this.getIngressReader(), chain)
|
|
||||||
private var hasLiveSubscriptionHead: AtomicBoolean = AtomicBoolean(false)
|
|
||||||
|
|
||||||
private var validatorSubscription: Disposable? = null
|
|
||||||
private var livenessSubscription: Disposable? = null
|
|
||||||
private var validationSettingsSubscription: Disposable? = null
|
|
||||||
|
|
||||||
override fun getCapabilities(): Set<Capability> {
|
|
||||||
return if (hasLiveSubscriptionHead.get()) {
|
|
||||||
setOf(Capability.RPC, Capability.BALANCE, Capability.WS_HEAD)
|
|
||||||
} else {
|
|
||||||
setOf(Capability.RPC, Capability.BALANCE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setCaches(caches: Caches) {
|
|
||||||
if (connector is CachesEnabled) {
|
|
||||||
connector.setCaches(caches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
log.info("Configured for ${chain.chainName}")
|
|
||||||
connector.start()
|
|
||||||
val validSettingsResult = validator.validateUpstreamSettingsOnStartup()
|
|
||||||
when (validSettingsResult) {
|
|
||||||
UPSTREAM_FATAL_SETTINGS_ERROR -> {
|
|
||||||
connector.stop()
|
|
||||||
log.warn("Upstream ${getId()} couldn't start, invalid upstream settings")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UPSTREAM_SETTINGS_ERROR -> {
|
|
||||||
validateUpstreamSettings()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
upstreamStart()
|
|
||||||
labelsDetector.detectLabels()
|
|
||||||
.toStream()
|
|
||||||
.forEach { updateLabels(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateUpstreamSettings() {
|
|
||||||
validationSettingsSubscription = Flux.interval(
|
|
||||||
Duration.ofSeconds(10),
|
|
||||||
Duration.ofSeconds(20),
|
|
||||||
).flatMap {
|
|
||||||
validator.validateUpstreamSettings()
|
|
||||||
}.subscribe {
|
|
||||||
when (it) {
|
|
||||||
UPSTREAM_FATAL_SETTINGS_ERROR -> {
|
|
||||||
connector.stop()
|
|
||||||
log.warn("Upstream ${getId()} couldn't start, invalid upstream settings")
|
|
||||||
disposeValidationSettingsSubscription()
|
|
||||||
}
|
|
||||||
UPSTREAM_VALID -> {
|
|
||||||
upstreamStart()
|
|
||||||
labelsDetector.detectLabels()
|
|
||||||
.subscribe { label -> updateLabels(label) }
|
|
||||||
eventPublisher?.publishEvent(UpstreamChangeEvent(chain, this, UpstreamChangeEvent.ChangeType.ADDED))
|
|
||||||
disposeValidationSettingsSubscription()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
log.warn("Continue validation of upstream ${getId()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun upstreamStart() {
|
|
||||||
if (getOptions().disableValidation) {
|
|
||||||
log.warn("Disable validation for upstream ${this.getId()}")
|
|
||||||
this.setLag(0)
|
|
||||||
this.setStatus(UpstreamAvailability.OK)
|
|
||||||
} else {
|
|
||||||
log.debug("Start validation for upstream ${this.getId()}")
|
|
||||||
validatorSubscription = validator.start()
|
|
||||||
.subscribe(this::setStatus)
|
|
||||||
}
|
|
||||||
livenessSubscription = connector.hasLiveSubscriptionHead().subscribe({
|
|
||||||
hasLiveSubscriptionHead.set(it)
|
|
||||||
eventPublisher?.publishEvent(UpstreamChangeEvent(chain, this, UpstreamChangeEvent.ChangeType.UPDATED))
|
|
||||||
}, {
|
|
||||||
log.debug("Error while checking live subscription for ${getId()}", it)
|
|
||||||
},)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateLabels(label: Pair<String, String>) {
|
|
||||||
log.info("Detected label ${label.first} with value ${label.second} for upstream ${getId()}")
|
|
||||||
node?.labels?.let { labels ->
|
|
||||||
labels[label.first] = label.second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIngressSubscription(): EthereumIngressSubscription {
|
|
||||||
return connector.getIngressSubscription()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSubscriptionTopics(): List<String> {
|
|
||||||
val subs = if (getCapabilities().contains(Capability.WS_HEAD)) {
|
|
||||||
listOf(EthereumEgressSubscription.METHOD_NEW_HEADS, EthereumEgressSubscription.METHOD_LOGS)
|
|
||||||
} else {
|
|
||||||
listOf()
|
|
||||||
}
|
|
||||||
return getIngressSubscription().getAvailableTopics().plus(subs).toSet().toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHead(): Head {
|
|
||||||
return connector.getHead()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stop() {
|
|
||||||
validatorSubscription?.dispose()
|
|
||||||
validatorSubscription = null
|
|
||||||
livenessSubscription?.dispose()
|
|
||||||
livenessSubscription = null
|
|
||||||
disposeValidationSettingsSubscription()
|
|
||||||
connector.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isRunning(): Boolean {
|
|
||||||
return connector.isRunning() && validationSettingsSubscription == null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIngressReader(): JsonRpcReader {
|
|
||||||
return connector.getIngressReader()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isGrpc(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : Upstream> cast(selfType: Class<T>): T {
|
|
||||||
if (!selfType.isAssignableFrom(this.javaClass)) {
|
|
||||||
throw ClassCastException("Cannot cast ${this.javaClass} to $selfType")
|
|
||||||
}
|
|
||||||
return this as T
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun disposeValidationSettingsSubscription() {
|
|
||||||
validationSettingsSubscription?.dispose()
|
|
||||||
validationSettingsSubscription = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2020 EmeraldPay, Inc
|
|
||||||
* 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.upstream.ethereum
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import io.emeraldpay.dshackle.foundation.ChainOptions
|
|
||||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
|
||||||
import io.emeraldpay.dshackle.upstream.Capability
|
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
|
||||||
|
|
||||||
abstract class EthereumLikeUpstream(
|
|
||||||
id: String,
|
|
||||||
hash: Byte,
|
|
||||||
options: ChainOptions.Options,
|
|
||||||
role: UpstreamsConfig.UpstreamRole,
|
|
||||||
targets: CallMethods?,
|
|
||||||
private val node: QuorumForLabels.QuorumItem?,
|
|
||||||
val chainConfig: ChainsConfig.ChainConfig,
|
|
||||||
) : DefaultUpstream(id, hash, options, role, targets, node, chainConfig) {
|
|
||||||
|
|
||||||
private val capabilities = setOf(Capability.RPC, Capability.BALANCE)
|
|
||||||
|
|
||||||
override fun getCapabilities(): Set<Capability> {
|
|
||||||
return capabilities
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLabels(): Collection<UpstreamsConfig.Labels> {
|
|
||||||
return node?.let { listOf(it.labels) } ?: emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun getIngressSubscription(): EthereumIngressSubscription
|
|
||||||
}
|
|
||||||
@@ -41,7 +41,6 @@ class EthereumLocalReader(
|
|||||||
private val reader: EthereumCachingReader,
|
private val reader: EthereumCachingReader,
|
||||||
private val methods: CallMethods,
|
private val methods: CallMethods,
|
||||||
private val head: Head,
|
private val head: Head,
|
||||||
private val localEnabled: Boolean,
|
|
||||||
) : JsonRpcReader {
|
) : JsonRpcReader {
|
||||||
|
|
||||||
override fun read(key: JsonRpcRequest): Mono<JsonRpcResponse> {
|
override fun read(key: JsonRpcRequest): Mono<JsonRpcResponse> {
|
||||||
@@ -49,9 +48,6 @@ class EthereumLocalReader(
|
|||||||
return Mono.just(methods.executeHardcoded(key.method))
|
return Mono.just(methods.executeHardcoded(key.method))
|
||||||
.map { JsonRpcResponse(it, null) }
|
.map { JsonRpcResponse(it, null) }
|
||||||
}
|
}
|
||||||
if (!localEnabled) {
|
|
||||||
return Mono.empty()
|
|
||||||
}
|
|
||||||
if (!methods.isCallable(key.method)) {
|
if (!methods.isCallable(key.method)) {
|
||||||
return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method"))
|
return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2020 EmeraldPay, Inc
|
|
||||||
* 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.api.proto.BlockchainOuterClass
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
|
||||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
|
||||||
import io.emeraldpay.dshackle.upstream.DistanceExtractor
|
|
||||||
import io.emeraldpay.dshackle.upstream.DynamicMergedHead
|
|
||||||
import io.emeraldpay.dshackle.upstream.EgressSubscription
|
|
||||||
import io.emeraldpay.dshackle.upstream.EmptyHead
|
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
|
||||||
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
|
||||||
import io.emeraldpay.dshackle.upstream.MergedHead
|
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.AggregatedPendingTxes
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.NoPendingTxes
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource
|
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
|
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.PriorityForkChoice
|
|
||||||
import io.emeraldpay.dshackle.upstream.grpc.GrpcUpstream
|
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
|
||||||
import org.springframework.cloud.sleuth.Tracer
|
|
||||||
import org.springframework.util.ConcurrentReferenceHashMap
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.core.scheduler.Scheduler
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
open class EthereumMultistream(
|
|
||||||
chain: Chain,
|
|
||||||
val upstreams: MutableList<EthereumLikeUpstream>,
|
|
||||||
caches: Caches,
|
|
||||||
private val headScheduler: Scheduler,
|
|
||||||
tracer: Tracer,
|
|
||||||
) : Multistream(chain, upstreams as MutableList<Upstream>, caches) {
|
|
||||||
|
|
||||||
private var head: DynamicMergedHead = DynamicMergedHead(
|
|
||||||
PriorityForkChoice(),
|
|
||||||
"ETH Multistream of ${chain.chainCode}",
|
|
||||||
headScheduler,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val filteredHeads: MutableMap<String, Head> =
|
|
||||||
ConcurrentReferenceHashMap(16, ConcurrentReferenceHashMap.ReferenceType.WEAK)
|
|
||||||
|
|
||||||
private val reader: EthereumCachingReader = EthereumCachingReader(this, this.caches, getMethodsFactory(), tracer)
|
|
||||||
private var subscribe = EthereumEgressSubscription(this, headScheduler, NoPendingTxes())
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun init() {
|
|
||||||
if (upstreams.size > 0) {
|
|
||||||
upstreams.forEach { addHead(it) }
|
|
||||||
}
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpstreamsUpdated() {
|
|
||||||
super.onUpstreamsUpdated()
|
|
||||||
|
|
||||||
val pendingTxes: PendingTxesSource = upstreams
|
|
||||||
.mapNotNull {
|
|
||||||
it.getIngressSubscription().getPendingTxes()
|
|
||||||
}.let {
|
|
||||||
if (it.isEmpty()) {
|
|
||||||
NoPendingTxes()
|
|
||||||
} else if (it.size == 1) {
|
|
||||||
it.first()
|
|
||||||
} else {
|
|
||||||
AggregatedPendingTxes(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscribe = EthereumEgressSubscription(this, headScheduler, pendingTxes)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
super.start()
|
|
||||||
head.start()
|
|
||||||
onHeadUpdated(head)
|
|
||||||
reader.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stop() {
|
|
||||||
super.stop()
|
|
||||||
reader.stop()
|
|
||||||
filteredHeads.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addHead(upstream: Upstream) {
|
|
||||||
val newHead = upstream.getHead()
|
|
||||||
if (newHead is Lifecycle && !newHead.isRunning()) {
|
|
||||||
newHead.start()
|
|
||||||
}
|
|
||||||
head.addHead(upstream)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeHead(upstreamId: String) {
|
|
||||||
head.removeHead(upstreamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun makeLagObserver(): HeadLagObserver =
|
|
||||||
HeadLagObserver(head, upstreams, DistanceExtractor::extractPowDistance, headScheduler, 6).apply {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isRunning(): Boolean {
|
|
||||||
return super.isRunning() || reader.isRunning()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCachingReader(): EthereumCachingReader {
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHead(): Head {
|
|
||||||
return head
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tryProxySubscribe(
|
|
||||||
matcher: Selector.Matcher,
|
|
||||||
request: BlockchainOuterClass.NativeSubscribeRequest,
|
|
||||||
): Flux<out Any>? =
|
|
||||||
upstreams.filter {
|
|
||||||
matcher.matches(it)
|
|
||||||
}.takeIf { ups ->
|
|
||||||
ups.size == 1 && ups.all { it.isGrpc() }
|
|
||||||
}?.map {
|
|
||||||
it as GrpcUpstream
|
|
||||||
}?.map {
|
|
||||||
it.getBlockchainApi().nativeSubscribe(request)
|
|
||||||
}?.let {
|
|
||||||
Flux.merge(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLabels(): Collection<UpstreamsConfig.Labels> {
|
|
||||||
return upstreams.flatMap { it.getLabels() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : Upstream> cast(selfType: Class<T>): T {
|
|
||||||
if (!selfType.isAssignableFrom(this.javaClass)) {
|
|
||||||
throw ClassCastException("Cannot cast ${this.javaClass} to $selfType")
|
|
||||||
}
|
|
||||||
return this as T
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEgressSubscription(): EgressSubscription {
|
|
||||||
return subscribe
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLocalReader(localEnabled: Boolean): Mono<JsonRpcReader> {
|
|
||||||
return Mono.just(EthereumLocalReader(reader, getMethods(), getHead(), localEnabled))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHead(mather: Selector.Matcher): Head =
|
|
||||||
filteredHeads.computeIfAbsent(mather.describeInternal().intern()) { _ ->
|
|
||||||
upstreams.filter { mather.matches(it) }
|
|
||||||
.apply {
|
|
||||||
log.debug("Found $size upstreams matching [${mather.describeInternal()}]")
|
|
||||||
}.let {
|
|
||||||
val selected = it.map { it.getHead() }
|
|
||||||
when (it.size) {
|
|
||||||
0 -> EmptyHead()
|
|
||||||
1 -> selected.first()
|
|
||||||
else -> MergedHead(selected, MostWorkForkChoice(), headScheduler, "Eth head ${it.map { it.getId() }}").apply {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEnrichedHead(mather: Selector.Matcher): Head =
|
|
||||||
filteredHeads.computeIfAbsent(mather.describeInternal().intern()) { _ ->
|
|
||||||
upstreams.filter { mather.matches(it) }
|
|
||||||
.apply {
|
|
||||||
log.debug("Found $size upstreams matching [${mather.describeInternal()}]")
|
|
||||||
}.let {
|
|
||||||
val selected = it.map { source -> source.getHead() }
|
|
||||||
EnrichedMergedHead(
|
|
||||||
selected,
|
|
||||||
getHead(),
|
|
||||||
headScheduler,
|
|
||||||
object :
|
|
||||||
Reader<BlockHash, BlockContainer> {
|
|
||||||
override fun read(key: BlockHash): Mono<BlockContainer> {
|
|
||||||
return reader.blocksByHashAsCont().read(key).map { res -> res.data }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionJsonSnapshot
|
|
||||||
import io.emeraldpay.etherjar.domain.Wei
|
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.util.function.Function
|
|
||||||
|
|
||||||
class EthereumPriorityFees(upstreams: Multistream, reader: EthereumCachingReader, heightLimit: Int) :
|
|
||||||
EthereumFees(upstreams, reader, heightLimit) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LoggerFactory.getLogger(EthereumPriorityFees::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val toGrpc: Function<EthereumFee, BlockchainOuterClass.EstimateFeeResponse> =
|
|
||||||
Function {
|
|
||||||
BlockchainOuterClass.EstimateFeeResponse.newBuilder()
|
|
||||||
.setEthereumExtended(
|
|
||||||
BlockchainOuterClass.EthereumExtFees.newBuilder()
|
|
||||||
.setMax(it.max.amount.toString())
|
|
||||||
.setPriority(it.priority.amount.toString())
|
|
||||||
.setExpect(it.paid.amount.toString()),
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun extractFee(block: BlockJson<TransactionRefJson>, tx: TransactionJsonSnapshot): EthereumFee {
|
|
||||||
val baseFee = block.baseFeePerGas ?: Wei.ZERO
|
|
||||||
if (tx.type == 2) {
|
|
||||||
// an EIP-1559 Transaction provides Max and Priority fee
|
|
||||||
val paid = (baseFee + tx.maxPriorityFeePerGas).coerceAtMost(tx.maxFeePerGas)
|
|
||||||
return EthereumFee(tx.maxFeePerGas, tx.maxPriorityFeePerGas, paid, baseFee)
|
|
||||||
}
|
|
||||||
return EthereumFee(tx.gasPrice, (tx.gasPrice - baseFee).coerceAtLeast(Wei.ZERO), tx.gasPrice, baseFee)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getResponseBuilder(): Function<EthereumFee, BlockchainOuterClass.EstimateFeeResponse> {
|
|
||||||
return toGrpc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,12 +20,12 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Defaults
|
import io.emeraldpay.dshackle.Defaults
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
|
||||||
import io.emeraldpay.dshackle.foundation.ChainOptions
|
import io.emeraldpay.dshackle.foundation.ChainOptions
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
|
import io.emeraldpay.dshackle.upstream.UpstreamValidator
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR
|
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_VALID
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
@@ -47,8 +47,8 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
private val chain: Chain,
|
private val chain: Chain,
|
||||||
private val upstream: Upstream,
|
private val upstream: Upstream,
|
||||||
private val options: ChainOptions.Options,
|
private val options: ChainOptions.Options,
|
||||||
private val callLimitContract: String? = null,
|
private val config: ChainConfig,
|
||||||
) {
|
) : UpstreamValidator {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(EthereumUpstreamValidator::class.java)
|
private val log = LoggerFactory.getLogger(EthereumUpstreamValidator::class.java)
|
||||||
val scheduler =
|
val scheduler =
|
||||||
@@ -127,7 +127,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(): Flux<UpstreamAvailability> {
|
override fun start(): Flux<UpstreamAvailability> {
|
||||||
return Flux.interval(
|
return Flux.interval(
|
||||||
Duration.ZERO,
|
Duration.ZERO,
|
||||||
Duration.ofSeconds(options.validationInterval.toLong()),
|
Duration.ofSeconds(options.validationInterval.toLong()),
|
||||||
@@ -140,13 +140,9 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateUpstreamSettingsOnStartup(): ValidateUpstreamSettingsResult {
|
override fun validateUpstreamSettings(): Mono<ValidateUpstreamSettingsResult> {
|
||||||
return validateUpstreamSettings().block() ?: UPSTREAM_FATAL_SETTINGS_ERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
fun validateUpstreamSettings(): Mono<ValidateUpstreamSettingsResult> {
|
|
||||||
if (options.disableUpstreamValidation) {
|
if (options.disableUpstreamValidation) {
|
||||||
return Mono.just(UPSTREAM_VALID)
|
return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
|
||||||
}
|
}
|
||||||
return Mono.zip(
|
return Mono.zip(
|
||||||
validateChain(),
|
validateChain(),
|
||||||
@@ -159,7 +155,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
|
|
||||||
private fun validateChain(): Mono<ValidateUpstreamSettingsResult> {
|
private fun validateChain(): Mono<ValidateUpstreamSettingsResult> {
|
||||||
if (!options.validateChain) {
|
if (!options.validateChain) {
|
||||||
return Mono.just(UPSTREAM_VALID)
|
return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
|
||||||
}
|
}
|
||||||
return Mono.zip(
|
return Mono.zip(
|
||||||
chainId(),
|
chainId(),
|
||||||
@@ -178,20 +174,20 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isChainValid) {
|
if (isChainValid) {
|
||||||
UPSTREAM_VALID
|
ValidateUpstreamSettingsResult.UPSTREAM_VALID
|
||||||
} else {
|
} else {
|
||||||
UPSTREAM_FATAL_SETTINGS_ERROR
|
ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onErrorResume {
|
.onErrorResume {
|
||||||
log.error("Error during chain validation", it)
|
log.error("Error during chain validation", it)
|
||||||
Mono.just(UPSTREAM_SETTINGS_ERROR)
|
Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateCallLimit(): Mono<ValidateUpstreamSettingsResult> {
|
private fun validateCallLimit(): Mono<ValidateUpstreamSettingsResult> {
|
||||||
if (!options.validateCallLimit || callLimitContract == null) {
|
if (!options.validateCallLimit || config.callLimitContract == null) {
|
||||||
return Mono.just(UPSTREAM_VALID)
|
return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
|
||||||
}
|
}
|
||||||
return upstream.getIngressReader()
|
return upstream.getIngressReader()
|
||||||
.read(
|
.read(
|
||||||
@@ -199,7 +195,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
"eth_call",
|
"eth_call",
|
||||||
listOf(
|
listOf(
|
||||||
TransactionCallJson(
|
TransactionCallJson(
|
||||||
Address.from(callLimitContract),
|
Address.from(config.callLimitContract),
|
||||||
// calling contract with param 200_000, meaning it will generate 200k symbols or response
|
// calling contract with param 200_000, meaning it will generate 200k symbols or response
|
||||||
// f4240 + metadata — ~1 million
|
// f4240 + metadata — ~1 million
|
||||||
HexData.from("0xd8a26e3a00000000000000000000000000000000000000000000000000000000000f4240"),
|
HexData.from("0xd8a26e3a00000000000000000000000000000000000000000000000000000000000f4240"),
|
||||||
@@ -209,7 +205,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.flatMap(JsonRpcResponse::requireResult)
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
.map { UPSTREAM_VALID }
|
.map { ValidateUpstreamSettingsResult.UPSTREAM_VALID }
|
||||||
.onErrorResume {
|
.onErrorResume {
|
||||||
if (it.message != null && it.message!!.contains("rpc.returndata.limit")) {
|
if (it.message != null && it.message!!.contains("rpc.returndata.limit")) {
|
||||||
log.warn(
|
log.warn(
|
||||||
@@ -217,7 +213,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
"You need to set up your return limit to at least 1_100_000. " +
|
"You need to set up your return limit to at least 1_100_000. " +
|
||||||
"Erigon config example: https://github.com/ledgerwatch/erigon/blob/d014da4dc039ea97caf04ed29feb2af92b7b129d/cmd/utils/flags.go#L369",
|
"Erigon config example: https://github.com/ledgerwatch/erigon/blob/d014da4dc039ea97caf04ed29feb2af92b7b129d/cmd/utils/flags.go#L369",
|
||||||
)
|
)
|
||||||
Mono.just(UPSTREAM_FATAL_SETTINGS_ERROR)
|
Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR)
|
||||||
} else {
|
} else {
|
||||||
Mono.error(it)
|
Mono.error(it)
|
||||||
}
|
}
|
||||||
@@ -233,7 +229,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
"message ${ctx.exception().message}",
|
"message ${ctx.exception().message}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onErrorReturn(UPSTREAM_SETTINGS_ERROR)
|
.onErrorReturn(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateOldBlocks(): Mono<ValidateUpstreamSettingsResult> {
|
private fun validateOldBlocks(): Mono<ValidateUpstreamSettingsResult> {
|
||||||
@@ -257,11 +253,11 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
"Node ${upstream.getId()} probably is synced incorrectly, it is not possible to get old blocks",
|
"Node ${upstream.getId()} probably is synced incorrectly, it is not possible to get old blocks",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
UPSTREAM_VALID
|
ValidateUpstreamSettingsResult.UPSTREAM_VALID
|
||||||
}
|
}
|
||||||
.onErrorResume {
|
.onErrorResume {
|
||||||
log.warn("Error during old blocks validation", it)
|
log.warn("Error during old blocks validation", it)
|
||||||
Mono.just(UPSTREAM_VALID)
|
Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,10 +286,4 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
.doOnError { log.error("Error during execution 'net_version' - ${it.message} for ${upstream.getId()}") }
|
.doOnError { log.error("Error during execution 'net_version' - ${it.message} for ${upstream.getId()}") }
|
||||||
.flatMap(JsonRpcResponse::requireStringResult)
|
.flatMap(JsonRpcResponse::requireStringResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ValidateUpstreamSettingsResult {
|
|
||||||
UPSTREAM_VALID,
|
|
||||||
UPSTREAM_SETTINGS_ERROR,
|
|
||||||
UPSTREAM_FATAL_SETTINGS_ERROR,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
|
|||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Global.Companion.objectMapper
|
import io.emeraldpay.dshackle.Global.Companion.objectMapper
|
||||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.LabelsDetector
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumArchiveBlockNumberReader
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumArchiveBlockNumberReader
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
@@ -14,10 +15,10 @@ import reactor.core.publisher.Mono
|
|||||||
class EthereumLabelsDetector(
|
class EthereumLabelsDetector(
|
||||||
private val reader: JsonRpcReader,
|
private val reader: JsonRpcReader,
|
||||||
private val chain: Chain,
|
private val chain: Chain,
|
||||||
) {
|
) : LabelsDetector {
|
||||||
private val blockNumberReader = EthereumArchiveBlockNumberReader(reader)
|
private val blockNumberReader = EthereumArchiveBlockNumberReader(reader)
|
||||||
|
|
||||||
fun detectLabels(): Flux<Pair<String, String>> {
|
override fun detectLabels(): Flux<Pair<String, String>> {
|
||||||
return Flux.merge(
|
return Flux.merge(
|
||||||
detectNodeType(),
|
detectNodeType(),
|
||||||
detectArchiveNode(),
|
detectArchiveNode(),
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2020 EmeraldPay, Inc
|
|
||||||
* 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.api.proto.BlockchainOuterClass
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
|
||||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
|
||||||
import io.emeraldpay.dshackle.upstream.DistanceExtractor
|
|
||||||
import io.emeraldpay.dshackle.upstream.DynamicMergedHead
|
|
||||||
import io.emeraldpay.dshackle.upstream.EmptyHead
|
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
|
||||||
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
|
||||||
import io.emeraldpay.dshackle.upstream.MergedHead
|
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.AggregatedPendingTxes
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.NoPendingTxes
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource
|
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.PriorityForkChoice
|
|
||||||
import io.emeraldpay.dshackle.upstream.grpc.GrpcUpstream
|
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
|
||||||
import org.springframework.cloud.sleuth.Tracer
|
|
||||||
import org.springframework.util.ConcurrentReferenceHashMap
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.core.scheduler.Scheduler
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
open class EthereumPosMultiStream(
|
|
||||||
chain: Chain,
|
|
||||||
val upstreams: MutableList<EthereumLikeUpstream>,
|
|
||||||
caches: Caches,
|
|
||||||
private val headScheduler: Scheduler,
|
|
||||||
tracer: Tracer,
|
|
||||||
) : Multistream(chain, upstreams as MutableList<Upstream>, caches) {
|
|
||||||
|
|
||||||
private var head: DynamicMergedHead = DynamicMergedHead(
|
|
||||||
PriorityForkChoice(),
|
|
||||||
"ETH Pos Multistream of ${chain.chainCode}",
|
|
||||||
headScheduler,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val reader: EthereumCachingReader = EthereumCachingReader(this, this.caches, getMethodsFactory(), tracer)
|
|
||||||
private var subscribe = EthereumEgressSubscription(this, headScheduler, NoPendingTxes())
|
|
||||||
private val filteredHeads: MutableMap<String, Head> =
|
|
||||||
ConcurrentReferenceHashMap(16, ConcurrentReferenceHashMap.ReferenceType.WEAK)
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun init() {
|
|
||||||
if (upstreams.size > 0) {
|
|
||||||
upstreams.forEach { addHead(it) }
|
|
||||||
}
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
super.start()
|
|
||||||
head.start()
|
|
||||||
onHeadUpdated(head)
|
|
||||||
reader.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stop() {
|
|
||||||
super.stop()
|
|
||||||
reader.stop()
|
|
||||||
filteredHeads.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addHead(upstream: Upstream) {
|
|
||||||
val newHead = upstream.getHead()
|
|
||||||
if (newHead is Lifecycle && !newHead.isRunning()) {
|
|
||||||
newHead.start()
|
|
||||||
}
|
|
||||||
head.addHead(upstream)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeHead(upstreamId: String) {
|
|
||||||
head.removeHead(upstreamId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isRunning(): Boolean {
|
|
||||||
return super.isRunning() || reader.isRunning()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun makeLagObserver(): HeadLagObserver =
|
|
||||||
HeadLagObserver(head, upstreams, DistanceExtractor::extractPriorityDistance, headScheduler, 6).apply {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCachingReader(): EthereumCachingReader {
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHead(): Head {
|
|
||||||
return head
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tryProxySubscribe(
|
|
||||||
matcher: Selector.Matcher,
|
|
||||||
request: BlockchainOuterClass.NativeSubscribeRequest,
|
|
||||||
): Flux<out Any>? =
|
|
||||||
upstreams.filter {
|
|
||||||
matcher.matches(it)
|
|
||||||
}.takeIf { ups ->
|
|
||||||
ups.size == 1 && ups.all { it.isGrpc() }
|
|
||||||
}?.map {
|
|
||||||
it as GrpcUpstream
|
|
||||||
}?.map {
|
|
||||||
it.proxySubscribe(request)
|
|
||||||
}?.let {
|
|
||||||
Flux.merge(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLabels(): Collection<UpstreamsConfig.Labels> {
|
|
||||||
return upstreams.flatMap { it.getLabels() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : Upstream> cast(selfType: Class<T>): T {
|
|
||||||
if (!selfType.isAssignableFrom(this.javaClass)) {
|
|
||||||
throw ClassCastException("Cannot cast ${this.javaClass} to $selfType")
|
|
||||||
}
|
|
||||||
return this as T
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLocalReader(localEnabled: Boolean): Mono<JsonRpcReader> {
|
|
||||||
return Mono.just(EthereumLocalReader(reader, getMethods(), getHead(), localEnabled))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEgressSubscription(): EthereumEgressSubscription {
|
|
||||||
return subscribe
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHead(mather: Selector.Matcher): Head =
|
|
||||||
if (mather == Selector.empty || mather == Selector.anyLabel) {
|
|
||||||
head
|
|
||||||
} else {
|
|
||||||
filteredHeads.computeIfAbsent(mather.describeInternal().intern()) { _ ->
|
|
||||||
upstreams.filter { mather.matches(it) }
|
|
||||||
.apply {
|
|
||||||
log.debug("Found $size upstreams matching [${mather.describeInternal()}]")
|
|
||||||
}
|
|
||||||
.let {
|
|
||||||
val selected = it.map { it.getHead() }
|
|
||||||
when (it.size) {
|
|
||||||
0 -> EmptyHead()
|
|
||||||
1 -> selected.first()
|
|
||||||
else -> MergedHead(
|
|
||||||
selected,
|
|
||||||
PriorityForkChoice(),
|
|
||||||
headScheduler,
|
|
||||||
"ETH head for ${it.map { it.getId() }}",
|
|
||||||
).apply {
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEnrichedHead(mather: Selector.Matcher): Head =
|
|
||||||
filteredHeads.computeIfAbsent(mather.describeInternal().intern()) { _ ->
|
|
||||||
upstreams.filter { mather.matches(it) }
|
|
||||||
.apply {
|
|
||||||
log.debug("Found $size upstreams matching [${mather.describeInternal()}]")
|
|
||||||
}.let {
|
|
||||||
val selected = it.map { source -> source.getHead() }
|
|
||||||
EnrichedMergedHead(
|
|
||||||
selected,
|
|
||||||
getHead(),
|
|
||||||
headScheduler,
|
|
||||||
object :
|
|
||||||
Reader<BlockHash, BlockContainer> {
|
|
||||||
override fun read(key: BlockHash): Mono<BlockContainer> {
|
|
||||||
return reader.blocksByHashAsCont().read(key).map { res -> res.data }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpstreamsUpdated() {
|
|
||||||
super.onUpstreamsUpdated()
|
|
||||||
|
|
||||||
val pendingTxes: PendingTxesSource = upstreams
|
|
||||||
.mapNotNull {
|
|
||||||
it.getIngressSubscription().getPendingTxes()
|
|
||||||
}.let {
|
|
||||||
if (it.isEmpty()) {
|
|
||||||
NoPendingTxes()
|
|
||||||
} else if (it.size == 1) {
|
|
||||||
it.first()
|
|
||||||
} else {
|
|
||||||
AggregatedPendingTxes(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscribe = EthereumEgressSubscription(this, headScheduler, pendingTxes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,19 +3,53 @@ package io.emeraldpay.dshackle.upstream.generic
|
|||||||
import io.emeraldpay.dshackle.BlockchainType
|
import io.emeraldpay.dshackle.BlockchainType
|
||||||
import io.emeraldpay.dshackle.BlockchainType.STARKNET
|
import io.emeraldpay.dshackle.BlockchainType.STARKNET
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
|
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.foundation.ChainOptions
|
||||||
|
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.CachingReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.EgressSubscription
|
||||||
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.LabelsDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamValidator
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific
|
import io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific
|
||||||
|
import org.apache.commons.collections4.Factory
|
||||||
|
import org.springframework.cloud.sleuth.Tracer
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.core.scheduler.Scheduler
|
||||||
|
|
||||||
|
typealias SubscriptionBuilder = (Multistream) -> EgressSubscription
|
||||||
|
typealias LocalReaderBuilder = (CachingReader, CallMethods, Head) -> Mono<JsonRpcReader>
|
||||||
|
typealias CachingReaderBuilder = (Multistream, Caches, Factory<CallMethods>) -> CachingReader
|
||||||
|
|
||||||
interface ChainSpecific {
|
interface ChainSpecific {
|
||||||
fun parseBlock(data: JsonRpcResponse, upstreamId: String): BlockContainer
|
fun parseBlock(data: JsonRpcResponse, upstreamId: String): BlockContainer
|
||||||
|
|
||||||
fun latestBlockRequest(): JsonRpcRequest
|
fun latestBlockRequest(): JsonRpcRequest
|
||||||
|
|
||||||
|
fun localReaderBuilder(cachingReader: CachingReader, methods: CallMethods, head: Head): Mono<JsonRpcReader>
|
||||||
|
|
||||||
|
fun subscriptionBuilder(headScheduler: Scheduler): (Multistream) -> EgressSubscription
|
||||||
|
|
||||||
|
fun makeCachingReaderBuilder(tracer: Tracer): CachingReaderBuilder
|
||||||
|
|
||||||
|
fun validator(chain: Chain, upstream: Upstream, options: ChainOptions.Options, config: ChainConfig): UpstreamValidator?
|
||||||
|
|
||||||
|
fun labelDetector(chain: Chain, reader: JsonRpcReader): LabelsDetector?
|
||||||
|
|
||||||
|
fun subscriptionTopics(upstream: GenericUpstream): List<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
object ChainSpecificRegistry {
|
object ChainSpecificRegistry {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
fun resolve(chain: Chain): ChainSpecific {
|
fun resolve(chain: Chain): ChainSpecific {
|
||||||
if (BlockchainType.from(chain) == STARKNET) {
|
if (BlockchainType.from(chain) == STARKNET) {
|
||||||
return StarknetChainSpecific
|
return StarknetChainSpecific
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream.generic
|
package io.emeraldpay.dshackle.upstream.generic
|
||||||
|
|
||||||
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||||
@@ -24,24 +25,42 @@ import io.emeraldpay.dshackle.upstream.CachingReader
|
|||||||
import io.emeraldpay.dshackle.upstream.DistanceExtractor
|
import io.emeraldpay.dshackle.upstream.DistanceExtractor
|
||||||
import io.emeraldpay.dshackle.upstream.DynamicMergedHead
|
import io.emeraldpay.dshackle.upstream.DynamicMergedHead
|
||||||
import io.emeraldpay.dshackle.upstream.EgressSubscription
|
import io.emeraldpay.dshackle.upstream.EgressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.EmptyEgressSubscription
|
import io.emeraldpay.dshackle.upstream.EmptyHead
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
import io.emeraldpay.dshackle.upstream.Lifecycle
|
||||||
|
import io.emeraldpay.dshackle.upstream.MergedHead
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.Selector
|
||||||
import io.emeraldpay.dshackle.upstream.Selector.Matcher
|
import io.emeraldpay.dshackle.upstream.Selector.Matcher
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.PriorityForkChoice
|
import io.emeraldpay.dshackle.upstream.forkchoice.PriorityForkChoice
|
||||||
|
import io.emeraldpay.dshackle.upstream.grpc.GrpcUpstream
|
||||||
|
import org.springframework.util.ConcurrentReferenceHashMap
|
||||||
|
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType.WEAK
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import reactor.core.scheduler.Scheduler
|
import reactor.core.scheduler.Scheduler
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
open class GenericMultistream(
|
open class GenericMultistream(
|
||||||
chain: Chain,
|
chain: Chain,
|
||||||
val upstreams: MutableList<GenericUpstream>,
|
private val upstreams: MutableList<Upstream>,
|
||||||
caches: Caches,
|
caches: Caches,
|
||||||
private val headScheduler: Scheduler,
|
private val headScheduler: Scheduler,
|
||||||
) : Multistream(chain, upstreams as MutableList<Upstream>, caches) {
|
cachingReaderBuilder: CachingReaderBuilder,
|
||||||
|
private val localReaderBuilder: LocalReaderBuilder,
|
||||||
|
private val subscriptionBuilder: SubscriptionBuilder,
|
||||||
|
) : Multistream(chain, caches) {
|
||||||
|
|
||||||
|
private val cachingReader = cachingReaderBuilder(this, caches, getMethodsFactory())
|
||||||
|
|
||||||
|
override fun getUpstreams(): MutableList<out Upstream> {
|
||||||
|
return upstreams
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addUpstreamInternal(u: Upstream) {
|
||||||
|
upstreams.add(u as GenericUpstream)
|
||||||
|
}
|
||||||
|
|
||||||
private var head: DynamicMergedHead = DynamicMergedHead(
|
private var head: DynamicMergedHead = DynamicMergedHead(
|
||||||
PriorityForkChoice(),
|
PriorityForkChoice(),
|
||||||
@@ -53,6 +72,8 @@ open class GenericMultistream(
|
|||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var subscription: EgressSubscription = subscriptionBuilder(this)
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
if (upstreams.size > 0) {
|
if (upstreams.size > 0) {
|
||||||
upstreams.forEach { addHead(it) }
|
upstreams.forEach { addHead(it) }
|
||||||
@@ -60,10 +81,20 @@ open class GenericMultistream(
|
|||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val filteredHeads: MutableMap<String, Head> =
|
||||||
|
ConcurrentReferenceHashMap(16, WEAK)
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
super.start()
|
super.start()
|
||||||
head.start()
|
head.start()
|
||||||
onHeadUpdated(head)
|
onHeadUpdated(head)
|
||||||
|
cachingReader.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
super.stop()
|
||||||
|
cachingReader.stop()
|
||||||
|
filteredHeads.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addHead(upstream: Upstream) {
|
override fun addHead(upstream: Upstream) {
|
||||||
@@ -78,17 +109,45 @@ open class GenericMultistream(
|
|||||||
head.removeHead(upstreamId)
|
head.removeHead(upstreamId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun isRunning(): Boolean {
|
||||||
|
return super.isRunning() || cachingReader.isRunning()
|
||||||
|
}
|
||||||
|
|
||||||
override fun makeLagObserver(): HeadLagObserver =
|
override fun makeLagObserver(): HeadLagObserver =
|
||||||
HeadLagObserver(head, upstreams, DistanceExtractor::extractPriorityDistance, headScheduler, 6).apply {
|
HeadLagObserver(head, upstreams, DistanceExtractor::extractPriorityDistance, headScheduler, 6).apply {
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCachingReader(): CachingReader? {
|
override fun getCachingReader(): CachingReader? {
|
||||||
return null
|
return cachingReader
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getHead(mather: Matcher): Head {
|
override fun getHead(mather: Matcher): Head {
|
||||||
return getHead()
|
if (mather == Selector.empty || mather == Selector.anyLabel) {
|
||||||
|
return head
|
||||||
|
} else {
|
||||||
|
return filteredHeads.computeIfAbsent(mather.describeInternal().intern()) { _ ->
|
||||||
|
upstreams.filter { mather.matches(it) }
|
||||||
|
.apply {
|
||||||
|
log.debug("Found $size upstreams matching [${mather.describeInternal()}]")
|
||||||
|
}
|
||||||
|
.let {
|
||||||
|
val selected = it.map { it.getHead() }
|
||||||
|
when (it.size) {
|
||||||
|
0 -> EmptyHead()
|
||||||
|
1 -> selected.first()
|
||||||
|
else -> MergedHead(
|
||||||
|
selected,
|
||||||
|
PriorityForkChoice(),
|
||||||
|
headScheduler,
|
||||||
|
"Head for ${it.map { it.getId() }}",
|
||||||
|
).apply {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getHead(): Head {
|
override fun getHead(): Head {
|
||||||
@@ -111,11 +170,32 @@ open class GenericMultistream(
|
|||||||
return this as T
|
return this as T
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLocalReader(localEnabled: Boolean): Mono<JsonRpcReader> {
|
override fun getLocalReader(): Mono<JsonRpcReader> {
|
||||||
return Mono.just(LocalReader(getMethods()))
|
return localReaderBuilder(cachingReader, getMethods(), getHead())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEgressSubscription(): EgressSubscription {
|
override fun getEgressSubscription(): EgressSubscription {
|
||||||
return EmptyEgressSubscription()
|
return subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUpstreamsUpdated() {
|
||||||
|
super.onUpstreamsUpdated()
|
||||||
|
subscription = subscriptionBuilder(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tryProxySubscribe(
|
||||||
|
matcher: Matcher,
|
||||||
|
request: BlockchainOuterClass.NativeSubscribeRequest,
|
||||||
|
): Flux<out Any>? =
|
||||||
|
upstreams.filter {
|
||||||
|
matcher.matches(it)
|
||||||
|
}.takeIf { ups ->
|
||||||
|
ups.size == 1 && ups.all { it.isGrpc() }
|
||||||
|
}?.map {
|
||||||
|
it as GrpcUpstream
|
||||||
|
}?.map {
|
||||||
|
it.proxySubscribe(request)
|
||||||
|
}?.let {
|
||||||
|
Flux.merge(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,21 +8,27 @@ import io.emeraldpay.dshackle.foundation.ChainOptions
|
|||||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent.ChangeType.UPDATED
|
|
||||||
import io.emeraldpay.dshackle.upstream.Capability
|
import io.emeraldpay.dshackle.upstream.Capability
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.IngressSubscription
|
||||||
|
import io.emeraldpay.dshackle.upstream.LabelsDetectorBuilder
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamValidator
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamValidatorBuilder
|
||||||
|
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.ConnectorFactory
|
import io.emeraldpay.dshackle.upstream.generic.connectors.ConnectorFactory
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
|
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.context.Lifecycle
|
import org.springframework.context.Lifecycle
|
||||||
import reactor.core.Disposable
|
import reactor.core.Disposable
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
import java.time.Duration
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class GenericUpstream(
|
open class GenericUpstream(
|
||||||
id: String,
|
id: String,
|
||||||
val chain: Chain,
|
val chain: Chain,
|
||||||
hash: Byte,
|
hash: Byte,
|
||||||
@@ -30,14 +36,23 @@ class GenericUpstream(
|
|||||||
role: UpstreamsConfig.UpstreamRole,
|
role: UpstreamsConfig.UpstreamRole,
|
||||||
targets: CallMethods?,
|
targets: CallMethods?,
|
||||||
private val node: QuorumForLabels.QuorumItem?,
|
private val node: QuorumForLabels.QuorumItem?,
|
||||||
val chainConfig: ChainsConfig.ChainConfig,
|
chainConfig: ChainsConfig.ChainConfig,
|
||||||
connectorFactory: ConnectorFactory,
|
connectorFactory: ConnectorFactory,
|
||||||
private val eventPublisher: ApplicationEventPublisher?,
|
private val eventPublisher: ApplicationEventPublisher?,
|
||||||
|
validatorBuilder: UpstreamValidatorBuilder,
|
||||||
|
labelsDetectorBuilder: LabelsDetectorBuilder,
|
||||||
|
private val subscriptionTopics: (GenericUpstream) -> List<String>,
|
||||||
) : DefaultUpstream(id, hash, null, UpstreamAvailability.OK, options, role, targets, node, chainConfig), Lifecycle {
|
) : DefaultUpstream(id, hash, null, UpstreamAvailability.OK, options, role, targets, node, chainConfig), Lifecycle {
|
||||||
|
|
||||||
|
private val validator: UpstreamValidator? = validatorBuilder(chain, this, getOptions(), chainConfig)
|
||||||
|
private var validatorSubscription: Disposable? = null
|
||||||
|
private var validationSettingsSubscription: Disposable? = null
|
||||||
|
|
||||||
private val hasLiveSubscriptionHead: AtomicBoolean = AtomicBoolean(false)
|
private val hasLiveSubscriptionHead: AtomicBoolean = AtomicBoolean(false)
|
||||||
private val connector: GenericConnector = connectorFactory.create(this, chain, true)
|
protected val connector: GenericConnector = connectorFactory.create(this, chain, true)
|
||||||
private var livenessSubscription: Disposable? = null
|
private var livenessSubscription: Disposable? = null
|
||||||
|
private val labelsDetector = labelsDetectorBuilder(chain, this.getIngressReader())
|
||||||
|
|
||||||
override fun getHead(): Head {
|
override fun getHead(): Head {
|
||||||
return connector.getHead()
|
return connector.getHead()
|
||||||
}
|
}
|
||||||
@@ -51,10 +66,7 @@ class GenericUpstream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getSubscriptionTopics(): List<String> {
|
override fun getSubscriptionTopics(): List<String> {
|
||||||
// should be implemented in next iterations
|
return subscriptionTopics(this)
|
||||||
// starknet doesn't have any subscriptions at all
|
|
||||||
// polkadot serves subscriptions like separate json-rpc methods
|
|
||||||
return emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// outdated, looks like applicable only for bitcoin and our ws_head trick
|
// outdated, looks like applicable only for bitcoin and our ws_head trick
|
||||||
@@ -83,19 +95,107 @@ class GenericUpstream(
|
|||||||
log.info("Configured for ${chain.chainName}")
|
log.info("Configured for ${chain.chainName}")
|
||||||
connector.start()
|
connector.start()
|
||||||
|
|
||||||
|
if (validator != null) {
|
||||||
|
val validSettingsResult = validator.validateUpstreamSettingsOnStartup()
|
||||||
|
when (validSettingsResult) {
|
||||||
|
ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR -> {
|
||||||
|
connector.stop()
|
||||||
|
log.warn("Upstream ${getId()} couldn't start, invalid upstream settings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR -> {
|
||||||
|
validateUpstreamSettings()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
upstreamStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
upstreamStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateUpstreamSettings() {
|
||||||
|
if (validator != null) {
|
||||||
|
validationSettingsSubscription = Flux.interval(
|
||||||
|
Duration.ofSeconds(10),
|
||||||
|
Duration.ofSeconds(20),
|
||||||
|
).flatMap {
|
||||||
|
validator.validateUpstreamSettings()
|
||||||
|
}.subscribe {
|
||||||
|
when (it) {
|
||||||
|
ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR -> {
|
||||||
|
connector.stop()
|
||||||
|
disposeValidationSettingsSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateUpstreamSettingsResult.UPSTREAM_VALID -> {
|
||||||
|
upstreamStart()
|
||||||
|
eventPublisher?.publishEvent(
|
||||||
|
UpstreamChangeEvent(
|
||||||
|
chain,
|
||||||
|
this,
|
||||||
|
UpstreamChangeEvent.ChangeType.ADDED,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
disposeValidationSettingsSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
log.warn("Continue validation of upstream ${getId()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun detectLabels() {
|
||||||
|
labelsDetector?.detectLabels()?.subscribe { label -> updateLabels(label) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun upstreamStart() {
|
||||||
|
if (getOptions().disableValidation) {
|
||||||
|
log.warn("Disable validation for upstream ${this.getId()}")
|
||||||
|
this.setLag(0)
|
||||||
|
this.setStatus(UpstreamAvailability.OK)
|
||||||
|
} else {
|
||||||
|
log.debug("Start validation for upstream ${this.getId()}")
|
||||||
|
validatorSubscription = validator?.start()
|
||||||
|
?.subscribe(this::setStatus)
|
||||||
|
}
|
||||||
livenessSubscription = connector.hasLiveSubscriptionHead().subscribe({
|
livenessSubscription = connector.hasLiveSubscriptionHead().subscribe({
|
||||||
hasLiveSubscriptionHead.set(it)
|
hasLiveSubscriptionHead.set(it)
|
||||||
eventPublisher?.publishEvent(UpstreamChangeEvent(chain, this, UPDATED))
|
eventPublisher?.publishEvent(UpstreamChangeEvent(chain, this, UpstreamChangeEvent.ChangeType.UPDATED))
|
||||||
}, {
|
}, {
|
||||||
log.debug("Error while checking live subscription for ${getId()}", it)
|
log.debug("Error while checking live subscription for ${getId()}", it)
|
||||||
},)
|
},)
|
||||||
|
detectLabels()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
|
validatorSubscription?.dispose()
|
||||||
|
validatorSubscription = null
|
||||||
livenessSubscription?.dispose()
|
livenessSubscription?.dispose()
|
||||||
livenessSubscription = null
|
livenessSubscription = null
|
||||||
|
disposeValidationSettingsSubscription()
|
||||||
connector.stop()
|
connector.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun disposeValidationSettingsSubscription() {
|
||||||
|
validationSettingsSubscription?.dispose()
|
||||||
|
validationSettingsSubscription = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLabels(label: Pair<String, String>) {
|
||||||
|
log.info("Detected label ${label.first} with value ${label.second} for upstream ${getId()}")
|
||||||
|
node?.labels?.let { labels ->
|
||||||
|
labels[label.first] = label.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIngressSubscription(): IngressSubscription {
|
||||||
|
return connector.getIngressSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
override fun isRunning() = connector.isRunning()
|
override fun isRunning() = connector.isRunning()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package io.emeraldpay.dshackle.upstream.generic.connectors
|
|||||||
|
|
||||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.IngressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
import io.emeraldpay.dshackle.upstream.Lifecycle
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
|
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
|
|
||||||
interface GenericConnector : Lifecycle {
|
interface GenericConnector : Lifecycle {
|
||||||
@@ -13,5 +13,5 @@ interface GenericConnector : Lifecycle {
|
|||||||
|
|
||||||
fun getIngressReader(): JsonRpcReader
|
fun getIngressReader(): JsonRpcReader
|
||||||
|
|
||||||
fun getIngressSubscription(): EthereumIngressSubscription
|
fun getIngressSubscription(): IngressSubscription
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import io.emeraldpay.dshackle.reader.JsonRpcReader
|
|||||||
import io.emeraldpay.dshackle.upstream.BlockValidator
|
import io.emeraldpay.dshackle.upstream.BlockValidator
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.IngressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
import io.emeraldpay.dshackle.upstream.Lifecycle
|
||||||
import io.emeraldpay.dshackle.upstream.MergedHead
|
import io.emeraldpay.dshackle.upstream.MergedHead
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsHead
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsHead
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.HeadLivenessValidator
|
import io.emeraldpay.dshackle.upstream.ethereum.HeadLivenessValidator
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.NoEthereumIngressSubscription
|
import io.emeraldpay.dshackle.upstream.ethereum.NoEthereumIngressSubscription
|
||||||
@@ -145,7 +145,7 @@ class GenericRpcConnector(
|
|||||||
return directReader
|
return directReader
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIngressSubscription(): EthereumIngressSubscription {
|
override fun getIngressSubscription(): IngressSubscription {
|
||||||
return NoEthereumIngressSubscription.DEFAULT
|
return NoEthereumIngressSubscription.DEFAULT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import io.emeraldpay.dshackle.reader.JsonRpcReader
|
|||||||
import io.emeraldpay.dshackle.upstream.BlockValidator
|
import io.emeraldpay.dshackle.upstream.BlockValidator
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.IngressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsHead
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWsHead
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.HeadLivenessValidator
|
import io.emeraldpay.dshackle.upstream.ethereum.HeadLivenessValidator
|
||||||
@@ -74,7 +75,7 @@ class GenericWsConnector(
|
|||||||
return reader
|
return reader
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIngressSubscription(): EthereumIngressSubscription {
|
override fun getIngressSubscription(): IngressSubscription {
|
||||||
return subscriptions
|
return subscriptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2020 EmeraldPay, Inc
|
|
||||||
* 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.upstream.grpc
|
|
||||||
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
||||||
import io.emeraldpay.api.proto.ReactorBlockchainGrpc.ReactorBlockchainStub
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
|
||||||
import io.emeraldpay.dshackle.Defaults
|
|
||||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
|
||||||
import io.emeraldpay.dshackle.data.BlockId
|
|
||||||
import io.emeraldpay.dshackle.foundation.ChainOptions
|
|
||||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
|
||||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
|
||||||
import io.emeraldpay.dshackle.upstream.BuildInfo
|
|
||||||
import io.emeraldpay.dshackle.upstream.Capability
|
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.EthereumDshackleIngressSubscription
|
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
|
||||||
import io.emeraldpay.etherjar.rpc.RpcException
|
|
||||||
import org.reactivestreams.Publisher
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.core.scheduler.Scheduler
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
import java.util.function.Function
|
|
||||||
|
|
||||||
open class EthereumGrpcUpstream(
|
|
||||||
private val parentId: String,
|
|
||||||
hash: Byte,
|
|
||||||
role: UpstreamsConfig.UpstreamRole,
|
|
||||||
private val chain: Chain,
|
|
||||||
private val remote: ReactorBlockchainStub,
|
|
||||||
client: JsonRpcGrpcClient,
|
|
||||||
overrideLabels: UpstreamsConfig.Labels?,
|
|
||||||
chainConfig: ChainsConfig.ChainConfig,
|
|
||||||
headScheduler: Scheduler,
|
|
||||||
) : EthereumLikeUpstream(
|
|
||||||
"${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}",
|
|
||||||
hash,
|
|
||||||
ChainOptions.PartialOptions.getDefaults().buildOptions(),
|
|
||||||
role,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
chainConfig,
|
|
||||||
),
|
|
||||||
GrpcUpstream,
|
|
||||||
Lifecycle {
|
|
||||||
|
|
||||||
private val blockConverter: Function<BlockchainOuterClass.ChainHead, BlockContainer> = Function { value ->
|
|
||||||
val parentHash =
|
|
||||||
if (value.parentBlockId.isBlank()) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
BlockId.from(BlockHash.from("0x" + value.parentBlockId))
|
|
||||||
}
|
|
||||||
val block = BlockContainer(
|
|
||||||
value.height,
|
|
||||||
BlockId.from(BlockHash.from("0x" + value.blockId)),
|
|
||||||
BigInteger(1, value.weight.toByteArray()),
|
|
||||||
Instant.ofEpochMilli(value.timestamp),
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
parentHash,
|
|
||||||
)
|
|
||||||
block
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSubscriptionTopics(): List<String> {
|
|
||||||
return subscriptionTopics
|
|
||||||
}
|
|
||||||
|
|
||||||
private val reloadBlock: Function<BlockContainer, Publisher<BlockContainer>> = Function { existingBlock ->
|
|
||||||
// head comes without transaction data
|
|
||||||
// need to download transactions for the block
|
|
||||||
defaultReader.read(JsonRpcRequest("eth_getBlockByHash", listOf(existingBlock.hash.toHexWithPrefix(), false)))
|
|
||||||
.flatMap(JsonRpcResponse::requireResult)
|
|
||||||
.map {
|
|
||||||
BlockContainer.fromEthereumJson(it, getId())
|
|
||||||
}
|
|
||||||
.timeout(timeout, Mono.error(TimeoutException("Timeout from upstream")))
|
|
||||||
.doOnError { t ->
|
|
||||||
setStatus(UpstreamAvailability.UNAVAILABLE)
|
|
||||||
val msg = "Failed to download block data for chain $chain on $parentId"
|
|
||||||
if (t is RpcException || t is TimeoutException) {
|
|
||||||
log.warn("$msg. Message: ${t.message}")
|
|
||||||
} else {
|
|
||||||
log.error(msg, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val upstreamStatus = GrpcUpstreamStatus(overrideLabels)
|
|
||||||
private val grpcHead = GrpcHead(
|
|
||||||
getId(),
|
|
||||||
chain,
|
|
||||||
this,
|
|
||||||
remote,
|
|
||||||
blockConverter,
|
|
||||||
reloadBlock,
|
|
||||||
MostWorkForkChoice(),
|
|
||||||
headScheduler,
|
|
||||||
)
|
|
||||||
private var capabilities: Set<Capability> = emptySet()
|
|
||||||
private val buildInfo: BuildInfo = BuildInfo()
|
|
||||||
private var subscriptionTopics = listOf<String>()
|
|
||||||
|
|
||||||
private val defaultReader: JsonRpcReader = client.getReader()
|
|
||||||
var timeout = Defaults.timeout
|
|
||||||
private val ethereumSubscriptions = EthereumDshackleIngressSubscription(chain, remote)
|
|
||||||
|
|
||||||
override fun getBlockchainApi(): ReactorBlockchainStub {
|
|
||||||
return remote
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun proxySubscribe(request: BlockchainOuterClass.NativeSubscribeRequest): Flux<out Any> =
|
|
||||||
remote.nativeSubscribe(request)
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isRunning(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBuildInfo(): BuildInfo {
|
|
||||||
return buildInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun update(conf: BlockchainOuterClass.DescribeChain, buildInfo: BlockchainOuterClass.BuildInfo): Boolean {
|
|
||||||
val newBuildInfo = BuildInfo.extract(buildInfo)
|
|
||||||
val buildInfoChanged = this.buildInfo.update(newBuildInfo)
|
|
||||||
val newCapabilities = RemoteCapabilities.extract(conf)
|
|
||||||
val upstreamStatusChanged = (upstreamStatus.update(conf) || (newCapabilities != capabilities)).also {
|
|
||||||
capabilities = newCapabilities
|
|
||||||
}
|
|
||||||
conf.status?.let { status -> onStatus(status) }
|
|
||||||
val subsChanged = (conf.supportedSubscriptionsList != subscriptionTopics).also {
|
|
||||||
subscriptionTopics = conf.supportedSubscriptionsList
|
|
||||||
}
|
|
||||||
return buildInfoChanged || upstreamStatusChanged || subsChanged
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getQuorumByLabel(): QuorumForLabels {
|
|
||||||
return upstreamStatus.getNodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
override fun getLabels(): Collection<UpstreamsConfig.Labels> {
|
|
||||||
return upstreamStatus.getLabels()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIngressSubscription(): EthereumIngressSubscription {
|
|
||||||
return ethereumSubscriptions
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getMethods(): CallMethods {
|
|
||||||
return upstreamStatus.getCallMethods()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isAvailable(): Boolean {
|
|
||||||
return super.isAvailable() && grpcHead.getCurrent() != null && getQuorumByLabel().getAll().any {
|
|
||||||
it.quorum > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHead(): Head {
|
|
||||||
return grpcHead
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIngressReader(): JsonRpcReader {
|
|
||||||
return defaultReader
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : Upstream> cast(selfType: Class<T>): T {
|
|
||||||
if (!selfType.isAssignableFrom(this.javaClass)) {
|
|
||||||
throw ClassCastException("Cannot cast ${this.javaClass} to $selfType")
|
|
||||||
}
|
|
||||||
return this as T
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCapabilities(): Set<Capability> {
|
|
||||||
return capabilities
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isGrpc(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,13 +28,11 @@ import io.emeraldpay.dshackle.reader.JsonRpcReader
|
|||||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||||
import io.emeraldpay.dshackle.upstream.BuildInfo
|
import io.emeraldpay.dshackle.upstream.BuildInfo
|
||||||
import io.emeraldpay.dshackle.upstream.Capability
|
import io.emeraldpay.dshackle.upstream.Capability
|
||||||
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
import io.emeraldpay.dshackle.upstream.Lifecycle
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.EthereumDshackleIngressSubscription
|
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.NoChoiceWithPriorityForkChoice
|
import io.emeraldpay.dshackle.upstream.forkchoice.NoChoiceWithPriorityForkChoice
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
import io.emeraldpay.etherjar.domain.BlockHash
|
||||||
@@ -45,7 +43,7 @@ import java.time.Instant
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
open class EthereumPosGrpcUpstream(
|
open class GenericGrpcUpstream(
|
||||||
parentId: String,
|
parentId: String,
|
||||||
hash: Byte,
|
hash: Byte,
|
||||||
role: UpstreamsConfig.UpstreamRole,
|
role: UpstreamsConfig.UpstreamRole,
|
||||||
@@ -56,7 +54,7 @@ open class EthereumPosGrpcUpstream(
|
|||||||
overrideLabels: UpstreamsConfig.Labels?,
|
overrideLabels: UpstreamsConfig.Labels?,
|
||||||
chainConfig: ChainsConfig.ChainConfig,
|
chainConfig: ChainsConfig.ChainConfig,
|
||||||
headScheduler: Scheduler,
|
headScheduler: Scheduler,
|
||||||
) : EthereumLikeUpstream(
|
) : DefaultUpstream(
|
||||||
"${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}",
|
"${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}",
|
||||||
hash,
|
hash,
|
||||||
ChainOptions.PartialOptions.getDefaults().buildOptions(),
|
ChainOptions.PartialOptions.getDefaults().buildOptions(),
|
||||||
@@ -103,7 +101,8 @@ open class EthereumPosGrpcUpstream(
|
|||||||
private val buildInfo: BuildInfo = BuildInfo()
|
private val buildInfo: BuildInfo = BuildInfo()
|
||||||
|
|
||||||
private val defaultReader: JsonRpcReader = client.getReader()
|
private val defaultReader: JsonRpcReader = client.getReader()
|
||||||
private val ethereumSubscriptions = EthereumDshackleIngressSubscription(chain, remote)
|
|
||||||
|
// private val ethereumSubscriptions = EthereumDshackleIngressSubscription(chain, remote)
|
||||||
private var subscriptionTopics = listOf<String>()
|
private var subscriptionTopics = listOf<String>()
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
@@ -155,10 +154,6 @@ open class EthereumPosGrpcUpstream(
|
|||||||
return upstreamStatus.getLabels()
|
return upstreamStatus.getLabels()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIngressSubscription(): EthereumIngressSubscription {
|
|
||||||
return ethereumSubscriptions
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getMethods(): CallMethods {
|
override fun getMethods(): CallMethods {
|
||||||
return upstreamStatus.getCallMethods()
|
return upstreamStatus.getCallMethods()
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ import io.emeraldpay.api.proto.Common.ChainRef.UNRECOGNIZED
|
|||||||
import io.emeraldpay.api.proto.ReactorAuthGrpc
|
import io.emeraldpay.api.proto.ReactorAuthGrpc
|
||||||
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
|
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
|
||||||
import io.emeraldpay.dshackle.BlockchainType
|
import io.emeraldpay.dshackle.BlockchainType
|
||||||
|
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Defaults
|
import io.emeraldpay.dshackle.Defaults
|
||||||
import io.emeraldpay.dshackle.FileResolver
|
import io.emeraldpay.dshackle.FileResolver
|
||||||
@@ -34,7 +35,6 @@ import io.emeraldpay.dshackle.config.ChainsConfig
|
|||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.Lifecycle
|
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
import io.emeraldpay.dshackle.upstream.grpc.auth.AuthException
|
import io.emeraldpay.dshackle.upstream.grpc.auth.AuthException
|
||||||
import io.emeraldpay.dshackle.upstream.grpc.auth.ClientAuthenticationInterceptor
|
import io.emeraldpay.dshackle.upstream.grpc.auth.ClientAuthenticationInterceptor
|
||||||
@@ -246,42 +246,37 @@ class GrpcUpstreams(
|
|||||||
return sslContext.build()
|
return sslContext.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val creators: Map<BlockchainType, (chain: Chain, client: JsonRpcGrpcClient) -> DefaultUpstream> = mapOf(
|
|
||||||
BlockchainType.EVM_POW to { chain, rpcClient ->
|
|
||||||
EthereumGrpcUpstream(
|
|
||||||
id,
|
|
||||||
hash,
|
|
||||||
role,
|
|
||||||
chain,
|
|
||||||
client,
|
|
||||||
rpcClient,
|
|
||||||
labels,
|
|
||||||
chainsConfig.resolve(chain.chainName),
|
|
||||||
headScheduler,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
BlockchainType.EVM_POS to { chain, rpcClient ->
|
|
||||||
EthereumPosGrpcUpstream(
|
|
||||||
id,
|
|
||||||
hash,
|
|
||||||
role,
|
|
||||||
chain,
|
|
||||||
client,
|
|
||||||
rpcClient,
|
|
||||||
nodeRating,
|
|
||||||
labels,
|
|
||||||
chainsConfig.resolve(chain.chainName),
|
|
||||||
headScheduler,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
BlockchainType.BITCOIN to { chain, rpcClient ->
|
|
||||||
BitcoinGrpcUpstream(id, role, chain, client, rpcClient, labels, chainsConfig.resolve(chain.chainCode), headScheduler)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getOrCreate(chain: Chain): UpstreamChangeEvent {
|
private fun getOrCreate(chain: Chain): UpstreamChangeEvent {
|
||||||
val metrics = makeMetrics(chain)
|
val metrics = makeMetrics(chain)
|
||||||
val creator = creators.getValue(BlockchainType.from(chain))
|
val creator = if (BlockchainType.from(chain) != BITCOIN) {
|
||||||
|
{ ch: Chain, rpcClient: JsonRpcGrpcClient ->
|
||||||
|
GenericGrpcUpstream(
|
||||||
|
id,
|
||||||
|
hash,
|
||||||
|
role,
|
||||||
|
ch,
|
||||||
|
client,
|
||||||
|
rpcClient,
|
||||||
|
nodeRating,
|
||||||
|
labels,
|
||||||
|
chainsConfig.resolve(chain.chainName),
|
||||||
|
headScheduler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
{ ch: Chain, rpcClient: JsonRpcGrpcClient ->
|
||||||
|
BitcoinGrpcUpstream(
|
||||||
|
id,
|
||||||
|
role,
|
||||||
|
chain,
|
||||||
|
client,
|
||||||
|
rpcClient,
|
||||||
|
labels,
|
||||||
|
chainsConfig.resolve(chain.chainCode),
|
||||||
|
headScheduler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
return getOrCreate(chain, metrics, creator)
|
return getOrCreate(chain, metrics, creator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +310,7 @@ class GrpcUpstreams(
|
|||||||
val rpcClient = JsonRpcGrpcClient(client, chain, metrics)
|
val rpcClient = JsonRpcGrpcClient(client, chain, metrics)
|
||||||
val created = creator(chain, rpcClient)
|
val created = creator(chain, rpcClient)
|
||||||
known[chain] = created
|
known[chain] = created
|
||||||
if (created is Lifecycle) created.start()
|
created.start()
|
||||||
UpstreamChangeEvent(chain, created, UpstreamChangeEvent.ChangeType.ADDED)
|
UpstreamChangeEvent(chain, created, UpstreamChangeEvent.ChangeType.ADDED)
|
||||||
} else {
|
} else {
|
||||||
UpstreamChangeEvent(chain, current, UpstreamChangeEvent.ChangeType.REVALIDATED)
|
UpstreamChangeEvent(chain, current, UpstreamChangeEvent.ChangeType.REVALIDATED)
|
||||||
|
|||||||
@@ -2,12 +2,32 @@ package io.emeraldpay.dshackle.upstream.starknet
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
import io.emeraldpay.dshackle.data.BlockId
|
import io.emeraldpay.dshackle.data.BlockId
|
||||||
|
import io.emeraldpay.dshackle.foundation.ChainOptions.Options
|
||||||
|
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.CachingReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.EgressSubscription
|
||||||
|
import io.emeraldpay.dshackle.upstream.EmptyEgressSubscription
|
||||||
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.LabelsDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.NoopCachingReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamValidator
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.CachingReaderBuilder
|
||||||
import io.emeraldpay.dshackle.upstream.generic.ChainSpecific
|
import io.emeraldpay.dshackle.upstream.generic.ChainSpecific
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.LocalReader
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
|
import org.springframework.cloud.sleuth.Tracer
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.core.scheduler.Scheduler
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@@ -30,7 +50,41 @@ object StarknetChainSpecific : ChainSpecific {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestBlockRequest(): JsonRpcRequest = JsonRpcRequest("starknet_getBlockWithTxHashes", listOf("latest"))
|
override fun latestBlockRequest(): JsonRpcRequest =
|
||||||
|
JsonRpcRequest("starknet_getBlockWithTxHashes", listOf("latest"))
|
||||||
|
|
||||||
|
override fun localReaderBuilder(
|
||||||
|
cachingReader: CachingReader,
|
||||||
|
methods: CallMethods,
|
||||||
|
head: Head,
|
||||||
|
): Mono<JsonRpcReader> {
|
||||||
|
return Mono.just(LocalReader(methods))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun subscriptionBuilder(headScheduler: Scheduler): (Multistream) -> EgressSubscription {
|
||||||
|
return { _ -> EmptyEgressSubscription }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun makeCachingReaderBuilder(tracer: Tracer): CachingReaderBuilder {
|
||||||
|
return { _, _, _ -> NoopCachingReader }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validator(
|
||||||
|
chain: Chain,
|
||||||
|
upstream: Upstream,
|
||||||
|
options: Options,
|
||||||
|
config: ChainConfig,
|
||||||
|
): UpstreamValidator? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun labelDetector(chain: Chain, reader: JsonRpcReader): LabelsDetector? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun subscriptionTopics(upstream: GenericUpstream): List<String> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|||||||
@@ -47,14 +47,4 @@ class CacheConfigReaderSpec extends Specification {
|
|||||||
//later may be not null if we support something else besides Redis
|
//later may be not null if we support something else besides Redis
|
||||||
act == null
|
act == null
|
||||||
}
|
}
|
||||||
|
|
||||||
def "Local read disabled"() {
|
|
||||||
setup:
|
|
||||||
def config = this.class.getClassLoader().getResourceAsStream("cache-local-router-disabled.yaml")
|
|
||||||
when:
|
|
||||||
def act = reader.read(config)
|
|
||||||
|
|
||||||
then:
|
|
||||||
!act.requestsCacheEnabled
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class NativeCallSpec extends Specification {
|
|||||||
|
|
||||||
ObjectMapper objectMapper = Global.objectMapper
|
ObjectMapper objectMapper = Global.objectMapper
|
||||||
|
|
||||||
def nativeCall(MultistreamHolder upstreams = null, ResponseSigner signer = null, Boolean enableCache = true, Boolean passthrough = false) {
|
def nativeCall(MultistreamHolder upstreams = null, ResponseSigner signer = null, Boolean passthrough = false) {
|
||||||
|
|
||||||
if (upstreams == null) {
|
if (upstreams == null) {
|
||||||
upstreams = Stub(MultistreamHolder)
|
upstreams = Stub(MultistreamHolder)
|
||||||
@@ -65,7 +65,6 @@ class NativeCallSpec extends Specification {
|
|||||||
|
|
||||||
def config = new MainConfig()
|
def config = new MainConfig()
|
||||||
def cacheConfig = new CacheConfig()
|
def cacheConfig = new CacheConfig()
|
||||||
cacheConfig.requestsCacheEnabled = enableCache
|
|
||||||
config.cache = cacheConfig
|
config.cache = cacheConfig
|
||||||
config.passthrough = passthrough
|
config.passthrough = passthrough
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ class NativeCallSpec extends Specification {
|
|||||||
1 * read(new JsonRpcRequest("eth_test", [])) >> Mono.just(new JsonRpcResponse("1".bytes, null))
|
1 * read(new JsonRpcRequest("eth_test", [])) >> Mono.just(new JsonRpcResponse("1".bytes, null))
|
||||||
}
|
}
|
||||||
def upstream = Mock(Multistream) {
|
def upstream = Mock(Multistream) {
|
||||||
1 * getLocalReader(_) >> Mono.just(routedApi)
|
1 * getLocalReader() >> Mono.just(routedApi)
|
||||||
}
|
}
|
||||||
|
|
||||||
def nativeCall = nativeCall()
|
def nativeCall = nativeCall()
|
||||||
@@ -98,7 +97,7 @@ class NativeCallSpec extends Specification {
|
|||||||
1 * read(new JsonRpcRequest("eth_test", [])) >> Mono.error(new RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Test message"))
|
1 * read(new JsonRpcRequest("eth_test", [])) >> Mono.error(new RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Test message"))
|
||||||
}
|
}
|
||||||
def upstream = Mock(Multistream) {
|
def upstream = Mock(Multistream) {
|
||||||
1 * getLocalReader(_) >> Mono.just(routedApi)
|
1 * getLocalReader() >> Mono.just(routedApi)
|
||||||
}
|
}
|
||||||
|
|
||||||
def nativeCall = nativeCall()
|
def nativeCall = nativeCall()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import io.emeraldpay.dshackle.Chain
|
|||||||
import io.emeraldpay.dshackle.test.MultistreamHolderMock
|
import io.emeraldpay.dshackle.test.MultistreamHolderMock
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
import io.emeraldpay.dshackle.upstream.Selector
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumEgressSubscription
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumEgressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
||||||
import io.emeraldpay.dshackle.upstream.signature.NoSigner
|
import io.emeraldpay.dshackle.upstream.signature.NoSigner
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.test.StepVerifier
|
import reactor.test.StepVerifier
|
||||||
@@ -42,7 +42,7 @@ class NativeSubscribeSpec extends Specification {
|
|||||||
def subscribe = Mock(EthereumEgressSubscription) {
|
def subscribe = Mock(EthereumEgressSubscription) {
|
||||||
1 * it.subscribe("newHeads", null, _ as Selector.AnyLabelMatcher) >> Flux.just("{}")
|
1 * it.subscribe("newHeads", null, _ as Selector.AnyLabelMatcher) >> Flux.just("{}")
|
||||||
}
|
}
|
||||||
def up = Mock(EthereumPosMultiStream) {
|
def up = Mock(GenericMultistream) {
|
||||||
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> null
|
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> null
|
||||||
1 * it.getEgressSubscription() >> subscribe
|
1 * it.getEgressSubscription() >> subscribe
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ class NativeSubscribeSpec extends Specification {
|
|||||||
ok
|
ok
|
||||||
}, _ as Selector.AnyLabelMatcher) >> Flux.just("{}")
|
}, _ as Selector.AnyLabelMatcher) >> Flux.just("{}")
|
||||||
}
|
}
|
||||||
def up = Mock(EthereumPosMultiStream) {
|
def up = Mock(GenericMultistream) {
|
||||||
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> null
|
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> null
|
||||||
1 * it.getEgressSubscription() >> subscribe
|
1 * it.getEgressSubscription() >> subscribe
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ class NativeSubscribeSpec extends Specification {
|
|||||||
.setChainValue(Chain.ETHEREUM__MAINNET.id)
|
.setChainValue(Chain.ETHEREUM__MAINNET.id)
|
||||||
.setMethod("newHeads")
|
.setMethod("newHeads")
|
||||||
.build()
|
.build()
|
||||||
def up = Mock(EthereumPosMultiStream) {
|
def up = Mock(GenericMultistream) {
|
||||||
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> Flux.just("{}")
|
1 * it.tryProxySubscribe(_ as Selector.AnyLabelMatcher, call) >> Flux.just("{}")
|
||||||
0 * it.getEgressSubscription()
|
0 * it.getEgressSubscription()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.rpc
|
package io.emeraldpay.dshackle.rpc
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||||
import io.emeraldpay.api.proto.Common
|
import io.emeraldpay.api.proto.Common
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Global
|
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
|
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
||||||
|
import io.emeraldpay.dshackle.test.GenericUpstreamMock
|
||||||
import io.emeraldpay.dshackle.test.MultistreamHolderMock
|
import io.emeraldpay.dshackle.test.MultistreamHolderMock
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
import io.emeraldpay.etherjar.domain.BlockHash
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
||||||
@@ -39,11 +37,9 @@ import java.time.Instant
|
|||||||
|
|
||||||
class StreamHeadSpec extends Specification {
|
class StreamHeadSpec extends Specification {
|
||||||
|
|
||||||
ObjectMapper objectMapper = Global.objectMapper
|
|
||||||
|
|
||||||
def "Errors on unavailable chain"() {
|
def "Errors on unavailable chain"() {
|
||||||
setup:
|
setup:
|
||||||
def upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, Stub(EthereumLikeRpcUpstream))
|
def upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, Stub(GenericUpstream))
|
||||||
def streamHead = new StreamHead(upstreams)
|
def streamHead = new StreamHead(upstreams)
|
||||||
when:
|
when:
|
||||||
def flux = streamHead.add(
|
def flux = streamHead.add(
|
||||||
@@ -80,7 +76,7 @@ class StreamHeadSpec extends Specification {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
def upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def upstream = new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
def upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstream)
|
def upstreams = new MultistreamHolderMock(Chain.ETHEREUM__MAINNET, upstream)
|
||||||
def streamHead = new StreamHead(upstreams)
|
def streamHead = new StreamHead(upstreams)
|
||||||
when:
|
when:
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2019 ETCDEV GmbH
|
|
||||||
* Copyright (c) 2020 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.test
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
|
||||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
|
||||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
|
||||||
import io.emeraldpay.dshackle.upstream.calls.*
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
|
||||||
import org.jetbrains.annotations.NotNull
|
|
||||||
import io.emeraldpay.dshackle.foundation.ChainOptions
|
|
||||||
import org.reactivestreams.Publisher
|
|
||||||
|
|
||||||
class EthereumRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
|
||||||
EthereumHeadMock ethereumHeadMock
|
|
||||||
|
|
||||||
|
|
||||||
static CallMethods allMethods() {
|
|
||||||
new AggregatedCallMethods([
|
|
||||||
new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET),
|
|
||||||
new DefaultBitcoinMethods(),
|
|
||||||
new DirectCallMethods(["eth_test"])
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumRpcUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
|
||||||
this(chain, api, allMethods())
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
|
||||||
this(id, chain, api, allMethods())
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumRpcUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
|
||||||
this("test", chain, api, methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
|
||||||
super(id, id.hashCode().byteValue(), chain,
|
|
||||||
ChainOptions.PartialOptions.getDefaults().buildOptions(),
|
|
||||||
UpstreamsConfig.UpstreamRole.PRIMARY,
|
|
||||||
methods,
|
|
||||||
new QuorumForLabels.QuorumItem(1, new UpstreamsConfig.Labels()),
|
|
||||||
new ConnectorFactoryMock(api, new EthereumHeadMock()),
|
|
||||||
ChainsConfig.ChainConfig.default(),
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
this.ethereumHeadMock = this.getHead() as EthereumHeadMock
|
|
||||||
setLag(0)
|
|
||||||
setStatus(UpstreamAvailability.OK)
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
void nextBlock(BlockContainer block) {
|
|
||||||
this.ethereumHeadMock.nextBlock(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBlocks(Publisher<BlockContainer> blocks) {
|
|
||||||
this.ethereumHeadMock.predefined = blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String toString() {
|
|
||||||
return "Upstream mock ${getId()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ package io.emeraldpay.dshackle.test
|
|||||||
|
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
|
import io.emeraldpay.dshackle.upstream.IngressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.NoEthereumIngressSubscription
|
import io.emeraldpay.dshackle.upstream.ethereum.NoEthereumIngressSubscription
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
|
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
@@ -47,7 +47,7 @@ class GenericConnectorMock implements GenericConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
EthereumIngressSubscription getIngressSubscription() {
|
IngressSubscription getIngressSubscription() {
|
||||||
return NoEthereumIngressSubscription.DEFAULT
|
return NoEthereumIngressSubscription.DEFAULT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,14 +24,14 @@ import io.emeraldpay.dshackle.reader.Reader
|
|||||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
import io.emeraldpay.dshackle.upstream.calls.*
|
import io.emeraldpay.dshackle.upstream.calls.*
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import org.jetbrains.annotations.NotNull
|
import org.jetbrains.annotations.NotNull
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
import io.emeraldpay.dshackle.foundation.ChainOptions
|
import io.emeraldpay.dshackle.foundation.ChainOptions
|
||||||
|
|
||||||
class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
class GenericUpstreamMock extends GenericUpstream {
|
||||||
EthereumHeadMock ethereumHeadMock
|
EthereumHeadMock ethereumHeadMock
|
||||||
|
|
||||||
static CallMethods allMethods() {
|
static CallMethods allMethods() {
|
||||||
@@ -42,36 +42,40 @@ class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumPosRpcUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
GenericUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||||
this(chain, api, allMethods())
|
this(chain, api, allMethods())
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, Map<String, String> labels) {
|
GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, Map<String, String> labels) {
|
||||||
this(id, chain, api, allMethods(), labels)
|
this(id, chain, api, allMethods(), labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||||
this(id, chain, api, allMethods())
|
this(id, chain, api, allMethods())
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumPosRpcUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
GenericUpstreamMock(@NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
||||||
this("test", chain, api, methods)
|
this("test", chain, api, methods)
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods) {
|
||||||
this(id, chain, api, methods, Collections.<String, String>emptyMap())
|
this(id, chain, api, methods, Collections.<String, String>emptyMap())
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods, Map<String, String> labels) {
|
GenericUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods methods, Map<String, String> labels) {
|
||||||
super(id, (byte)id.hashCode(), chain,
|
super(id,
|
||||||
|
chain,
|
||||||
|
(byte)id.hashCode(),
|
||||||
getOpts(),
|
getOpts(),
|
||||||
UpstreamsConfig.UpstreamRole.PRIMARY,
|
UpstreamsConfig.UpstreamRole.PRIMARY,
|
||||||
methods,
|
methods,
|
||||||
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(labels)),
|
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(labels)),
|
||||||
new ConnectorFactoryMock(api, new EthereumHeadMock()),
|
|
||||||
ChainConfig.default(),
|
ChainConfig.default(),
|
||||||
true,
|
new ConnectorFactoryMock(api, new EthereumHeadMock()),
|
||||||
null
|
null,
|
||||||
|
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.&validator,
|
||||||
|
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.&labelDetector,
|
||||||
|
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.&subscriptionTopics,
|
||||||
)
|
)
|
||||||
this.ethereumHeadMock = this.getHead() as EthereumHeadMock
|
this.ethereumHeadMock = this.getHead() as EthereumHeadMock
|
||||||
setLag(0)
|
setLag(0)
|
||||||
@@ -20,13 +20,13 @@ import io.emeraldpay.dshackle.BlockchainType
|
|||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
import io.emeraldpay.dshackle.upstream.*
|
import io.emeraldpay.dshackle.upstream.*
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.*
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcUpstream
|
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumCachingReader
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumCachingReader
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
|
||||||
import org.jetbrains.annotations.NotNull
|
import org.jetbrains.annotations.NotNull
|
||||||
import org.springframework.cloud.sleuth.brave.bridge.BraveTracer
|
import org.springframework.cloud.sleuth.brave.bridge.BraveTracer
|
||||||
import reactor.core.scheduler.Schedulers
|
import reactor.core.scheduler.Schedulers
|
||||||
@@ -42,13 +42,16 @@ class MultistreamHolderMock implements MultistreamHolder {
|
|||||||
|
|
||||||
Multistream addUpstream(@NotNull Chain chain, @NotNull Upstream up) {
|
Multistream addUpstream(@NotNull Chain chain, @NotNull Upstream up) {
|
||||||
if (!upstreams.containsKey(chain)) {
|
if (!upstreams.containsKey(chain)) {
|
||||||
if (BlockchainType.from(chain) == BlockchainType.EVM_POS) {
|
if (BlockchainType.from(chain) == BlockchainType.ETHEREUM) {
|
||||||
if (up instanceof EthereumPosMultiStream) {
|
if (up instanceof GenericMultistream) {
|
||||||
upstreams[chain] = up
|
upstreams[chain] = up
|
||||||
} else if (up instanceof EthereumLikeRpcUpstream) {
|
} else if (up instanceof GenericUpstream) {
|
||||||
upstreams[chain] = new EthereumPosMultiStream(
|
upstreams[chain] = new GenericMultistream(
|
||||||
chain, [up as EthereumLikeRpcUpstream], Caches.default(),
|
chain, [up as GenericUpstream], Caches.default(),
|
||||||
Schedulers.boundedElastic(), TestingCommons.tracerMock()
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unsupported upstream type ${up.class}")
|
throw new IllegalArgumentException("Unsupported upstream type ${up.class}")
|
||||||
@@ -90,21 +93,24 @@ class MultistreamHolderMock implements MultistreamHolder {
|
|||||||
return upstreams.values().toList()
|
return upstreams.values().toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
static class EthereumMultistreamMock extends EthereumPosMultiStream {
|
static class EthereumMultistreamMock extends GenericMultistream {
|
||||||
|
|
||||||
EthereumCachingReader customReader = null
|
EthereumCachingReader customReader = null
|
||||||
CallMethods customMethods = null
|
CallMethods customMethods = null
|
||||||
Head customHead = null
|
Head customHead = null
|
||||||
|
|
||||||
EthereumMultistreamMock(@NotNull Chain chain, @NotNull List<EthereumLikeRpcUpstream> upstreams, @NotNull Caches caches) {
|
EthereumMultistreamMock(@NotNull Chain chain, @NotNull List<GenericUpstream> upstreams, @NotNull Caches caches) {
|
||||||
super(chain, upstreams, caches, Schedulers.boundedElastic(), new BraveTracer(null, null, null))
|
super(chain, upstreams, caches, Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(new BraveTracer(null, null, null)),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumMultistreamMock(@NotNull Chain chain, @NotNull List<EthereumLikeRpcUpstream> upstreams) {
|
EthereumMultistreamMock(@NotNull Chain chain, @NotNull List<GenericUpstream> upstreams) {
|
||||||
this(chain, upstreams, Caches.default())
|
this(chain, upstreams, Caches.default())
|
||||||
}
|
}
|
||||||
|
|
||||||
EthereumMultistreamMock(@NotNull Chain chain, @NotNull EthereumLikeRpcUpstream upstream) {
|
EthereumMultistreamMock(@NotNull Chain chain, @NotNull GenericUpstream upstream) {
|
||||||
this(chain, [upstream])
|
this(chain, [upstream])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ import io.emeraldpay.dshackle.reader.Reader
|
|||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
import io.emeraldpay.etherjar.domain.BlockHash
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
||||||
import io.emeraldpay.etherjar.domain.TransactionId
|
import io.emeraldpay.etherjar.domain.TransactionId
|
||||||
@@ -54,44 +54,49 @@ class TestingCommons {
|
|||||||
return new TracerMock(null, null, null)
|
return new TracerMock(null, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream() {
|
static GenericUpstreamMock upstream() {
|
||||||
return new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, api())
|
return new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, api())
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream(String id) {
|
static GenericUpstreamMock upstream(String id) {
|
||||||
return new EthereumPosRpcUpstreamMock(id, Chain.ETHEREUM__MAINNET, api())
|
return new GenericUpstreamMock(id, Chain.ETHEREUM__MAINNET, api())
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream(String id, String provider) {
|
static GenericUpstreamMock upstream(String id, String provider) {
|
||||||
return new EthereumPosRpcUpstreamMock(id, Chain.ETHEREUM__MAINNET, api(), Collections.singletonMap("provider", provider))
|
return new GenericUpstreamMock(id, Chain.ETHEREUM__MAINNET, api(), Collections.singletonMap("provider", provider))
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream(String id, Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
static GenericUpstreamMock upstream(String id, Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||||
return new EthereumPosRpcUpstreamMock(id, Chain.ETHEREUM__MAINNET, api)
|
return new GenericUpstreamMock(id, Chain.ETHEREUM__MAINNET, api)
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
static GenericUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||||
return new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, api)
|
return new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, api)
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, String method) {
|
static GenericUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, String method) {
|
||||||
return upstream(api, [method])
|
return upstream(api, [method])
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, List<String> methods) {
|
static GenericUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, List<String> methods) {
|
||||||
return new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, api, new DirectCallMethods(methods))
|
return new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, api, new DirectCallMethods(methods))
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumPosRpcUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods callMethods) {
|
static GenericUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api, CallMethods callMethods) {
|
||||||
return new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, api, callMethods)
|
return new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, api, callMethods)
|
||||||
}
|
}
|
||||||
|
|
||||||
static Multistream multistream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
static Multistream multistream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||||
return multistream(upstream(api))
|
return multistream(upstream(api))
|
||||||
}
|
}
|
||||||
|
|
||||||
static Multistream multistream(EthereumPosRpcUpstreamMock up) {
|
static Multistream multistream(GenericUpstreamMock up) {
|
||||||
return new EthereumPosMultiStream(Chain.ETHEREUM__MAINNET, [up], Caches.default(), Schedulers.boundedElastic(), tracerMock()).tap {
|
return new GenericMultistream(Chain.ETHEREUM__MAINNET, [up], Caches.default(),
|
||||||
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()),
|
||||||
|
).tap {
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,11 +117,17 @@ class TestingCommons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Multistream multistreamWithoutUpstreams(Chain chain) {
|
static Multistream multistreamWithoutUpstreams(Chain chain) {
|
||||||
return new EthereumPosMultiStream(chain, [], emptyCaches().getCaches(chain), Schedulers.boundedElastic(), tracerMock())
|
return new GenericMultistream(chain, [], emptyCaches().getCaches(chain), Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
}
|
}
|
||||||
|
|
||||||
static Multistream multistreamClassicWithoutUpstreams(Chain chain) {
|
static Multistream multistreamClassicWithoutUpstreams(Chain chain) {
|
||||||
return new EthereumMultistream(chain, [], emptyCaches().getCaches(chain), Schedulers.boundedElastic(), tracerMock())
|
return new GenericMultistream(chain, [], emptyCaches().getCaches(chain), Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
}
|
}
|
||||||
|
|
||||||
static FileResolver fileResolver() {
|
static FileResolver fileResolver() {
|
||||||
|
|||||||
@@ -1,253 +0,0 @@
|
|||||||
package io.emeraldpay.dshackle.upstream
|
|
||||||
|
|
||||||
import kotlin.jvm.functions.Function1
|
|
||||||
import org.jetbrains.annotations.NotNull
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import spock.lang.Specification
|
|
||||||
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.function.Function
|
|
||||||
|
|
||||||
class AbstractChainFeesSpec extends Specification {
|
|
||||||
|
|
||||||
Function<List<String>, List<String>> extractTx = { it }
|
|
||||||
|
|
||||||
def "Iterates N last blocks"() {
|
|
||||||
setup:
|
|
||||||
def ups = Mock(Multistream) {
|
|
||||||
1 * getHead() >> Mock(Head) {
|
|
||||||
1 * it.getCurrentHeight() >> 234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def fees = new TestChainFees(5, ups, Stub(Function1))
|
|
||||||
when:
|
|
||||||
def act = fees.usingBlocks(3).collectList().block(Duration.ofSeconds(1)).toSorted()
|
|
||||||
then:
|
|
||||||
act == [232L, 233L, 234L]
|
|
||||||
}
|
|
||||||
|
|
||||||
def "Limits iteration to configured"() {
|
|
||||||
setup:
|
|
||||||
def ups = Mock(Multistream) {
|
|
||||||
1 * getHead() >> Mock(Head) {
|
|
||||||
1 * it.getCurrentHeight() >> 234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def fees = new TestChainFees(3, ups, Stub(Function1))
|
|
||||||
when:
|
|
||||||
def act = fees.usingBlocks(5).collectList().block(Duration.ofSeconds(1)).toSorted()
|
|
||||||
then:
|
|
||||||
act == [232L, 233L, 234L]
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtPos 0 for empty list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtPos<List<String>, String>(extractTx, 0)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get([])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == null
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtPos 0 for single item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtPos<List<String>, String>(extractTx, 0)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtPos 0 for many item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtPos<List<String>, String>(extractTx, 0)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_3"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtPos 1 for many item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtPos<List<String>, String>(extractTx, 1)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_2"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtPos 2 for many item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtPos<List<String>, String>(extractTx, 2)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtPos 5 for 3 item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtPos<List<String>, String>(extractTx, 5)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtBottom for empty list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtBottom<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get([])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == null
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtBottom for single item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtBottom<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtBottom for multi item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtBottom<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_3"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtTop for empty list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtTop<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get([])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == null
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtTop for single item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtTop<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtTop for multi item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtTop<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtMiddle for empty list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtMiddle<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get([])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == null
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtMiddle for single item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtMiddle<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtMiddle for 3 item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtMiddle<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_2"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtMiddle for 4 item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtMiddle<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3", "t_4"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_2" || act == "t_3"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "TxAtMiddle for 5 item list"() {
|
|
||||||
setup:
|
|
||||||
def txAt = new AbstractChainFees.TxAtMiddle<List<String>, String>(extractTx)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = txAt.get(["t_1", "t_2", "t_3", "t_4", "t_5"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
act == "t_3"
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestChainFees extends AbstractChainFees {
|
|
||||||
|
|
||||||
TestChainFees(int heightLimit, @NotNull Multistream upstreams, @NotNull Function1 extractTx) {
|
|
||||||
super(heightLimit, upstreams, extractTx)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Mono readFeesAt(long height, @NotNull TxAt selector) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Function<Flux, Mono> feeAggregation(@NotNull Mode mode) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Function getResponseBuilder() {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -17,8 +17,7 @@ package io.emeraldpay.dshackle.upstream
|
|||||||
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
||||||
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
|
import io.emeraldpay.dshackle.test.GenericUpstreamMock
|
||||||
import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock
|
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ class CurrentMultistreamHolderSpec extends Specification {
|
|||||||
def "add upstream"() {
|
def "add upstream"() {
|
||||||
setup:
|
setup:
|
||||||
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
||||||
def up = new EthereumPosRpcUpstreamMock("test", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def up = new GenericUpstreamMock("test", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
when:
|
when:
|
||||||
current.getUpstream(Chain.ETHEREUM__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up, UpstreamChangeEvent.ChangeType.ADDED))
|
current.getUpstream(Chain.ETHEREUM__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up, UpstreamChangeEvent.ChangeType.ADDED))
|
||||||
then:
|
then:
|
||||||
@@ -38,9 +37,9 @@ class CurrentMultistreamHolderSpec extends Specification {
|
|||||||
def "add multiple upstreams"() {
|
def "add multiple upstreams"() {
|
||||||
setup:
|
setup:
|
||||||
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
||||||
def up1 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def up1 = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
def up2 = new EthereumRpcUpstreamMock("test2", Chain.ETHEREUM_CLASSIC__MAINNET, TestingCommons.api())
|
def up2 = new GenericUpstreamMock("test2", Chain.ETHEREUM_CLASSIC__MAINNET, TestingCommons.api())
|
||||||
def up3 = new EthereumPosRpcUpstreamMock("test3", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def up3 = new GenericUpstreamMock("test3", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
when:
|
when:
|
||||||
current.getUpstream(Chain.ETHEREUM__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED))
|
current.getUpstream(Chain.ETHEREUM__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED))
|
||||||
current.getUpstream(Chain.ETHEREUM_CLASSIC__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM_CLASSIC__MAINNET, up2, UpstreamChangeEvent.ChangeType.ADDED))
|
current.getUpstream(Chain.ETHEREUM_CLASSIC__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM_CLASSIC__MAINNET, up2, UpstreamChangeEvent.ChangeType.ADDED))
|
||||||
@@ -55,10 +54,10 @@ class CurrentMultistreamHolderSpec extends Specification {
|
|||||||
def "remove upstream"() {
|
def "remove upstream"() {
|
||||||
setup:
|
setup:
|
||||||
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
||||||
def up1 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def up1 = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
def up2 = new EthereumRpcUpstreamMock("test2", Chain.ETHEREUM_CLASSIC__MAINNET, TestingCommons.api())
|
def up2 = new GenericUpstreamMock("test2", Chain.ETHEREUM_CLASSIC__MAINNET, TestingCommons.api())
|
||||||
def up3 = new EthereumPosRpcUpstreamMock("test3", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def up3 = new GenericUpstreamMock("test3", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
def up1_del = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def up1_del = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
when:
|
when:
|
||||||
current.getUpstream(Chain.ETHEREUM__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED))
|
current.getUpstream(Chain.ETHEREUM__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED))
|
||||||
current.getUpstream(Chain.ETHEREUM_CLASSIC__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM_CLASSIC__MAINNET, up2, UpstreamChangeEvent.ChangeType.ADDED))
|
current.getUpstream(Chain.ETHEREUM_CLASSIC__MAINNET).onUpstreamChange(new UpstreamChangeEvent(Chain.ETHEREUM_CLASSIC__MAINNET, up2, UpstreamChangeEvent.ChangeType.ADDED))
|
||||||
@@ -73,7 +72,7 @@ class CurrentMultistreamHolderSpec extends Specification {
|
|||||||
def "available after adding"() {
|
def "available after adding"() {
|
||||||
setup:
|
setup:
|
||||||
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
def current = new CurrentMultistreamHolder(TestingCommons.defaultMultistreams())
|
||||||
def up1 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
def up1 = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = current.isAvailable(Chain.ETHEREUM__MAINNET)
|
def act = current.isAvailable(Chain.ETHEREUM__MAINNET)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import io.emeraldpay.dshackle.startup.QuorumForLabels
|
|||||||
import io.emeraldpay.dshackle.test.EthereumApiStub
|
import io.emeraldpay.dshackle.test.EthereumApiStub
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory
|
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory
|
||||||
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
|
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
|
||||||
import reactor.core.scheduler.Schedulers
|
import reactor.core.scheduler.Schedulers
|
||||||
@@ -44,13 +44,15 @@ class FilteredApisSpec extends Specification {
|
|||||||
def "Verifies labels"() {
|
def "Verifies labels"() {
|
||||||
setup:
|
setup:
|
||||||
def i = 0
|
def i = 0
|
||||||
List<EthereumLikeRpcUpstream> upstreams = [
|
def cs = io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE
|
||||||
|
List<GenericUpstream> upstreams = [
|
||||||
[test: "foo"],
|
[test: "foo"],
|
||||||
[test: "bar"],
|
[test: "bar"],
|
||||||
[test: "foo", test2: "baz"],
|
[test: "foo", test2: "baz"],
|
||||||
[test: "foo"],
|
[test: "foo"],
|
||||||
[test: "baz"]
|
[test: "baz"]
|
||||||
].collect {
|
].collect {
|
||||||
|
|
||||||
def httpFactory = Mock(HttpFactory) {
|
def httpFactory = Mock(HttpFactory) {
|
||||||
create(_, _) >> Stub(JsonRpcHttpReader)
|
create(_, _) >> Stub(JsonRpcHttpReader)
|
||||||
}
|
}
|
||||||
@@ -64,18 +66,20 @@ class FilteredApisSpec extends Specification {
|
|||||||
Schedulers.boundedElastic(),
|
Schedulers.boundedElastic(),
|
||||||
Duration.ofSeconds(12)
|
Duration.ofSeconds(12)
|
||||||
)
|
)
|
||||||
new EthereumLikeRpcUpstream(
|
new GenericUpstream(
|
||||||
"test",
|
"test",
|
||||||
(byte) 123,
|
|
||||||
Chain.ETHEREUM__MAINNET,
|
Chain.ETHEREUM__MAINNET,
|
||||||
|
(byte) 123,
|
||||||
new ChainOptions.PartialOptions().buildOptions(),
|
new ChainOptions.PartialOptions().buildOptions(),
|
||||||
UpstreamsConfig.UpstreamRole.PRIMARY,
|
UpstreamsConfig.UpstreamRole.PRIMARY,
|
||||||
ethereumTargets,
|
ethereumTargets,
|
||||||
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(it)),
|
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(it)),
|
||||||
connectorFactory,
|
|
||||||
ChainsConfig.ChainConfig.default(),
|
ChainsConfig.ChainConfig.default(),
|
||||||
false,
|
connectorFactory,
|
||||||
null
|
null,
|
||||||
|
cs.&validator,
|
||||||
|
cs.&labelDetector,
|
||||||
|
cs.&subscriptionTopics,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
def matcher = new Selector.LabelMatcher("test", ["foo"])
|
def matcher = new Selector.LabelMatcher("test", ["foo"])
|
||||||
|
|||||||
@@ -25,15 +25,17 @@ import io.emeraldpay.dshackle.data.BlockContainer
|
|||||||
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
||||||
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
|
import io.emeraldpay.dshackle.test.GenericUpstreamMock
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
import io.emeraldpay.dshackle.upstream.generic.GenericUpstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
||||||
import io.emeraldpay.dshackle.upstream.grpc.EthereumPosGrpcUpstream
|
import io.emeraldpay.dshackle.upstream.grpc.GenericGrpcUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
|
import io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
import io.emeraldpay.etherjar.domain.BlockHash
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
||||||
import org.jetbrains.annotations.NotNull
|
import org.jetbrains.annotations.NotNull
|
||||||
@@ -51,9 +53,13 @@ class MultistreamSpec extends Specification {
|
|||||||
|
|
||||||
def "Aggregates methods"() {
|
def "Aggregates methods"() {
|
||||||
setup:
|
setup:
|
||||||
def up1 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
def up1 = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||||
def up2 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
def up2 = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
||||||
def aggr = new EthereumPosMultiStream(Chain.ETHEREUM__MAINNET, [up1, up2], Caches.default(), Schedulers.boundedElastic(), TestingCommons.tracerMock())
|
def aggr = new GenericMultistream(Chain.ETHEREUM__MAINNET, [up1, up2], Caches.default(),
|
||||||
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
when:
|
when:
|
||||||
aggr.onUpstreamsUpdated()
|
aggr.onUpstreamsUpdated()
|
||||||
def act = aggr.getMethods()
|
def act = aggr.getMethods()
|
||||||
@@ -184,7 +190,11 @@ class MultistreamSpec extends Specification {
|
|||||||
def up1 = TestingCommons.upstream("test-1", "internal")
|
def up1 = TestingCommons.upstream("test-1", "internal")
|
||||||
def up2 = TestingCommons.upstream("test-2", "external")
|
def up2 = TestingCommons.upstream("test-2", "external")
|
||||||
def up3 = TestingCommons.upstream("test-3", "external")
|
def up3 = TestingCommons.upstream("test-3", "external")
|
||||||
def multistream = new EthereumPosMultiStream(Chain.ETHEREUM__MAINNET, [up1, up2, up3], Caches.default(), Schedulers.boundedElastic(), TestingCommons.tracerMock())
|
def multistream = new GenericMultistream(Chain.ETHEREUM__MAINNET, [up1, up2, up3], Caches.default(),
|
||||||
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
|
|
||||||
expect:
|
expect:
|
||||||
multistream.getHead(new Selector.LabelMatcher("provider", ["internal"])).is(up1.ethereumHeadMock)
|
multistream.getHead(new Selector.LabelMatcher("provider", ["internal"])).is(up1.ethereumHeadMock)
|
||||||
@@ -205,14 +215,14 @@ class MultistreamSpec extends Specification {
|
|||||||
.setMethod("newHeads")
|
.setMethod("newHeads")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
def up1 = Mock(EthereumPosGrpcUpstream) {
|
def up1 = Mock(GenericGrpcUpstream) {
|
||||||
1 * isGrpc() >> true
|
1 * isGrpc() >> true
|
||||||
1 * getId() >> "internal"
|
_ * getId() >> "internal"
|
||||||
1 * getLabels() >> [UpstreamsConfig.Labels.fromMap(Collections.singletonMap("provider", "internal"))]
|
1 * getLabels() >> [UpstreamsConfig.Labels.fromMap(Collections.singletonMap("provider", "internal"))]
|
||||||
1 * proxySubscribe(call) >> Flux.just("{}")
|
1 * proxySubscribe(call) >> Flux.just("{}")
|
||||||
}
|
}
|
||||||
def up2 = Mock(EthereumPosGrpcUpstream) {
|
def up2 = Mock(GenericUpstream) {
|
||||||
1 * getId() >> "external"
|
_ * getId() >> "external"
|
||||||
1 * getLabels() >> [UpstreamsConfig.Labels.fromMap(Collections.singletonMap("provider", "external"))]
|
1 * getLabels() >> [UpstreamsConfig.Labels.fromMap(Collections.singletonMap("provider", "external"))]
|
||||||
}
|
}
|
||||||
def multiStream = new TestEthereumPosMultistream(Chain.ETHEREUM__MAINNET, [up1, up2], Caches.default())
|
def multiStream = new TestEthereumPosMultistream(Chain.ETHEREUM__MAINNET, [up1, up2], Caches.default())
|
||||||
@@ -235,9 +245,9 @@ class MultistreamSpec extends Specification {
|
|||||||
.setMethod("newHeads")
|
.setMethod("newHeads")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
def up2 = Mock(EthereumPosGrpcUpstream) {
|
def up2 = Mock(GenericUpstream) {
|
||||||
1 * isGrpc() >> false
|
1 * isGrpc() >> false
|
||||||
1 * getId() >> "2"
|
_ * getId() >> "2"
|
||||||
1 * getLabels() >> [UpstreamsConfig.Labels.fromMap(Collections.singletonMap("provider", "internal"))]
|
1 * getLabels() >> [UpstreamsConfig.Labels.fromMap(Collections.singletonMap("provider", "internal"))]
|
||||||
}
|
}
|
||||||
def multiStream = new TestEthereumPosMultistream(Chain.ETHEREUM__MAINNET, [up2], Caches.default())
|
def multiStream = new TestEthereumPosMultistream(Chain.ETHEREUM__MAINNET, [up2], Caches.default())
|
||||||
@@ -251,9 +261,13 @@ class MultistreamSpec extends Specification {
|
|||||||
|
|
||||||
def "Change ms methods based on upstream availability"() {
|
def "Change ms methods based on upstream availability"() {
|
||||||
setup:
|
setup:
|
||||||
def up1 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2", "eth_test3"]))
|
def up1 = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2", "eth_test3"]))
|
||||||
def up2 = new EthereumPosRpcUpstreamMock("test2", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
def up2 = new GenericUpstreamMock("test2", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||||
def ms = new EthereumPosMultiStream(Chain.ETHEREUM__MAINNET, new ArrayList<EthereumLikeRpcUpstream>(), Caches.default(), Schedulers.boundedElastic(), TestingCommons.tracerMock())
|
def ms = new GenericMultistream(Chain.ETHEREUM__MAINNET, new ArrayList<GenericMultistream>(), Caches.default(),
|
||||||
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
when:
|
when:
|
||||||
ms.onUpstreamChange(
|
ms.onUpstreamChange(
|
||||||
new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED)
|
new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up1, UpstreamChangeEvent.ChangeType.ADDED)
|
||||||
@@ -278,9 +292,13 @@ class MultistreamSpec extends Specification {
|
|||||||
|
|
||||||
def "Filter older blocks on multistream head"() {
|
def "Filter older blocks on multistream head"() {
|
||||||
setup:
|
setup:
|
||||||
def up1 = new EthereumPosRpcUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2", "eth_test3"]))
|
def up1 = new GenericUpstreamMock("test1", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2", "eth_test3"]))
|
||||||
def up2 = new EthereumPosRpcUpstreamMock("test2", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
def up2 = new GenericUpstreamMock("test2", Chain.ETHEREUM__MAINNET, TestingCommons.api(), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||||
def ms = new EthereumPosMultiStream(Chain.ETHEREUM__MAINNET, new ArrayList<EthereumLikeRpcUpstream>(), Caches.default(), Schedulers.boundedElastic(), TestingCommons.tracerMock())
|
def ms = new GenericMultistream(Chain.ETHEREUM__MAINNET, new ArrayList<GenericMultistream>(), Caches.default(),
|
||||||
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
def head1 = createBlock(250, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448401b")
|
def head1 = createBlock(250, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448401b")
|
||||||
def head2 = createBlock(270, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448402b")
|
def head2 = createBlock(270, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448402b")
|
||||||
def head3 = createBlock(100, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448412b")
|
def head3 = createBlock(100, "0x0d050c785de17179f935b9b93aca09c442964cc59972c71ae68e74731448412b")
|
||||||
@@ -311,7 +329,11 @@ class MultistreamSpec extends Specification {
|
|||||||
def up1 = TestingCommons.upstream("test-1", "internal")
|
def up1 = TestingCommons.upstream("test-1", "internal")
|
||||||
def up2 = TestingCommons.upstream("test-2", "external")
|
def up2 = TestingCommons.upstream("test-2", "external")
|
||||||
def up3 = TestingCommons.upstream("test-3", "external")
|
def up3 = TestingCommons.upstream("test-3", "external")
|
||||||
def multistream = new EthereumPosMultiStream(Chain.ETHEREUM__MAINNET, [up1, up2, up3], Caches.default(), Schedulers.boundedElastic(), TestingCommons.tracerMock())
|
def multistream = new GenericMultistream(Chain.ETHEREUM__MAINNET, [up1, up2, up3], Caches.default(),
|
||||||
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
EthereumChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
def observer = multistream.lagObserver
|
def observer = multistream.lagObserver
|
||||||
multistream.onUpstreamsUpdated()
|
multistream.onUpstreamsUpdated()
|
||||||
|
|
||||||
@@ -335,15 +357,19 @@ class MultistreamSpec extends Specification {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestEthereumPosMultistream extends EthereumPosMultiStream {
|
class TestEthereumPosMultistream extends GenericMultistream {
|
||||||
|
|
||||||
TestEthereumPosMultistream(@NotNull Chain chain, @NotNull List<EthereumLikeRpcUpstream> upstreams, @NotNull Caches caches) {
|
TestEthereumPosMultistream(@NotNull Chain chain, @NotNull List<GenericUpstream> upstreams, @NotNull Caches caches) {
|
||||||
super(chain, upstreams, caches, Schedulers.boundedElastic(), TestingCommons.tracerMock())
|
super(chain, upstreams, caches,
|
||||||
|
Schedulers.boundedElastic(),
|
||||||
|
EthereumChainSpecific.INSTANCE.makeCachingReaderBuilder(TestingCommons.tracerMock()),
|
||||||
|
EthereumChainSpecific.INSTANCE.&localReaderBuilder,
|
||||||
|
StarknetChainSpecific.INSTANCE.subscriptionBuilder(Schedulers.boundedElastic()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
Mono<Reader<JsonRpcRequest, JsonRpcResponse>> getLocalReader(boolean localEnabled) {
|
Mono<Reader<JsonRpcRequest, JsonRpcResponse>> getLocalReader() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
|
||||||
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
|
|
||||||
import io.emeraldpay.dshackle.test.ReaderMock
|
|
||||||
import io.emeraldpay.dshackle.upstream.ApiSource
|
|
||||||
import io.emeraldpay.dshackle.upstream.FilteredApis
|
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
|
||||||
import io.emeraldpay.etherjar.erc20.ERC20Token
|
|
||||||
import io.emeraldpay.etherjar.hex.HexData
|
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionCallJson
|
|
||||||
import spock.lang.Specification
|
|
||||||
|
|
||||||
import java.time.Duration
|
|
||||||
|
|
||||||
class ERC20BalanceSpec extends Specification {
|
|
||||||
|
|
||||||
def "Gets balance from upstream"() {
|
|
||||||
setup:
|
|
||||||
ReaderMock api = new ReaderMock()
|
|
||||||
.with(
|
|
||||||
new JsonRpcRequest("eth_call", [
|
|
||||||
new TransactionCallJson().tap { json ->
|
|
||||||
json.setTo(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"))
|
|
||||||
json.setData(HexData.from("0x70a0823100000000000000000000000016c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
|
||||||
},
|
|
||||||
"latest"
|
|
||||||
]),
|
|
||||||
JsonRpcResponse.ok('"0x0000000000000000000000000000000000000000000000000000001f28d72868"')
|
|
||||||
)
|
|
||||||
|
|
||||||
EthereumLikeRpcUpstream upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, api)
|
|
||||||
ERC20Token token = new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"))
|
|
||||||
ERC20Balance query = new ERC20Balance()
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = query.getBalance(upstream, token, Address.from("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
|
||||||
.block(Duration.ofSeconds(1))
|
|
||||||
|
|
||||||
then:
|
|
||||||
act.toLong() == 0x1f28d72868
|
|
||||||
}
|
|
||||||
|
|
||||||
def "Gets balance from api source"() {
|
|
||||||
setup:
|
|
||||||
ReaderMock api = new ReaderMock()
|
|
||||||
.with(
|
|
||||||
new JsonRpcRequest("eth_call", [
|
|
||||||
new TransactionCallJson().tap { json ->
|
|
||||||
json.setTo(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"))
|
|
||||||
json.setData(HexData.from("0x70a0823100000000000000000000000016c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
|
||||||
},
|
|
||||||
"latest"
|
|
||||||
]),
|
|
||||||
JsonRpcResponse.ok('"0x0000000000000000000000000000000000000000000000000000001f28d72868"')
|
|
||||||
)
|
|
||||||
|
|
||||||
EthereumLikeRpcUpstream upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, api)
|
|
||||||
ERC20Token token = new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"))
|
|
||||||
ERC20Balance query = new ERC20Balance()
|
|
||||||
|
|
||||||
ApiSource apiSource = new FilteredApis(
|
|
||||||
Chain.ETHEREUM__MAINNET, [upstream], Selector.empty
|
|
||||||
)
|
|
||||||
|
|
||||||
when:
|
|
||||||
def act = query.getBalance(apiSource, token, Address.from("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
|
||||||
.block(Duration.ofSeconds(1))
|
|
||||||
|
|
||||||
then:
|
|
||||||
act.toLong() == 0x1f28d72868
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
|||||||
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
import io.emeraldpay.etherjar.hex.Hex32
|
import io.emeraldpay.etherjar.hex.Hex32
|
||||||
@@ -28,7 +29,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "read empty logs request"() {
|
def "read empty logs request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([:])
|
def act = ethereumSubscribe.readLogsRequest([:])
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "read single address logs request"() {
|
def "read single address logs request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([
|
def act = ethereumSubscribe.readLogsRequest([
|
||||||
address: "0x829bd824b016326a401d083b33d092293333a830"
|
address: "0x829bd824b016326a401d083b33d092293333a830"
|
||||||
@@ -64,7 +65,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "ignores invalid address for logs request"() {
|
def "ignores invalid address for logs request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([
|
def act = ethereumSubscribe.readLogsRequest([
|
||||||
address: "829bd824b016326a401d083b33d092293333a830"
|
address: "829bd824b016326a401d083b33d092293333a830"
|
||||||
@@ -77,7 +78,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "read multi address logs request"() {
|
def "read multi address logs request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([
|
def act = ethereumSubscribe.readLogsRequest([
|
||||||
address: ["0x829bd824b016326a401d083b33d092293333a830", "0x401d083b33d092293333a83829bd824b016326a0"]
|
address: ["0x829bd824b016326a401d083b33d092293333a830", "0x401d083b33d092293333a83829bd824b016326a0"]
|
||||||
@@ -93,7 +94,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "read single topic logs request"() {
|
def "read single topic logs request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([
|
def act = ethereumSubscribe.readLogsRequest([
|
||||||
topics: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
topics: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
||||||
@@ -118,7 +119,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "read invalid topic for request"() {
|
def "read invalid topic for request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([
|
def act = ethereumSubscribe.readLogsRequest([
|
||||||
topics: [
|
topics: [
|
||||||
@@ -136,7 +137,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "read multi topic logs request"() {
|
def "read multi topic logs request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([
|
def act = ethereumSubscribe.readLogsRequest([
|
||||||
topics: [
|
topics: [
|
||||||
@@ -155,7 +156,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
|
|
||||||
def "read full logs request"() {
|
def "read full logs request"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe = new EthereumEgressSubscription(TestingCommons.emptyMultistream() as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
when:
|
when:
|
||||||
def act = ethereumSubscribe.readLogsRequest([
|
def act = ethereumSubscribe.readLogsRequest([
|
||||||
address: "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",
|
address: "0x298d492e8c1d909d3f63bc4a36c66c64acb3d695",
|
||||||
@@ -180,7 +181,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
def up1 = TestingCommons.upstream("test")
|
def up1 = TestingCommons.upstream("test")
|
||||||
up1.getConnectorMock().setLiveness(Flux.just(false))
|
up1.getConnectorMock().setLiveness(Flux.just(false))
|
||||||
|
|
||||||
def ethereumSubscribe1 = new EthereumEgressSubscription(TestingCommons.multistream(up1) as EthereumPosMultiStream, Schedulers.boundedElastic(), null)
|
def ethereumSubscribe1 = new EthereumEgressSubscription(TestingCommons.multistream(up1) as GenericMultistream, Schedulers.boundedElastic(), null)
|
||||||
then:
|
then:
|
||||||
ethereumSubscribe1.getAvailableTopics() == []
|
ethereumSubscribe1.getAvailableTopics() == []
|
||||||
when:
|
when:
|
||||||
@@ -188,7 +189,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
up2.getConnectorMock().setLiveness(Flux.just(true))
|
up2.getConnectorMock().setLiveness(Flux.just(true))
|
||||||
up2.stop()
|
up2.stop()
|
||||||
up2.start()
|
up2.start()
|
||||||
def ethereumSubscribe2 = new EthereumEgressSubscription(TestingCommons.multistream(up2) as EthereumPosMultiStream, Schedulers.boundedElastic(), null)
|
def ethereumSubscribe2 = new EthereumEgressSubscription(TestingCommons.multistream(up2) as GenericMultistream, Schedulers.boundedElastic(), null)
|
||||||
then:
|
then:
|
||||||
ethereumSubscribe2.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS].toSet()
|
ethereumSubscribe2.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS].toSet()
|
||||||
when:
|
when:
|
||||||
@@ -196,7 +197,7 @@ class EthereumEgressSubscriptionSpec extends Specification {
|
|||||||
up3.getConnectorMock().setLiveness(Flux.just(true))
|
up3.getConnectorMock().setLiveness(Flux.just(true))
|
||||||
up3.stop()
|
up3.stop()
|
||||||
up3.start()
|
up3.start()
|
||||||
def ethereumSubscribe3 = new EthereumEgressSubscription(TestingCommons.multistream(up3) as EthereumPosMultiStream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
def ethereumSubscribe3 = new EthereumEgressSubscription(TestingCommons.multistream(up3) as GenericMultistream, Schedulers.boundedElastic(), Stub(PendingTxesSource))
|
||||||
then:
|
then:
|
||||||
ethereumSubscribe3.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS, EthereumEgressSubscription.METHOD_PENDING_TXES].toSet()
|
ethereumSubscribe3.getAvailableTopics().toSet() == [EthereumEgressSubscription.METHOD_LOGS, EthereumEgressSubscription.METHOD_NEW_HEADS, EthereumEgressSubscription.METHOD_PENDING_TXES].toSet()
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionJsonSnapshot
|
|
||||||
import io.emeraldpay.etherjar.domain.Wei
|
|
||||||
import spock.lang.Specification
|
|
||||||
|
|
||||||
class EthereumLegacyFeesSpec extends Specification {
|
|
||||||
|
|
||||||
def "Extract fee from"() {
|
|
||||||
setup:
|
|
||||||
def block = new BlockJson()
|
|
||||||
// 0x75cc01873a9818bf426a8b23d83450bf18530a822fd4fe9e86a416a5554176a6
|
|
||||||
def tx = new TransactionJsonSnapshot().tap {
|
|
||||||
it.gasPrice = Wei.ofUnits(8, Wei.Unit.GWEI)
|
|
||||||
}
|
|
||||||
|
|
||||||
def fees = new EthereumLegacyFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
|
|
||||||
when:
|
|
||||||
def act = fees.extractFee(block, tx)
|
|
||||||
then:
|
|
||||||
act.priority == Wei.ofUnits(8, Wei.Unit.GWEI)
|
|
||||||
act.max == Wei.ofUnits(8, Wei.Unit.GWEI)
|
|
||||||
act.base == Wei.ZERO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,8 +30,7 @@ class EthereumLocalReaderSpec extends Specification {
|
|||||||
TestingCommons.tracerMock()
|
TestingCommons.tracerMock()
|
||||||
),
|
),
|
||||||
methods,
|
methods,
|
||||||
new EmptyHead(),
|
new EmptyHead()
|
||||||
true
|
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
def act = router.read(new JsonRpcRequest("eth_coinbase", [])).block(Duration.ofSeconds(1))
|
def act = router.read(new JsonRpcRequest("eth_coinbase", [])).block(Duration.ofSeconds(1))
|
||||||
@@ -50,8 +49,7 @@ class EthereumLocalReaderSpec extends Specification {
|
|||||||
TestingCommons.tracerMock()
|
TestingCommons.tracerMock()
|
||||||
),
|
),
|
||||||
methods,
|
methods,
|
||||||
new EmptyHead(),
|
new EmptyHead()
|
||||||
true
|
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
def act = router.read(new JsonRpcRequest("eth_getTransactionByHash", ["test"], 10))
|
def act = router.read(new JsonRpcRequest("eth_getTransactionByHash", ["test"], 10))
|
||||||
@@ -75,7 +73,7 @@ class EthereumLocalReaderSpec extends Specification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
||||||
def router = new EthereumLocalReader(reader, methods, head, true)
|
def router = new EthereumLocalReader(reader, methods, head)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = router.getBlockByNumber(["latest", false])
|
def act = router.getBlockByNumber(["latest", false])
|
||||||
@@ -103,7 +101,7 @@ class EthereumLocalReaderSpec extends Specification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
||||||
def router = new EthereumLocalReader(reader, methods, head, true)
|
def router = new EthereumLocalReader(reader, methods, head)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = router.getBlockByNumber(["earliest", false])
|
def act = router.getBlockByNumber(["earliest", false])
|
||||||
@@ -131,7 +129,7 @@ class EthereumLocalReaderSpec extends Specification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
||||||
def router = new EthereumLocalReader(reader, methods, head, true)
|
def router = new EthereumLocalReader(reader, methods, head)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = router.getBlockByNumber(["0x123ef", false])
|
def act = router.getBlockByNumber(["0x123ef", false])
|
||||||
@@ -155,7 +153,7 @@ class EthereumLocalReaderSpec extends Specification {
|
|||||||
_ * blocksByHeightAsCont() >> new EmptyReader<>()
|
_ * blocksByHeightAsCont() >> new EmptyReader<>()
|
||||||
}
|
}
|
||||||
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
def methods = new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
|
||||||
def router = new EthereumLocalReader(reader, methods, head, true)
|
def router = new EthereumLocalReader(reader, methods, head)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = router.getBlockByNumber(["0x0", true])
|
def act = router.getBlockByNumber(["0x0", true])
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
|
||||||
import io.emeraldpay.dshackle.upstream.ChainFees
|
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.TransactionJsonSnapshot
|
|
||||||
import io.emeraldpay.etherjar.domain.TransactionId
|
|
||||||
import io.emeraldpay.etherjar.domain.Wei
|
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import spock.lang.Specification
|
|
||||||
|
|
||||||
import java.time.Duration
|
|
||||||
|
|
||||||
class EthereumPriorityFeesSpec extends Specification {
|
|
||||||
|
|
||||||
def "Extract fee from EIP1559 tx"() {
|
|
||||||
setup:
|
|
||||||
// 13756007
|
|
||||||
def block = new BlockJson().tap {
|
|
||||||
it.baseFeePerGas = new Wei(104197355513)
|
|
||||||
}
|
|
||||||
// 0x5da50f35a51e56ecd4313417b1c30f9c088222f3f8763701effe14f3dd18b6cc
|
|
||||||
def tx = new TransactionJsonSnapshot().tap {
|
|
||||||
it.type = 2
|
|
||||||
it.maxFeePerGas = Wei.ofUnits(999, Wei.Unit.GWEI)
|
|
||||||
it.maxPriorityFeePerGas = Wei.ofUnits(5.0001, Wei.Unit.GWEI)
|
|
||||||
}
|
|
||||||
|
|
||||||
def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
|
|
||||||
when:
|
|
||||||
def act = fees.extractFee(block, tx)
|
|
||||||
then:
|
|
||||||
act.priority == Wei.ofUnits(5.0001, Wei.Unit.GWEI)
|
|
||||||
act.max == Wei.ofUnits(999, Wei.Unit.GWEI)
|
|
||||||
act.base == new Wei(104197355513)
|
|
||||||
}
|
|
||||||
|
|
||||||
def "Extract fee from legacy tx"() {
|
|
||||||
setup:
|
|
||||||
// 13756007
|
|
||||||
def block = new BlockJson().tap {
|
|
||||||
it.baseFeePerGas = new Wei(104197355513)
|
|
||||||
}
|
|
||||||
// 0x1f507982bef0f11a8304287d41f228b5f1dda1114a446ee781c3d95ef4a7b891
|
|
||||||
def tx = new TransactionJsonSnapshot().tap {
|
|
||||||
it.type = 0
|
|
||||||
// 109.564020111 Gwei
|
|
||||||
it.gasPrice = Wei.from("0x198286458f")
|
|
||||||
}
|
|
||||||
|
|
||||||
def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
|
|
||||||
when:
|
|
||||||
def act = fees.extractFee(block, tx)
|
|
||||||
then:
|
|
||||||
// as difference between base minimum and actually paid
|
|
||||||
act.priority == Wei.ofUnits(5.366664598, Wei.Unit.GWEI)
|
|
||||||
act.max == Wei.from("0x198286458f")
|
|
||||||
act.base == new Wei(104197355513)
|
|
||||||
}
|
|
||||||
|
|
||||||
def "Calculates average fee"() {
|
|
||||||
setup:
|
|
||||||
def inputs = [
|
|
||||||
new EthereumFees.EthereumFee(Wei.ofEthers(1), Wei.ofEthers(0.5), Wei.ofEthers(0.75), Wei.ofEthers(0.5)),
|
|
||||||
new EthereumFees.EthereumFee(Wei.ofEthers(0.75), Wei.ofEthers(0.5), Wei.ofEthers(0.75), Wei.ofEthers(0.5)),
|
|
||||||
new EthereumFees.EthereumFee(Wei.ofEthers(0.6), Wei.ofEthers(0.2), Wei.ofEthers(0.6), Wei.ofEthers(0.5)),
|
|
||||||
]
|
|
||||||
def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
|
|
||||||
when:
|
|
||||||
def act = Flux.fromIterable(inputs)
|
|
||||||
.transform(fees.feeAggregation(ChainFees.Mode.AVG_LAST))
|
|
||||||
.next().block(Duration.ofSeconds(1))
|
|
||||||
then:
|
|
||||||
act.priority == Wei.ofEthers(0.4) // 0.5 + 0.5 + 0.2
|
|
||||||
act.paid == Wei.ofEthers(0.7) // 0.75 + 0.75 + 0.6
|
|
||||||
}
|
|
||||||
|
|
||||||
def "Estimate"() {
|
|
||||||
setup:
|
|
||||||
def block1 = new BlockJson().tap {
|
|
||||||
it.baseFeePerGas = new Wei(92633661632)
|
|
||||||
it.transactions = [
|
|
||||||
new TransactionRefJson(TransactionId.from("0x00000000fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")),
|
|
||||||
new TransactionRefJson(TransactionId.from("0x11111111fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")),
|
|
||||||
new TransactionRefJson(TransactionId.from("0x22222222fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
def block2 = new BlockJson().tap {
|
|
||||||
it.baseFeePerGas = new Wei(104197355513)
|
|
||||||
it.transactions = [
|
|
||||||
new TransactionRefJson(TransactionId.from("0x33333333fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")),
|
|
||||||
new TransactionRefJson(TransactionId.from("0x44444444fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")),
|
|
||||||
new TransactionRefJson(TransactionId.from("0x55555555fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
def tx1 = new TransactionJsonSnapshot().tap {
|
|
||||||
it.type = 2
|
|
||||||
it.maxFeePerGas = Wei.ofUnits(150, Wei.Unit.GWEI)
|
|
||||||
it.maxPriorityFeePerGas = Wei.ofUnits(3, Wei.Unit.GWEI)
|
|
||||||
}
|
|
||||||
def tx2 = new TransactionJsonSnapshot().tap {
|
|
||||||
it.type = 2
|
|
||||||
it.maxFeePerGas = Wei.ofUnits(200, Wei.Unit.GWEI)
|
|
||||||
it.maxPriorityFeePerGas = Wei.ofUnits(6, Wei.Unit.GWEI)
|
|
||||||
}
|
|
||||||
|
|
||||||
def ups = Mock(EthereumMultistream) {
|
|
||||||
1 * getHead() >> Mock(Head) {
|
|
||||||
1 * getCurrentHeight() >> 13756007
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def reader = Mock(EthereumCachingReader) {
|
|
||||||
_ * it.blocksByHeightParsed() >> Mock(Reader) {
|
|
||||||
1 * it.read(13756006) >> Mono.just(block1)
|
|
||||||
1 * it.read(13756007) >> Mono.just(block2)
|
|
||||||
}
|
|
||||||
_ * it.txByHash() >> Mock(Reader) {
|
|
||||||
1 * it.read(TransactionId.from("0x22222222fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")) >> Mono.just(tx1)
|
|
||||||
1 * it.read(TransactionId.from("0x55555555fad596cad644b785a8a74f6580ceec9ae13c8aa174f819c0223b8c77")) >> Mono.just(tx2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def fees = new EthereumPriorityFees(ups, reader, 10)
|
|
||||||
when:
|
|
||||||
def act = fees.estimate(ChainFees.Mode.AVG_LAST, 2).block(Duration.ofSeconds(1))
|
|
||||||
|
|
||||||
then:
|
|
||||||
act.hasEthereumExtended()
|
|
||||||
act.ethereumExtended.priority == "4500000000"
|
|
||||||
act.ethereumExtended.max == "175000000000"
|
|
||||||
act.ethereumExtended.expect == "102915508572"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,6 +24,7 @@ import io.emeraldpay.dshackle.upstream.Head
|
|||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
import io.emeraldpay.etherjar.hex.HexData
|
import io.emeraldpay.etherjar.hex.HexData
|
||||||
import io.emeraldpay.etherjar.rpc.RpcResponseError
|
import io.emeraldpay.etherjar.rpc.RpcResponseError
|
||||||
@@ -37,16 +38,20 @@ import java.time.Duration
|
|||||||
import static io.emeraldpay.dshackle.Chain.ETHEREUM__MAINNET
|
import static io.emeraldpay.dshackle.Chain.ETHEREUM__MAINNET
|
||||||
import static io.emeraldpay.dshackle.Chain.OPTIMISM__MAINNET
|
import static io.emeraldpay.dshackle.Chain.OPTIMISM__MAINNET
|
||||||
import static io.emeraldpay.dshackle.upstream.UpstreamAvailability.*
|
import static io.emeraldpay.dshackle.upstream.UpstreamAvailability.*
|
||||||
import static io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
|
import static io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
|
||||||
import static io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR
|
import static io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR
|
||||||
import static io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator.ValidateUpstreamSettingsResult.UPSTREAM_VALID
|
import static io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult.UPSTREAM_VALID
|
||||||
import static java.util.Collections.emptyList
|
import static java.util.Collections.emptyList
|
||||||
|
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
|
||||||
|
|
||||||
class EthereumUpstreamValidatorSpec extends Specification {
|
class EthereumUpstreamValidatorSpec extends Specification {
|
||||||
|
|
||||||
|
def conf = ChainConfig.defaultWithContract("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||||
|
|
||||||
def "Resolve to final availability"() {
|
def "Resolve to final availability"() {
|
||||||
setup:
|
setup:
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, Stub(EthereumLikeUpstream), ChainOptions.PartialOptions.getDefaults().buildOptions())
|
|
||||||
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, Stub(Upstream), ChainOptions.PartialOptions.getDefaults().buildOptions(), conf)
|
||||||
expect:
|
expect:
|
||||||
validator.resolve(Tuples.of(sync, peers)) == exp
|
validator.resolve(Tuples.of(sync, peers)) == exp
|
||||||
where:
|
where:
|
||||||
@@ -67,8 +72,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateSyncing = false
|
it.validateSyncing = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream)
|
def up = Mock(Upstream)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -87,7 +92,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("eth_syncing", [], false)
|
answer("eth_syncing", [], false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -100,7 +105,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateSyncing = true
|
it.validateSyncing = true
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream) {
|
def up = Mock(Upstream) {
|
||||||
2 * getIngressReader() >> Mock(Reader) { reader ->
|
2 * getIngressReader() >> Mock(Reader) { reader ->
|
||||||
2 * reader.read(_) >>> [
|
2 * reader.read(_) >>> [
|
||||||
Mono.just(new JsonRpcResponse('true'.getBytes(), null)),
|
Mono.just(new JsonRpcResponse('true'.getBytes(), null)),
|
||||||
@@ -112,7 +117,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
1 * head.onSyncingNode(false)
|
1 * head.onSyncingNode(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -132,7 +137,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("eth_syncing", [], [startingBlock: 100, currentBlock: 50])
|
answer("eth_syncing", [], [startingBlock: 100, currentBlock: 50])
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -150,7 +155,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("eth_syncing", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
answer("eth_syncing", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -164,8 +169,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.validatePeers = false
|
it.validatePeers = false
|
||||||
it.minPeers = 10
|
it.minPeers = 10
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream)
|
def up = Mock(Upstream)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -180,8 +185,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.validatePeers = true
|
it.validatePeers = true
|
||||||
it.minPeers = 0
|
it.minPeers = 0
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream)
|
def up = Mock(Upstream)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -201,7 +206,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], "0x5")
|
answer("net_peerCount", [], "0x5")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -220,7 +225,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], "0xa")
|
answer("net_peerCount", [], "0xa")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -239,7 +244,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], "0xff")
|
answer("net_peerCount", [], "0xff")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -258,7 +263,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
answer("net_peerCount", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -272,7 +277,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.validateCalllimit = false
|
it.validateCalllimit = false
|
||||||
it.validateChain = false
|
it.validateChain = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream) {
|
def up = Mock(Upstream) {
|
||||||
2 * getIngressReader() >>
|
2 * getIngressReader() >>
|
||||||
Mock(Reader) {
|
Mock(Reader) {
|
||||||
1 * read(new JsonRpcRequest("eth_blockNumber", [])) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null))
|
1 * read(new JsonRpcRequest("eth_blockNumber", [])) >> Mono.just(new JsonRpcResponse('"0x10ff9be"'.getBytes(), null))
|
||||||
@@ -280,7 +285,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateUpstreamSettingsOnStartup()
|
def act = validator.validateUpstreamSettingsOnStartup()
|
||||||
@@ -293,7 +298,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateChain = false
|
it.validateChain = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeRpcUpstream) {
|
def up = Mock(Upstream) {
|
||||||
3 * getIngressReader() >> Mock(Reader) {
|
3 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
||||||
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||||
@@ -304,8 +309,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
//"0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96
|
||||||
when:
|
when:
|
||||||
def act = validator.validateUpstreamSettingsOnStartup()
|
def act = validator.validateUpstreamSettingsOnStartup()
|
||||||
then:
|
then:
|
||||||
@@ -317,7 +322,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateChain = false
|
it.validateChain = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeRpcUpstream) {
|
def up = Mock(Upstream) {
|
||||||
3 * getIngressReader() >> Mock(Reader) {
|
3 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
||||||
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||||
@@ -328,8 +333,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
// "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"
|
||||||
when:
|
when:
|
||||||
def act = validator.validateUpstreamSettingsOnStartup()
|
def act = validator.validateUpstreamSettingsOnStartup()
|
||||||
then:
|
then:
|
||||||
@@ -341,7 +346,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateCalllimit = false
|
it.validateCalllimit = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeRpcUpstream) {
|
def up = Mock(Upstream) {
|
||||||
4 * getIngressReader() >> Mock(Reader) {
|
4 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
||||||
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
||||||
@@ -350,8 +355,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
// "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"
|
||||||
when:
|
when:
|
||||||
def act = validator.validateUpstreamSettingsOnStartup()
|
def act = validator.validateUpstreamSettingsOnStartup()
|
||||||
then:
|
then:
|
||||||
@@ -363,7 +368,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateCalllimit = false
|
it.validateCalllimit = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeRpcUpstream) {
|
def up = Mock(Upstream) {
|
||||||
4 * getIngressReader() >> Mock(Reader) {
|
4 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
||||||
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
||||||
@@ -372,8 +377,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(OPTIMISM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
def validator = new EthereumUpstreamValidator(OPTIMISM__MAINNET, up, options, conf)
|
||||||
|
// "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"
|
||||||
when:
|
when:
|
||||||
def act = validator.validateUpstreamSettingsOnStartup()
|
def act = validator.validateUpstreamSettingsOnStartup()
|
||||||
then:
|
then:
|
||||||
@@ -383,7 +388,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def "Upstream is valid if all setting are valid"() {
|
def "Upstream is valid if all setting are valid"() {
|
||||||
setup:
|
setup:
|
||||||
def options = ChainOptions.PartialOptions.getDefaults().buildOptions()
|
def options = ChainOptions.PartialOptions.getDefaults().buildOptions()
|
||||||
def up = Mock(EthereumLikeRpcUpstream) {
|
def up = Mock(Upstream) {
|
||||||
5 * getIngressReader() >> Mock(Reader) {
|
5 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
||||||
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
||||||
@@ -396,8 +401,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
// "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"
|
||||||
when:
|
when:
|
||||||
def act = validator.validateUpstreamSettingsOnStartup()
|
def act = validator.validateUpstreamSettingsOnStartup()
|
||||||
then:
|
then:
|
||||||
@@ -407,7 +412,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
def "Upstream is not valid if there are errors"() {
|
def "Upstream is not valid if there are errors"() {
|
||||||
setup:
|
setup:
|
||||||
def options = ChainOptions.PartialOptions.getDefaults().buildOptions()
|
def options = ChainOptions.PartialOptions.getDefaults().buildOptions()
|
||||||
def up = Mock(EthereumLikeRpcUpstream) {
|
def up = Mock(Upstream) {
|
||||||
5 * getIngressReader() >> Mock(Reader) {
|
5 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
||||||
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
||||||
@@ -420,8 +425,8 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
Mono.just(new JsonRpcResponse('"result"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, conf)
|
||||||
|
// "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"
|
||||||
when:
|
when:
|
||||||
def act = validator.validateUpstreamSettingsOnStartup()
|
def act = validator.validateUpstreamSettingsOnStartup()
|
||||||
then:
|
then:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
|||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
|
import io.emeraldpay.dshackle.test.GenericUpstreamMock
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.BlockValidator
|
import io.emeraldpay.dshackle.upstream.BlockValidator
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
@@ -41,7 +41,7 @@ import java.time.temporal.ChronoUnit
|
|||||||
class EthereumWsHeadSpec extends Specification {
|
class EthereumWsHeadSpec extends Specification {
|
||||||
|
|
||||||
BlockHash parent = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
BlockHash parent = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
||||||
DefaultUpstream upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
DefaultUpstream upstream = new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
|
|
||||||
def "Fetch block"() {
|
def "Fetch block"() {
|
||||||
setup:
|
setup:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
|
import io.emeraldpay.dshackle.test.GenericUpstreamMock
|
||||||
import io.emeraldpay.dshackle.test.MockWSServer
|
import io.emeraldpay.dshackle.test.MockWSServer
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
@@ -15,7 +15,7 @@ import spock.lang.Specification
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
class WsConnectionImplRealSpec extends Specification {
|
class WsConnectionImplRealSpec extends Specification {
|
||||||
DefaultUpstream upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
DefaultUpstream upstream = new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
|
|
||||||
static SLEEP = 500
|
static SLEEP = 500
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ package io.emeraldpay.dshackle.upstream.ethereum
|
|||||||
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
|
import io.emeraldpay.dshackle.test.GenericUpstreamMock
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
@@ -32,7 +32,7 @@ import spock.lang.Specification
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
class WsConnectionImplSpec extends Specification {
|
class WsConnectionImplSpec extends Specification {
|
||||||
DefaultUpstream upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
DefaultUpstream upstream = new GenericUpstreamMock(Chain.ETHEREUM__MAINNET, TestingCommons.api())
|
||||||
|
|
||||||
def "Makes a RPC call"() {
|
def "Makes a RPC call"() {
|
||||||
setup:
|
setup:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import io.emeraldpay.dshackle.data.BlockId
|
|||||||
import io.emeraldpay.dshackle.data.TxId
|
import io.emeraldpay.dshackle.data.TxId
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
import io.emeraldpay.dshackle.upstream.Selector
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
import io.emeraldpay.etherjar.domain.BlockHash
|
||||||
import io.emeraldpay.etherjar.domain.TransactionId
|
import io.emeraldpay.etherjar.domain.TransactionId
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
||||||
@@ -38,7 +38,7 @@ class ConnectBlockUpdatesSpec extends Specification {
|
|||||||
|
|
||||||
def "Extracts updates"() {
|
def "Extracts updates"() {
|
||||||
setup:
|
setup:
|
||||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream), Schedulers.boundedElastic())
|
def connectBlockUpdates = new ConnectBlockUpdates(Stub(GenericMultistream), Schedulers.boundedElastic())
|
||||||
def block = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
def block = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||||
number = 13412871
|
number = 13412871
|
||||||
@@ -72,7 +72,7 @@ class ConnectBlockUpdatesSpec extends Specification {
|
|||||||
|
|
||||||
def "Produce DROP updates for replaced block"() {
|
def "Produce DROP updates for replaced block"() {
|
||||||
setup:
|
setup:
|
||||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream), Schedulers.boundedElastic())
|
def connectBlockUpdates = new ConnectBlockUpdates(Stub(GenericMultistream), Schedulers.boundedElastic())
|
||||||
def block = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
def block = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||||
number = 13412871
|
number = 13412871
|
||||||
@@ -106,7 +106,7 @@ class ConnectBlockUpdatesSpec extends Specification {
|
|||||||
|
|
||||||
def "Gets prev version if available"() {
|
def "Gets prev version if available"() {
|
||||||
setup:
|
setup:
|
||||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream), Schedulers.boundedElastic())
|
def connectBlockUpdates = new ConnectBlockUpdates(Stub(GenericMultistream), Schedulers.boundedElastic())
|
||||||
def block1 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
def block1 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||||
number = 13412871
|
number = 13412871
|
||||||
@@ -170,7 +170,7 @@ class ConnectBlockUpdatesSpec extends Specification {
|
|||||||
|
|
||||||
def "Marks old txes as dropped before producing a new version of same block"() {
|
def "Marks old txes as dropped before producing a new version of same block"() {
|
||||||
setup:
|
setup:
|
||||||
def connectBlockUpdates = new ConnectBlockUpdates(Stub(EthereumMultistream), Schedulers.boundedElastic())
|
def connectBlockUpdates = new ConnectBlockUpdates(Stub(GenericMultistream), Schedulers.boundedElastic())
|
||||||
def block1 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
def block1 = BlockContainer.from(new BlockJson<TransactionRefJson>().tap {
|
||||||
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
hash = BlockHash.from("0xe5be2159b2b7daf6b126babdcbaa349da668b92d6b8c7db1350fd527fec4885c")
|
||||||
number = 13412871
|
number = 13412871
|
||||||
@@ -232,7 +232,7 @@ class ConnectBlockUpdatesSpec extends Specification {
|
|||||||
def head = Mock(Head) {
|
def head = Mock(Head) {
|
||||||
1 * getFlux() >> Flux.never()
|
1 * getFlux() >> Flux.never()
|
||||||
}
|
}
|
||||||
def up = Mock(EthereumMultistream) {
|
def up = Mock(GenericMultistream) {
|
||||||
1 * getEnrichedHead(Selector.empty) >> head
|
1 * getEnrichedHead(Selector.empty) >> head
|
||||||
}
|
}
|
||||||
def connectBlockUpdates = new ConnectBlockUpdates(up, Schedulers.boundedElastic())
|
def connectBlockUpdates = new ConnectBlockUpdates(up, Schedulers.boundedElastic())
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage
|
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
import io.emeraldpay.etherjar.domain.BlockHash
|
||||||
@@ -91,7 +90,7 @@ class ConnectLogsSpec extends Specification {
|
|||||||
|
|
||||||
def "Filter is empty"() {
|
def "Filter is empty"() {
|
||||||
setup:
|
setup:
|
||||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic())
|
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream(), Schedulers.boundedElastic())
|
||||||
when:
|
when:
|
||||||
def input = Flux.fromIterable([
|
def input = Flux.fromIterable([
|
||||||
log1, log2, log3, log4
|
log1, log2, log3, log4
|
||||||
@@ -109,7 +108,7 @@ class ConnectLogsSpec extends Specification {
|
|||||||
|
|
||||||
def "Filter by address"() {
|
def "Filter by address"() {
|
||||||
setup:
|
setup:
|
||||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic())
|
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream(), Schedulers.boundedElastic())
|
||||||
when:
|
when:
|
||||||
def input = Flux.fromIterable([
|
def input = Flux.fromIterable([
|
||||||
log1, log2
|
log1, log2
|
||||||
@@ -124,7 +123,7 @@ class ConnectLogsSpec extends Specification {
|
|||||||
|
|
||||||
def "Filter by topic"() {
|
def "Filter by topic"() {
|
||||||
setup:
|
setup:
|
||||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic())
|
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream(), Schedulers.boundedElastic())
|
||||||
when:
|
when:
|
||||||
def input = Flux.fromIterable([
|
def input = Flux.fromIterable([
|
||||||
log1, log2, log3, log4
|
log1, log2, log3, log4
|
||||||
@@ -141,7 +140,7 @@ class ConnectLogsSpec extends Specification {
|
|||||||
|
|
||||||
def "Filter by address and topic"() {
|
def "Filter by address and topic"() {
|
||||||
setup:
|
setup:
|
||||||
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream() as EthereumPosMultiStream, Schedulers.boundedElastic())
|
def connectLogs = new ConnectLogs(TestingCommons.emptyMultistream(), Schedulers.boundedElastic())
|
||||||
when:
|
when:
|
||||||
def input = Flux.fromIterable([
|
def input = Flux.fromIterable([
|
||||||
log1, log2, log3, log4
|
log1, log2, log3, log4
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package io.emeraldpay.dshackle.upstream.ethereum.subscribe
|
|||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
import io.emeraldpay.dshackle.upstream.Selector
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
import io.emeraldpay.dshackle.upstream.generic.GenericMultistream
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.scheduler.Schedulers
|
import reactor.core.scheduler.Schedulers
|
||||||
import reactor.test.StepVerifier
|
import reactor.test.StepVerifier
|
||||||
@@ -18,7 +18,7 @@ class ConnectNewHeadsSpec extends Specification {
|
|||||||
TestingCommons.blockForEthereum(100)
|
TestingCommons.blockForEthereum(100)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
def up = Mock(EthereumMultistream) {
|
def up = Mock(GenericMultistream) {
|
||||||
1 * getHead(Selector.empty) >> head
|
1 * getHead(Selector.empty) >> head
|
||||||
}
|
}
|
||||||
ConnectNewHeads connectNewHeads = new ConnectNewHeads(up, Schedulers.boundedElastic())
|
ConnectNewHeads connectNewHeads = new ConnectNewHeads(up, Schedulers.boundedElastic())
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2019 ETCDEV GmbH
|
|
||||||
* Copyright (c) 2020 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.grpc
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import io.emeraldpay.api.proto.BlockchainGrpc
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
||||||
import io.emeraldpay.api.proto.Common
|
|
||||||
import io.emeraldpay.dshackle.Chain
|
|
||||||
import io.emeraldpay.dshackle.Global
|
|
||||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
||||||
import io.emeraldpay.dshackle.data.BlockId
|
|
||||||
import io.emeraldpay.dshackle.test.MockGrpcServer
|
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
|
||||||
import io.emeraldpay.dshackle.upstream.BuildInfo
|
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics
|
|
||||||
import io.emeraldpay.etherjar.domain.BlockHash
|
|
||||||
import io.grpc.stub.StreamObserver
|
|
||||||
import io.micrometer.core.instrument.Counter
|
|
||||||
import io.micrometer.core.instrument.Timer
|
|
||||||
import reactor.core.scheduler.Schedulers
|
|
||||||
import spock.lang.Specification
|
|
||||||
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
|
|
||||||
class EthereumGrpcUpstreamSpec extends Specification {
|
|
||||||
|
|
||||||
MockGrpcServer mockServer = new MockGrpcServer()
|
|
||||||
ObjectMapper objectMapper = Global.objectMapper
|
|
||||||
RpcMetrics metrics = new RpcMetrics(
|
|
||||||
Timer.builder("test1").register(TestingCommons.meterRegistry),
|
|
||||||
Counter.builder("test2").register(TestingCommons.meterRegistry)
|
|
||||||
)
|
|
||||||
BlockHash parent = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
|
||||||
|
|
||||||
def hash = (byte)123
|
|
||||||
def buildInfo = new BuildInfo("v0.0.1-test")
|
|
||||||
|
|
||||||
def "Subscribe to head"() {
|
|
||||||
setup:
|
|
||||||
def callData = [:]
|
|
||||||
def chain = Chain.ETHEREUM__MAINNET
|
|
||||||
def api = TestingCommons.api()
|
|
||||||
def block1 = new BlockJson().with {
|
|
||||||
it.number = 650246
|
|
||||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
|
||||||
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
|
|
||||||
it.timestamp = Instant.now()
|
|
||||||
it.parentHash = parent
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
|
|
||||||
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
|
|
||||||
@Override
|
|
||||||
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
|
|
||||||
api.nativeCall(request, responseObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
|
|
||||||
callData.chain = request.getTypeValue()
|
|
||||||
responseObserver.onNext(
|
|
||||||
BlockchainOuterClass.ChainHead.newBuilder()
|
|
||||||
.setBlockId(block1.hash.toHex().substring(2))
|
|
||||||
.setHeight(block1.number)
|
|
||||||
.setParentBlockId(parent.toHex().substring(2))
|
|
||||||
.setWeight(ByteString.copyFrom(block1.totalDifficulty.toByteArray()))
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null, ChainsConfig.ChainConfig.default(), Schedulers.boundedElastic())
|
|
||||||
upstream.setLag(0)
|
|
||||||
upstream.update(
|
|
||||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
|
||||||
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
|
|
||||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
|
||||||
.build(),
|
|
||||||
BlockchainOuterClass.BuildInfo.newBuilder()
|
|
||||||
.setVersion(buildInfo.version)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
when:
|
|
||||||
new Thread({ Thread.sleep(50); upstream.head.start() }).start()
|
|
||||||
def h = upstream.head.getFlux().next().block(Duration.ofSeconds(1))
|
|
||||||
then:
|
|
||||||
callData.chain == Chain.ETHEREUM__MAINNET.id
|
|
||||||
upstream.status == UpstreamAvailability.OK
|
|
||||||
upstream.getBuildInfo() == buildInfo
|
|
||||||
h.hash == BlockId.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
|
||||||
}
|
|
||||||
|
|
||||||
def "Follows difficulty, ignores less difficult"() {
|
|
||||||
setup:
|
|
||||||
def api = TestingCommons.api()
|
|
||||||
def block1 = new BlockJson().with {
|
|
||||||
it.number = 650246
|
|
||||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
|
||||||
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
|
|
||||||
it.timestamp = Instant.now()
|
|
||||||
it.parentHash = parent
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
def block2 = new BlockJson().with {
|
|
||||||
it.number = 650247
|
|
||||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
|
|
||||||
it.totalDifficulty = new BigInteger("35bbde5595de6455", 16)
|
|
||||||
it.timestamp = Instant.now()
|
|
||||||
it.parentHash = parent
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
|
|
||||||
api.answer("eth_getBlockByHash", [block2.hash.toHex(), false], block2)
|
|
||||||
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
|
|
||||||
@Override
|
|
||||||
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
|
|
||||||
api.nativeCall(request, responseObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
|
|
||||||
new Thread({
|
|
||||||
responseObserver.onNext(
|
|
||||||
BlockchainOuterClass.ChainHead.newBuilder()
|
|
||||||
.setBlockId(block1.hash.toHex().substring(2))
|
|
||||||
.setHeight(block1.number)
|
|
||||||
.setParentBlockId(parent.toHex().substring(2))
|
|
||||||
.setWeight(ByteString.copyFrom(block1.totalDifficulty.toByteArray()))
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
Thread.sleep(100)
|
|
||||||
responseObserver.onNext(
|
|
||||||
BlockchainOuterClass.ChainHead.newBuilder()
|
|
||||||
.setBlockId(block2.hash.toHex().substring(2))
|
|
||||||
.setHeight(block2.number)
|
|
||||||
.setParentBlockId(parent.toHex().substring(2))
|
|
||||||
.setWeight(ByteString.copyFrom(block2.totalDifficulty.toByteArray()))
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}).start()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, Chain.ETHEREUM__MAINNET, client, new JsonRpcGrpcClient(client, Chain.ETHEREUM__MAINNET, metrics), null, ChainsConfig.ChainConfig.default(), Schedulers.boundedElastic())
|
|
||||||
upstream.setLag(0)
|
|
||||||
upstream.update(
|
|
||||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
|
||||||
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
|
|
||||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
|
||||||
.build(),
|
|
||||||
BlockchainOuterClass.BuildInfo.newBuilder()
|
|
||||||
.setVersion(buildInfo.version)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
when:
|
|
||||||
new Thread({ Thread.sleep(50); upstream.head.start() }).start()
|
|
||||||
def h = upstream.head.getFlux().take(Duration.ofSeconds(1)).last().block(Duration.ofSeconds(2))
|
|
||||||
then:
|
|
||||||
upstream.status == UpstreamAvailability.OK
|
|
||||||
upstream.getBuildInfo() == buildInfo
|
|
||||||
h.hash == BlockId.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
|
||||||
h.height == 650246
|
|
||||||
}
|
|
||||||
|
|
||||||
def "Follows difficulty"() {
|
|
||||||
setup:
|
|
||||||
def callData = [:]
|
|
||||||
def finished = new CompletableFuture<Boolean>()
|
|
||||||
def chain = Chain.ETHEREUM__MAINNET
|
|
||||||
def api = TestingCommons.api()
|
|
||||||
def block1 = new BlockJson().with {
|
|
||||||
it.number = 650246
|
|
||||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
|
||||||
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
|
|
||||||
it.timestamp = Instant.now()
|
|
||||||
it.parentHash = parent
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
def block2 = new BlockJson().with {
|
|
||||||
it.number = 650247
|
|
||||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
|
|
||||||
it.totalDifficulty = new BigInteger("35bbde5595de6457", 16)
|
|
||||||
it.timestamp = Instant.now()
|
|
||||||
it.parentHash = parent
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
|
|
||||||
api.answer("eth_getBlockByHash", [block2.hash.toHex(), false], block2)
|
|
||||||
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
|
|
||||||
@Override
|
|
||||||
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
|
|
||||||
api.nativeCall(request, responseObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
|
|
||||||
responseObserver.onNext(
|
|
||||||
BlockchainOuterClass.ChainHead.newBuilder()
|
|
||||||
.setBlockId(block1.hash.toHex().substring(2))
|
|
||||||
.setHeight(block1.number)
|
|
||||||
.setParentBlockId(parent.toHex().substring(2))
|
|
||||||
.setWeight(ByteString.copyFrom(block1.totalDifficulty.toByteArray()))
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
responseObserver.onNext(
|
|
||||||
BlockchainOuterClass.ChainHead.newBuilder()
|
|
||||||
.setBlockId(block2.hash.toHex().substring(2))
|
|
||||||
.setHeight(block2.number)
|
|
||||||
.setParentBlockId(parent.toHex().substring(2))
|
|
||||||
.setWeight(ByteString.copyFrom(block2.totalDifficulty.toByteArray()))
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
finished.complete(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null, ChainsConfig.ChainConfig.default(), Schedulers.boundedElastic())
|
|
||||||
upstream.setLag(0)
|
|
||||||
upstream.update(
|
|
||||||
BlockchainOuterClass.DescribeChain.newBuilder()
|
|
||||||
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
|
|
||||||
.addAllSupportedMethods(["eth_getBlockByHash"])
|
|
||||||
.build(),
|
|
||||||
BlockchainOuterClass.BuildInfo.newBuilder()
|
|
||||||
.setVersion(buildInfo.version)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
when:
|
|
||||||
new Thread({ Thread.sleep(50); upstream.head.start() }).start()
|
|
||||||
finished.get()
|
|
||||||
def h = upstream.head.getFlux().take(Duration.ofSeconds(1)).last().block(Duration.ofSeconds(2))
|
|
||||||
then:
|
|
||||||
upstream.status == UpstreamAvailability.OK
|
|
||||||
upstream.getBuildInfo() == buildInfo
|
|
||||||
h.hash == BlockId.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
|
|
||||||
h.height == 650247
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlockchainOuterClass.DescribeChain describe(List<String> methods) {
|
|
||||||
return BlockchainOuterClass.DescribeChain.newBuilder()
|
|
||||||
.setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId))
|
|
||||||
.addAllSupportedMethods(methods)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user