diff --git a/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt b/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt index f7aaa678..67995fcd 100644 --- a/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt +++ b/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt @@ -115,7 +115,8 @@ class ChainsConfigReader( ?: throw IllegalArgumentException("undefined code for $blockchain") val grpcId = getValueAsInt(node, "grpcId") ?: throw IllegalArgumentException("undefined code for $blockchain") - val netVersion = getValueAsLong(node, "net-version")?.toBigInteger() ?: BigInteger(chainId.drop(2), 16) + val netVersion = getValueAsLong(node, "net-version")?.toBigInteger() ?: + if (chainId.startsWith("0x")) BigInteger(chainId.drop(2), 16) else BigInteger.ZERO val shortNames = getListOfString(node, "short-names") ?: throw IllegalArgumentException("undefined shortnames for $blockchain") val type = getValueAsString(node, "type") diff --git a/foundation/src/main/resources/chains.yaml b/foundation/src/main/resources/chains.yaml index 578cc2b0..07a5b26e 100644 --- a/foundation/src/main/resources/chains.yaml +++ b/foundation/src/main/resources/chains.yaml @@ -35,6 +35,7 @@ chain-settings: settings: expected-block-time: 12s mev-critical: true + allow-pruning-requirement: true lags: syncing: 6 lagging: 1 @@ -76,6 +77,7 @@ chain-settings: type: eth settings: expected-block-time: 3s + allow-pruning-requirement: true options: validate-peers: false lags: @@ -101,6 +103,7 @@ chain-settings: settings: mev-critical: true expected-block-time: 2.7s + allow-pruning-requirement: true lags: syncing: 20 lagging: 10 @@ -131,6 +134,7 @@ chain-settings: type: eth settings: expected-block-time: 260ms + allow-pruning-requirement: true options: validate-peers: false lags: @@ -155,6 +159,7 @@ chain-settings: type: eth settings: expected-block-time: 2s + allow-pruning-requirement: true options: validate-peers: false lags: @@ -179,6 +184,7 @@ chain-settings: type: eth settings: expected-block-time: 3s + allow-pruning-requirement: true mev-critical: true lags: syncing: 20 @@ -206,6 +212,7 @@ chain-settings: type: eth settings: expected-block-time: 2.7s + allow-pruning-requirement: true options: disable-validation: true lags: @@ -232,6 +239,7 @@ chain-settings: type: eth settings: expected-block-time: 260ms + allow-pruning-requirement: true options: disable-validation: true lags: @@ -249,6 +257,7 @@ chain-settings: type: eth settings: expected-block-time: 5s + allow-pruning-requirement: true lags: syncing: 70 lagging: 50 @@ -273,6 +282,7 @@ chain-settings: type: eth settings: expected-block-time: 2s + allow-pruning-requirement: true options: validate-peers: false lags: @@ -297,6 +307,7 @@ chain-settings: type: eth settings: expected-block-time: 12s + allow-pruning-requirement: true lags: syncing: 6 lagging: 1 @@ -319,6 +330,7 @@ chain-settings: type: eth settings: expected-block-time: 6s + allow-pruning-requirement: true options: validate-peers: false lags: @@ -343,6 +355,7 @@ chain-settings: type: eth settings: expected-block-time: 2s + allow-pruning-requirement: true options: validate-peers: false validate-syncing: false @@ -392,6 +405,7 @@ chain-settings: type: eth settings: expected-block-time: 500ms + allow-pruning-requirement: true options: validate-peers: false lags: @@ -416,6 +430,7 @@ chain-settings: type: eth settings: expected-block-time: 1s + allow-pruning-requirement: true options: validate-peers: false lags: @@ -442,6 +457,7 @@ chain-settings: type: eth settings: expected-block-time: 5s + allow-pruning-requirement: true lags: syncing: 10 lagging: 5 @@ -464,6 +480,7 @@ chain-settings: type: eth settings: expected-block-time: 12s + allow-pruning-requirement: true lags: syncing: 6 lagging: 1 @@ -486,6 +503,7 @@ chain-settings: type: eth settings: expected-block-time: 12s + allow-pruning-requirement: true lags: syncing: 6 lagging: 1 @@ -554,6 +572,7 @@ chain-settings: options: validate-peers: false expected-block-time: 30s + allow-pruning-requirement: true lags: syncing: 40 lagging: 20 @@ -600,6 +619,7 @@ chain-settings: type: eth settings: expected-block-time: 6s + allow-pruning-requirement: true options: validate-peers: false lags: @@ -613,8 +633,7 @@ chain-settings: short-names: [kava] chain-id: 0x8ae grpcId: 1025 - gas-price-condition: - - eq 1000000000 + gas-price-condition: eq 1000000000 - id: Testnet priority: 10 code: KAVA_TESTNET @@ -683,13 +702,13 @@ chain-settings: priority: 1 code: VARA_MAINNET short-names: [vara] - chain-id: 0x0 + chain-id: Vara Network grpcId: 1027 - id: Testnet priority: 1 code: VARA_TESTMET short-names: [vara-testnet] - chain-id: 0x0 + chain-id: Vara Network Testnet grpcId: 10036 - id: solana label: Solana @@ -773,6 +792,7 @@ chain-settings: type: eth settings: expected-block-time: 5s + allow-pruning-requirement: true lags: syncing: 20 lagging: 10 @@ -835,6 +855,7 @@ chain-settings: type: eth settings: expected-block-time: 10s + allow-pruning-requirement: true lags: syncing: 10 lagging: 5 @@ -933,6 +954,7 @@ chain-settings: type: eth settings: expected-block-time: 2s + allow-pruning-requirement: true options: validate-peers: false lags: @@ -1022,6 +1044,7 @@ chain-settings: type: eth settings: expected-block-time: 2s + allow-pruning-requirement: true lags: syncing: 40 lagging: 20 @@ -1282,19 +1305,19 @@ chain-settings: code: NEAR_MAINNET grpcId: 1050 short-names: [near] - chain-id: 0x0 + chain-id: mainnet - id: Testnet priority: 5 code: NEAR_TESTNET grpcId: 10064 short-names: [near-testnet] - chain-id: 0x0 + chain-id: testnet - id: Betanet priority: 1 code: NEAR_BETANET grpcId: 10065 short-names: [near-betanet] - chain-id: 0x0 + chain-id: betanet - id: opcelestia-raspberry label: OP Celestia Raspberry type: eth @@ -1420,13 +1443,13 @@ chain-settings: lagging: 1 chains: - id: Mainnet - chain-id: 0x0 + chain-id: cosmoshub-4 short-names: [ cosmos-hub ] code: COSMOS_HUB grpcId: 1057 priority: 100 - id: Testnet - chain-id: 0x0 + chain-id: provider short-names: [ cosmos-hub-testnet ] code: COSMOS_HUB_TESTNET grpcId: 10079 @@ -1441,13 +1464,13 @@ chain-settings: lagging: 1 chains: - id: Mainnet - chain-id: 0x0 + chain-id: axelar-dojo-1 short-names: [ axelar ] code: AXELAR grpcId: 1059 priority: 100 - id: Testnet - chain-id: 0x0 + chain-id: axelar-testnet-lisbon-3 short-names: [ axelar-testnet ] code: AXELAR_TESTNET grpcId: 10081 @@ -1462,13 +1485,13 @@ chain-settings: lagging: 1 chains: - id: Mainnet - chain-id: 0x0 + chain-id: osmosis short-names: [ osmosis ] code: OSMOSIS grpcId: 1060 priority: 100 - id: Testnet - chain-id: 0x0 + chain-id: osmo-test-5 short-names: [ osmosis-testnet ] code: OSMOSIS_TESTNET grpcId: 10082 @@ -1483,13 +1506,13 @@ chain-settings: lagging: 1 chains: - id: Mainnet - chain-id: 0x0 + chain-id: neutron-1 short-names: [ neutron ] code: NEUTRON grpcId: 1061 priority: 100 - id: Testnet - chain-id: 0x0 + chain-id: pion-1 short-names: [ neutron-testnet ] code: NEUTRON_TESTNET grpcId: 10083 diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt index 3df71e26..55d62236 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt @@ -44,16 +44,21 @@ abstract class UpstreamValidator( val cp = Comparator { avail1: UpstreamAvailability, avail2: UpstreamAvailability -> if (avail1.isBetterTo(avail2)) -1 else 1 } return results.sortedWith(cp).last() } + + fun resolve(results: Iterable): ValidateUpstreamSettingsResult { + val cp = Comparator { res1: ValidateUpstreamSettingsResult, res2: ValidateUpstreamSettingsResult -> if (res1.priority < res2.priority) -1 else 1 } + return results.sortedWith(cp).last() + } } } -enum class ValidateUpstreamSettingsResult { - UPSTREAM_VALID, - UPSTREAM_SETTINGS_ERROR, - UPSTREAM_FATAL_SETTINGS_ERROR, +enum class ValidateUpstreamSettingsResult(val priority: Int) { + UPSTREAM_VALID(0), + UPSTREAM_SETTINGS_ERROR(1), + UPSTREAM_FATAL_SETTINGS_ERROR(2), } -data class SingleCallValidator( +data class SingleCallValidator( val method: ChainRequest, - val check: (ByteArray) -> UpstreamAvailability, + val check: (ByteArray) -> T, ) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/cosmos/CosmosChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/cosmos/CosmosChainSpecific.kt index 8dfd719e..1ec56c6e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/cosmos/CosmosChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/cosmos/CosmosChainSpecific.kt @@ -14,15 +14,19 @@ import io.emeraldpay.dshackle.upstream.SingleCallValidator import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability.OK import io.emeraldpay.dshackle.upstream.UpstreamValidator +import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific import io.emeraldpay.dshackle.upstream.generic.GenericUpstreamValidator import io.emeraldpay.dshackle.upstream.rpcclient.ListParams import io.emeraldpay.dshackle.upstream.rpcclient.ObjectParams +import org.slf4j.LoggerFactory import reactor.core.publisher.Mono import java.math.BigInteger import java.time.Instant object CosmosChainSpecific : AbstractPollChainSpecific() { + + val log = LoggerFactory.getLogger(this::class.java) override fun latestBlockRequest(): ChainRequest = ChainRequest("block", ObjectParams()) override fun parseBlock(data: ByteArray, upstreamId: String): BlockContainer { @@ -75,9 +79,24 @@ object CosmosChainSpecific : AbstractPollChainSpecific() { return GenericUpstreamValidator( upstream, options, - SingleCallValidator( - ChainRequest("health", ListParams()), - ) { _ -> OK }, + listOf( + SingleCallValidator( + ChainRequest("health", ListParams()), + ) { _ -> OK }, + ), + listOf( + SingleCallValidator( + ChainRequest("status", ListParams()), + ) { data -> + val resp = Global.objectMapper.readValue(data, CosmosStatus::class.java) + if (chain.chainId.isNotEmpty() && resp.nodeInfo.network.lowercase() != chain.chainId.lowercase()) { + ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR + } else { + ValidateUpstreamSettingsResult.UPSTREAM_VALID + } + }, + ), + ) } @@ -115,6 +134,7 @@ data class CosmosStatus( @JsonIgnoreProperties(ignoreUnknown = true) data class CosmosNodeInfo( @JsonProperty("version") var version: String, + @JsonProperty("network") var network: String, ) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt index f0bc359c..4a547429 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/GenericUpstreamValidator.kt @@ -8,17 +8,28 @@ import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.UpstreamValidator import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult -import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult.UPSTREAM_VALID import reactor.core.publisher.Mono import java.util.concurrent.TimeoutException class GenericUpstreamValidator( upstream: Upstream, options: ChainOptions.Options, - private val validator: SingleCallValidator, + private val validators: List>, + private val startupValidators: List>, ) : UpstreamValidator(upstream, options) { override fun validate(): Mono { + return Mono.zip( + validators.map { exec(it, UpstreamAvailability.UNAVAILABLE) }, + ) { a -> a.map { it as UpstreamAvailability } } + .map(::resolve) + .defaultIfEmpty(UpstreamAvailability.UNAVAILABLE) + .onErrorResume { + log.error("Error during upstream validation for ${upstream.getId()}", it) + Mono.just(UpstreamAvailability.UNAVAILABLE) + } + } + fun exec(validator: SingleCallValidator, onError: T): Mono { return upstream.getIngressReader() .read(validator.method) .flatMap(ChainResponse::requireResult) @@ -29,10 +40,22 @@ class GenericUpstreamValidator( .then(Mono.error(TimeoutException("Validation timeout for ${validator.method.method}"))), ) .doOnError { err -> log.error("Error during ${validator.method.method} validation for ${upstream.getId()}", err) } - .onErrorReturn(UpstreamAvailability.UNAVAILABLE) + .onErrorReturn(onError) } override fun validateUpstreamSettings(): Mono { - return Mono.just(UPSTREAM_VALID) + return Mono.zip( + startupValidators.map { exec(it, ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR) }, + ) { a -> a.map { it as ValidateUpstreamSettingsResult } } + .map(::resolve) + .defaultIfEmpty(ValidateUpstreamSettingsResult.UPSTREAM_VALID) + .onErrorResume { + log.error("Error during upstream validation for ${upstream.getId()}", it) + Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR) + } + } + + override fun validateUpstreamSettingsOnStartup(): ValidateUpstreamSettingsResult { + return validateUpstreamSettings().block() ?: ValidateUpstreamSettingsResult.UPSTREAM_VALID } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt index 2ad00401..1facfb95 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/near/NearChainSpecific.kt @@ -15,6 +15,7 @@ import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.UpstreamSettingsDetector import io.emeraldpay.dshackle.upstream.UpstreamValidator +import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific import io.emeraldpay.dshackle.upstream.generic.GenericUpstreamValidator import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundService @@ -64,11 +65,20 @@ object NearChainSpecific : AbstractPollChainSpecific() { return GenericUpstreamValidator( upstream, options, - SingleCallValidator( - ChainRequest("status", ListParams()), - ) { data -> - validate(data) - }, + listOf( + SingleCallValidator( + ChainRequest("status", ListParams()), + ) { data -> + validate(data) + }, + ), + listOf( + SingleCallValidator( + ChainRequest("status", ListParams()), + ) { data -> + validateSettings(data, chain) + }, + ), ) } @@ -85,6 +95,15 @@ object NearChainSpecific : AbstractPollChainSpecific() { } } + fun validateSettings(data: ByteArray, chain: Chain): ValidateUpstreamSettingsResult { + val resp = Global.objectMapper.readValue(data, NearStatus::class.java) + return if (chain.chainId.isNotEmpty() && resp.chainId.lowercase() != chain.chainId.lowercase()) { + ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR + } else { + ValidateUpstreamSettingsResult.UPSTREAM_VALID + } + } + override fun upstreamSettingsDetector(chain: Chain, upstream: Upstream): UpstreamSettingsDetector { return NearUpstreamSettingsDetector(upstream) } @@ -108,6 +127,7 @@ data class NearHeader( @JsonIgnoreProperties(ignoreUnknown = true) data class NearStatus( + @JsonProperty("chain_id") var chainId: String, @JsonProperty("sync_info") var syncInfo: NearSync, ) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt index da5bf15d..5c87b469 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/polkadot/PolkadotChainSpecific.kt @@ -20,6 +20,7 @@ import io.emeraldpay.dshackle.upstream.SingleCallValidator import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.UpstreamValidator +import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.DefaultPolkadotMethods import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions @@ -97,11 +98,20 @@ object PolkadotChainSpecific : AbstractPollChainSpecific() { return GenericUpstreamValidator( upstream, options, - SingleCallValidator( - ChainRequest("system_health", ListParams()), - ) { data -> - validate(data, options.minPeers, upstream.getId()) - }, + listOf( + SingleCallValidator( + ChainRequest("system_health", ListParams()), + ) { data -> + validate(data, options.minPeers, upstream.getId()) + }, + ), + listOf( + SingleCallValidator( + ChainRequest("system_chain", ListParams()), + ) { data -> + validateSettings(data, chain) + }, + ), ) } @@ -123,6 +133,15 @@ object PolkadotChainSpecific : AbstractPollChainSpecific() { return UpstreamAvailability.OK } + fun validateSettings(data: ByteArray, chain: Chain): ValidateUpstreamSettingsResult { + val id = Global.objectMapper.readValue(data, String::class.java) + return if (chain.chainId.isNotEmpty() && id.lowercase() != chain.chainId.lowercase()) { + ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR + } else { + ValidateUpstreamSettingsResult.UPSTREAM_VALID + } + } + override fun makeIngressSubscription(ws: WsSubscriptions): IngressSubscription { return GenericIngressSubscription(ws, DefaultPolkadotMethods.subs.map { it.first }) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt index 1d5ddc3c..40710dbd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/solana/SolanaChainSpecific.kt @@ -124,17 +124,20 @@ object SolanaChainSpecific : AbstractChainSpecific() { return GenericUpstreamValidator( upstream, options, - SingleCallValidator( - ChainRequest("getHealth", ListParams()), - ) { data -> - val resp = String(data) - if (resp == "\"ok\"") { - UpstreamAvailability.OK - } else { - log.warn("Upstream {} validation failed, solana status is {}", upstream.getId(), resp) - UpstreamAvailability.UNAVAILABLE - } - }, + listOf( + SingleCallValidator( + ChainRequest("getHealth", ListParams()), + ) { data -> + val resp = String(data) + if (resp == "\"ok\"") { + UpstreamAvailability.OK + } else { + log.warn("Upstream {} validation failed, solana status is {}", upstream.getId(), resp) + UpstreamAvailability.UNAVAILABLE + } + }, + ), + listOf(), ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt index c2f241e1..b6882999 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/starknet/StarknetChainSpecific.kt @@ -65,11 +65,14 @@ object StarknetChainSpecific : AbstractPollChainSpecific() { return GenericUpstreamValidator( upstream, options, - SingleCallValidator( - ChainRequest("starknet_syncing", ListParams()), - ) { data -> - validate(data, config.laggingLagSize, upstream.getId()) - }, + listOf( + SingleCallValidator( + ChainRequest("starknet_syncing", ListParams()), + ) { data -> + validate(data, config.laggingLagSize, upstream.getId()) + }, + ), + listOf(), ) }