Kadena support (#650)
* kadena support * add kadena mempool endpoits * createQuorumFor fix
This commit is contained in:
@@ -138,6 +138,7 @@ open class CodeGen(private val config: ChainsConfig) {
|
|||||||
"ton" -> "BlockchainType.TON"
|
"ton" -> "BlockchainType.TON"
|
||||||
"cosmos" -> "BlockchainType.COSMOS"
|
"cosmos" -> "BlockchainType.COSMOS"
|
||||||
"ripple" -> "BlockchainType.RIPPLE"
|
"ripple" -> "BlockchainType.RIPPLE"
|
||||||
|
"kadena" -> "BlockchainType.KADENA"
|
||||||
else -> throw IllegalArgumentException("unknown blockchain type $type")
|
else -> throw IllegalArgumentException("unknown blockchain type $type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule emerald-grpc updated: cea302c0d7...1b37beea67
@@ -13,7 +13,8 @@ enum class BlockchainType(
|
|||||||
ETHEREUM_BEACON_CHAIN(ApiType.REST),
|
ETHEREUM_BEACON_CHAIN(ApiType.REST),
|
||||||
COSMOS(ApiType.JSON_RPC),
|
COSMOS(ApiType.JSON_RPC),
|
||||||
TON(ApiType.REST),
|
TON(ApiType.REST),
|
||||||
RIPPLE(ApiType.JSON_RPC),;
|
RIPPLE(ApiType.JSON_RPC),
|
||||||
|
KADENA(ApiType.REST),;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ApiType {
|
enum class ApiType {
|
||||||
|
|||||||
Submodule foundation/src/main/resources/public updated: 88313c0c55...cc548cfe19
@@ -4,6 +4,7 @@ import io.emeraldpay.dshackle.BlockchainType.BITCOIN
|
|||||||
import io.emeraldpay.dshackle.BlockchainType.COSMOS
|
import io.emeraldpay.dshackle.BlockchainType.COSMOS
|
||||||
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
|
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
|
||||||
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM_BEACON_CHAIN
|
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM_BEACON_CHAIN
|
||||||
|
import io.emeraldpay.dshackle.BlockchainType.KADENA
|
||||||
import io.emeraldpay.dshackle.BlockchainType.NEAR
|
import io.emeraldpay.dshackle.BlockchainType.NEAR
|
||||||
import io.emeraldpay.dshackle.BlockchainType.POLKADOT
|
import io.emeraldpay.dshackle.BlockchainType.POLKADOT
|
||||||
import io.emeraldpay.dshackle.BlockchainType.RIPPLE
|
import io.emeraldpay.dshackle.BlockchainType.RIPPLE
|
||||||
@@ -19,6 +20,7 @@ import io.emeraldpay.dshackle.upstream.calls.DefaultBeaconChainMethods
|
|||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultCosmosMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultCosmosMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.DefaultKadenaMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultNearMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultNearMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultPolkadotMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultPolkadotMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultRippleMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultRippleMethods
|
||||||
@@ -54,6 +56,7 @@ class CallTargetsHolder {
|
|||||||
COSMOS -> DefaultCosmosMethods()
|
COSMOS -> DefaultCosmosMethods()
|
||||||
TON -> DefaultTonHttpMethods(connection)
|
TON -> DefaultTonHttpMethods(connection)
|
||||||
RIPPLE -> DefaultRippleMethods()
|
RIPPLE -> DefaultRippleMethods()
|
||||||
|
KADENA -> DefaultKadenaMethods()
|
||||||
UNKNOWN -> throw IllegalArgumentException("unknown chain")
|
UNKNOWN -> throw IllegalArgumentException("unknown chain")
|
||||||
}
|
}
|
||||||
callTargets[chain] = created
|
callTargets[chain] = created
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.calls
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
|
||||||
|
import io.emeraldpay.dshackle.quorum.BroadcastQuorum
|
||||||
|
import io.emeraldpay.dshackle.quorum.CallQuorum
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException
|
||||||
|
|
||||||
|
class DefaultKadenaMethods : CallMethods {
|
||||||
|
|
||||||
|
private val kadenaMethods = setOf(
|
||||||
|
getMethod("/chain/*/hash"),
|
||||||
|
postMethod("/chain/*/hash"),
|
||||||
|
getMethod("/chain/*/header"),
|
||||||
|
getMethod("/chain/*/header/*"),
|
||||||
|
postMethod("/chain/*/header/branch"),
|
||||||
|
getMethod("/chain/*/payload/*"),
|
||||||
|
postMethod("/chain/*/payload/batch"),
|
||||||
|
getMethod("/chain/*/payload/*/outputs"),
|
||||||
|
postMethod("/chain/*/payload/*/outputs/batch"),
|
||||||
|
getMethod("/chain/*/payload/*"),
|
||||||
|
postMethod("/chain/*/pact/local"),
|
||||||
|
postMethod("/chain/*/pact/send"),
|
||||||
|
postMethod("/chain/*/pact/poll"),
|
||||||
|
postMethod("/chain/*/pact/listen"),
|
||||||
|
postMethod("/chain/*/pact/private"),
|
||||||
|
postMethod("/chain/*/pact/spv"),
|
||||||
|
postMethod("/chain/*/mempool/getPending"),
|
||||||
|
postMethod("/chain/*/mempool/member"),
|
||||||
|
postMethod("/chain/*/mempool/lookup"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val insert = setOf(
|
||||||
|
putMethod("/chain/*/mempool/insert"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val allowedMethods: Set<String> = kadenaMethods + insert
|
||||||
|
|
||||||
|
override fun createQuorumFor(method: String): CallQuorum {
|
||||||
|
if (insert.contains(method)) {
|
||||||
|
return BroadcastQuorum()
|
||||||
|
}
|
||||||
|
return AlwaysQuorum()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isCallable(method: String): Boolean {
|
||||||
|
return allowedMethods.contains(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedMethods(): Set<String> {
|
||||||
|
return allowedMethods.toSortedSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isHardcoded(method: String): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun executeHardcoded(method: String): ByteArray {
|
||||||
|
throw RpcException(-32601, "Method not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getGroupMethods(groupName: String): Set<String> {
|
||||||
|
return when (groupName) {
|
||||||
|
"default" -> getSupportedMethods()
|
||||||
|
else -> emptyList()
|
||||||
|
}.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMethod(method: String) = "GET#$method"
|
||||||
|
|
||||||
|
private fun postMethod(method: String) = "POST#$method"
|
||||||
|
|
||||||
|
private fun putMethod(method: String) = "PUT#$method"
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import io.emeraldpay.dshackle.BlockchainType.BITCOIN
|
|||||||
import io.emeraldpay.dshackle.BlockchainType.COSMOS
|
import io.emeraldpay.dshackle.BlockchainType.COSMOS
|
||||||
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
|
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
|
||||||
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM_BEACON_CHAIN
|
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM_BEACON_CHAIN
|
||||||
|
import io.emeraldpay.dshackle.BlockchainType.KADENA
|
||||||
import io.emeraldpay.dshackle.BlockchainType.NEAR
|
import io.emeraldpay.dshackle.BlockchainType.NEAR
|
||||||
import io.emeraldpay.dshackle.BlockchainType.POLKADOT
|
import io.emeraldpay.dshackle.BlockchainType.POLKADOT
|
||||||
import io.emeraldpay.dshackle.BlockchainType.RIPPLE
|
import io.emeraldpay.dshackle.BlockchainType.RIPPLE
|
||||||
@@ -38,6 +39,7 @@ import io.emeraldpay.dshackle.upstream.cosmos.CosmosChainSpecific
|
|||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions
|
import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions
|
||||||
import io.emeraldpay.dshackle.upstream.finalization.FinalizationDetector
|
import io.emeraldpay.dshackle.upstream.finalization.FinalizationDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.kadena.KadenaChainSpecific
|
||||||
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundService
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundService
|
||||||
import io.emeraldpay.dshackle.upstream.near.NearChainSpecific
|
import io.emeraldpay.dshackle.upstream.near.NearChainSpecific
|
||||||
import io.emeraldpay.dshackle.upstream.polkadot.PolkadotChainSpecific
|
import io.emeraldpay.dshackle.upstream.polkadot.PolkadotChainSpecific
|
||||||
@@ -115,6 +117,7 @@ object ChainSpecificRegistry {
|
|||||||
TON -> TonHttpSpecific
|
TON -> TonHttpSpecific
|
||||||
COSMOS -> CosmosChainSpecific
|
COSMOS -> CosmosChainSpecific
|
||||||
RIPPLE -> RippleChainSpecific
|
RIPPLE -> RippleChainSpecific
|
||||||
|
KADENA -> KadenaChainSpecific
|
||||||
BITCOIN -> throw IllegalArgumentException("bitcoin should use custom streams implementation")
|
BITCOIN -> throw IllegalArgumentException("bitcoin should use custom streams implementation")
|
||||||
UNKNOWN -> throw IllegalArgumentException("unknown chain")
|
UNKNOWN -> throw IllegalArgumentException("unknown chain")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.kadena
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
|
||||||
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.data.BlockId
|
||||||
|
import io.emeraldpay.dshackle.foundation.ChainOptions.Options
|
||||||
|
import io.emeraldpay.dshackle.reader.ChainReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.ChainRequest
|
||||||
|
import io.emeraldpay.dshackle.upstream.GenericSingleCallValidator
|
||||||
|
import io.emeraldpay.dshackle.upstream.SingleValidator
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
|
import io.emeraldpay.dshackle.upstream.UpstreamSettingsDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
|
||||||
|
import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundService
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.RestParams
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
|
object KadenaChainSpecific : AbstractPollChainSpecific() {
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
|
override fun parseBlock(data: ByteArray, upstreamId: String, api: ChainReader): Mono<BlockContainer> {
|
||||||
|
val block = Global.objectMapper.readValue<KadenaHeader>(data)
|
||||||
|
|
||||||
|
val blockHash = Base64.encode(block.id.encodeToByteArray())
|
||||||
|
return Mono.just(
|
||||||
|
BlockContainer(
|
||||||
|
height = block.height,
|
||||||
|
hash = BlockId.fromBase64(blockHash),
|
||||||
|
difficulty = BigInteger.ZERO,
|
||||||
|
timestamp = Instant.EPOCH,
|
||||||
|
full = false,
|
||||||
|
json = data,
|
||||||
|
parsed = block,
|
||||||
|
transactions = emptyList(),
|
||||||
|
upstreamId = upstreamId,
|
||||||
|
parentHash = BlockId.fromBase64(blockHash),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFromHeader(data: ByteArray, upstreamId: String, api: ChainReader): Mono<BlockContainer> {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun listenNewHeadsRequest(): ChainRequest {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun upstreamValidators(
|
||||||
|
chain: Chain,
|
||||||
|
upstream: Upstream,
|
||||||
|
options: Options,
|
||||||
|
config: ChainConfig,
|
||||||
|
): List<SingleValidator<UpstreamAvailability>> {
|
||||||
|
var validators = listOf(
|
||||||
|
GenericSingleCallValidator(
|
||||||
|
ChainRequest("GET#/cut", RestParams.emptyParams()),
|
||||||
|
upstream,
|
||||||
|
) { data ->
|
||||||
|
val block = Global.objectMapper.readValue<KadenaHeader>(data)
|
||||||
|
if (block.id != "") {
|
||||||
|
UpstreamAvailability.OK
|
||||||
|
} else {
|
||||||
|
UpstreamAvailability.UNAVAILABLE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return validators
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun upstreamSettingsValidators(
|
||||||
|
chain: Chain,
|
||||||
|
upstream: Upstream,
|
||||||
|
options: Options,
|
||||||
|
config: ChainConfig,
|
||||||
|
): List<SingleValidator<ValidateUpstreamSettingsResult>> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lowerBoundService(chain: Chain, upstream: Upstream): LowerBoundService {
|
||||||
|
return KadenaLowerBoundService(chain, upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun upstreamSettingsDetector(chain: Chain, upstream: Upstream): UpstreamSettingsDetector {
|
||||||
|
return KadenaUpstreamSettingsDetector(upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestBlockRequest(): ChainRequest {
|
||||||
|
return ChainRequest("GET#/cut", RestParams.emptyParams())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class KadenaHeader(
|
||||||
|
@JsonProperty("height") var height: Long,
|
||||||
|
@JsonProperty("weight") var weight: String,
|
||||||
|
@JsonProperty("instance") var instance: String,
|
||||||
|
@JsonProperty("id") var id: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.kadena
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundService
|
||||||
|
|
||||||
|
class KadenaLowerBoundService(
|
||||||
|
private val chain: Chain,
|
||||||
|
upstream: Upstream,
|
||||||
|
) : LowerBoundService(chain, upstream) {
|
||||||
|
override fun detectors(): List<LowerBoundDetector> {
|
||||||
|
return listOf(KadenaLowerBoundStateDetector(chain))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.kadena
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundData
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
|
||||||
|
class KadenaLowerBoundStateDetector(
|
||||||
|
chain: Chain,
|
||||||
|
) : LowerBoundDetector(chain) {
|
||||||
|
|
||||||
|
override fun period(): Long {
|
||||||
|
return 120
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun internalDetectLowerBound(): Flux<LowerBoundData> {
|
||||||
|
return Flux.just(LowerBoundData(1, LowerBoundType.STATE))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun types(): Set<LowerBoundType> {
|
||||||
|
return setOf(LowerBoundType.STATE)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.kadena
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.upstream.BasicUpstreamSettingsDetector
|
||||||
|
import io.emeraldpay.dshackle.upstream.ChainRequest
|
||||||
|
import io.emeraldpay.dshackle.upstream.NodeTypeRequest
|
||||||
|
import io.emeraldpay.dshackle.upstream.UNKNOWN_CLIENT_VERSION
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.RestParams
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
|
|
||||||
|
class KadenaUpstreamSettingsDetector(
|
||||||
|
upstream: Upstream,
|
||||||
|
) : BasicUpstreamSettingsDetector(upstream) {
|
||||||
|
override fun internalDetectLabels(): Flux<Pair<String, String>> {
|
||||||
|
return Flux.merge(
|
||||||
|
detectNodeType(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clientVersionRequest(): ChainRequest {
|
||||||
|
return ChainRequest("GET#/cut", RestParams.emptyParams())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseClientVersion(data: ByteArray): String {
|
||||||
|
return Global.objectMapper.readValue<KadenaHeader>(data).instance
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class KadenaHeader(
|
||||||
|
@JsonProperty("instance") var instance: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun nodeTypeRequest(): NodeTypeRequest = NodeTypeRequest(clientVersionRequest())
|
||||||
|
|
||||||
|
override fun clientType(node: JsonNode): String = "kadena"
|
||||||
|
|
||||||
|
override fun clientVersion(node: JsonNode): String =
|
||||||
|
node.get("version")?.get("version")?.asText() ?: UNKNOWN_CLIENT_VERSION
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.kadena
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundData
|
||||||
|
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundType
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import reactor.test.StepVerifier
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
class KadenaLowerBoundStateDetectorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `kadena lower block is 1`() {
|
||||||
|
val detector = KadenaLowerBoundStateDetector(Chain.KADENA__MAINNET)
|
||||||
|
|
||||||
|
StepVerifier.withVirtualTime { detector.detectLowerBound() }
|
||||||
|
.expectSubscription()
|
||||||
|
.expectNoEvent(Duration.ofSeconds(15))
|
||||||
|
.expectNext(LowerBoundData(1, LowerBoundType.STATE))
|
||||||
|
.thenCancel()
|
||||||
|
.verify(Duration.ofSeconds(3))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user