From 750310702a5675642cc86df2985c92c0c97fa5ce Mon Sep 17 00:00:00 2001 From: Aarif Khamdi Date: Wed, 23 Oct 2024 11:57:42 +0500 Subject: [PATCH] add toncenter (#580) * add toncenter * upd dep * toncenter->ton * toncenter->ton --- .../kotlin/chainsconfig.codegen.gradle.kts | 1 + emerald-grpc | 2 +- .../io/emeraldpay/dshackle/BlockchainType.kt | 3 +- foundation/src/main/resources/public | 2 +- .../kotlin/io/emeraldpay/dshackle/Global.kt | 3 + .../dshackle/upstream/CallTargetsHolder.kt | 3 + .../upstream/calls/DefaultTonHttpMethods.kt | 100 +++++++++++++ .../upstream/generic/ChainSpecific.kt | 3 + .../dshackle/upstream/ton/TonHttpSpecific.kt | 134 ++++++++++++++++++ .../upstream/ton/TonLowerBoundService.kt | 15 ++ 10 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultTonHttpMethods.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonHttpSpecific.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonLowerBoundService.kt diff --git a/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts b/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts index 57c0c5168..362f1073e 100644 --- a/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts +++ b/buildSrc/src/main/kotlin/chainsconfig.codegen.gradle.kts @@ -135,6 +135,7 @@ open class CodeGen(private val config: ChainsConfig) { "solana" -> "BlockchainType.SOLANA" "near" -> "BlockchainType.NEAR" "eth-beacon-chain" -> "BlockchainType.ETHEREUM_BEACON_CHAIN" + "ton" -> "BlockchainType.TON" "cosmos" -> "BlockchainType.COSMOS" else -> throw IllegalArgumentException("unknown blockchain type $type") } diff --git a/emerald-grpc b/emerald-grpc index 77ed85be8..6dba9b1a1 160000 --- a/emerald-grpc +++ b/emerald-grpc @@ -1 +1 @@ -Subproject commit 77ed85be8087689288182841855db8e0fa542c85 +Subproject commit 6dba9b1a10a86c09f419e43bf97ee4c27962adf9 diff --git a/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt b/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt index 55e94eac0..72f3dd8a0 100644 --- a/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt +++ b/foundation/src/main/kotlin/io/emeraldpay/dshackle/BlockchainType.kt @@ -11,7 +11,8 @@ enum class BlockchainType( SOLANA(ApiType.JSON_RPC), NEAR(ApiType.JSON_RPC), ETHEREUM_BEACON_CHAIN(ApiType.REST), - COSMOS(ApiType.JSON_RPC); + COSMOS(ApiType.JSON_RPC), + TON(ApiType.REST); } enum class ApiType { diff --git a/foundation/src/main/resources/public b/foundation/src/main/resources/public index 583c93ae0..a4e3137cf 160000 --- a/foundation/src/main/resources/public +++ b/foundation/src/main/resources/public @@ -1 +1 @@ -Subproject commit 583c93ae0e5daa275e6df32844b83939b65b10d1 +Subproject commit a4e3137cfe2dd9fdfbb4ac07c4fe89ff68e771e7 diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt index 3cefefc96..ec7da933f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt @@ -33,6 +33,8 @@ import io.emeraldpay.dshackle.upstream.bitcoin.data.RpcUnspent import io.emeraldpay.dshackle.upstream.bitcoin.data.RpcUnspentDeserializer import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.TransactionIdSerializer +import io.emeraldpay.dshackle.upstream.ton.TonMasterchainInfo +import io.emeraldpay.dshackle.upstream.ton.TonMasterchainInfoDeserializer import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone @@ -81,6 +83,7 @@ class Global { module.addDeserializer(RpcUnspent::class.java, RpcUnspentDeserializer()) module.addDeserializer(ChainRequest::class.java, ChainRequest.Deserializer()) module.addDeserializer(BeaconChainBlockHeader::class.java, BeaconChainBlockHeaderDeserializer()) + module.addDeserializer(TonMasterchainInfo::class.java, TonMasterchainInfoDeserializer()) val objectMapper = ObjectMapper() objectMapper.registerModule(module) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt index f4b5f46ac..dbe0237d5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CallTargetsHolder.kt @@ -8,6 +8,7 @@ import io.emeraldpay.dshackle.BlockchainType.NEAR import io.emeraldpay.dshackle.BlockchainType.POLKADOT import io.emeraldpay.dshackle.BlockchainType.SOLANA import io.emeraldpay.dshackle.BlockchainType.STARKNET +import io.emeraldpay.dshackle.BlockchainType.TON import io.emeraldpay.dshackle.BlockchainType.UNKNOWN import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.foundation.ChainOptions @@ -18,6 +19,7 @@ import io.emeraldpay.dshackle.upstream.calls.DefaultCosmosMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.DefaultPolkadotMethods import io.emeraldpay.dshackle.upstream.calls.DefaultStarknetMethods +import io.emeraldpay.dshackle.upstream.calls.DefaultTonHttpMethods import org.springframework.stereotype.Component @Component @@ -38,6 +40,7 @@ class CallTargetsHolder { NEAR -> DefaultNearMethods() ETHEREUM_BEACON_CHAIN -> DefaultBeaconChainMethods() COSMOS -> DefaultCosmosMethods() + TON -> DefaultTonHttpMethods() UNKNOWN -> throw IllegalArgumentException("unknown chain") } callTargets[chain] = created diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultTonHttpMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultTonHttpMethods.kt new file mode 100644 index 000000000..1cc9c8bbc --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultTonHttpMethods.kt @@ -0,0 +1,100 @@ +package io.emeraldpay.dshackle.upstream.calls + +import io.emeraldpay.dshackle.quorum.AlwaysQuorum +import io.emeraldpay.dshackle.quorum.CallQuorum +import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException + +class DefaultTonHttpMethods : CallMethods { + + // HTTP API section + private val accountHttpMethods = setOf( + getMethod("/getAddressInformation"), + getMethod("/getExtendedAddressInformation"), + getMethod("/getWalletInformation"), + getMethod("/getAddressBalance"), + getMethod("/getAddressState"), + getMethod("/packAddress"), + getMethod("/unpackAddress"), + getMethod("/getTokenData"), + getMethod("/detectAddress"), + ) + + private val blockHttpMethods = setOf( + getMethod("/getMasterchainInfo"), + getMethod("/getMasterchainBlockSignatures"), + getMethod("/getShardBlockProof"), + getMethod("/getConsensusBlock"), + getMethod("/lookupBlock"), + getMethod("/shards"), + getMethod("/getBlockHeader"), + getMethod("/getOutMsgQueueSizes"), + ) + + private val transactionHttpMethods = setOf( + getMethod("/getTransactions"), + getMethod("/getBlockTransactions"), + getMethod("/getBlockTransactionsExt"), + getMethod("/tryLocateTx"), + getMethod("/tryLocateResultTx"), + getMethod("/tryLocateSourceTx"), + ) + + private val getConfigHttpMethods = setOf( + getMethod("/getConfigParam"), + getMethod("/getConfigAll"), + ) + + private val runMethodHttpMethods = setOf( + postMethod("/runGetMethod"), + ) + + private val sendHttpMethods = setOf( + postMethod("/sendBoc"), + postMethod("/sendBocReturnHash"), + postMethod("/sendQuery"), + postMethod("/estimateFee"), + ) + + private val jsonRpcHttpMethods = setOf( + postMethod("/jsonRPC"), + ) + + private val allowedHttpMethods: Set = accountHttpMethods + + blockHttpMethods + + transactionHttpMethods + + getConfigHttpMethods + + runMethodHttpMethods + + sendHttpMethods + + jsonRpcHttpMethods + + override fun createQuorumFor(method: String): CallQuorum { + return AlwaysQuorum() + } + + override fun isCallable(method: String): Boolean { + return allowedHttpMethods.contains(method) + } + + override fun getSupportedMethods(): Set { + return allowedHttpMethods.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 { + return when (groupName) { + "default" -> allowedHttpMethods + else -> emptyList() + }.toSet() + } + + private fun getMethod(method: String) = "GET#$method" + + private fun postMethod(method: String) = "POST#$method" +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt index 2b5c86ed1..0c919a43f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/generic/ChainSpecific.kt @@ -8,6 +8,7 @@ import io.emeraldpay.dshackle.BlockchainType.NEAR import io.emeraldpay.dshackle.BlockchainType.POLKADOT import io.emeraldpay.dshackle.BlockchainType.SOLANA import io.emeraldpay.dshackle.BlockchainType.STARKNET +import io.emeraldpay.dshackle.BlockchainType.TON import io.emeraldpay.dshackle.BlockchainType.UNKNOWN import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.cache.Caches @@ -42,6 +43,7 @@ import io.emeraldpay.dshackle.upstream.near.NearChainSpecific import io.emeraldpay.dshackle.upstream.polkadot.PolkadotChainSpecific import io.emeraldpay.dshackle.upstream.solana.SolanaChainSpecific import io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific +import io.emeraldpay.dshackle.upstream.ton.TonHttpSpecific import org.apache.commons.collections4.Factory import org.springframework.cloud.sleuth.Tracer import reactor.core.publisher.Mono @@ -110,6 +112,7 @@ object ChainSpecificRegistry { SOLANA -> SolanaChainSpecific NEAR -> NearChainSpecific ETHEREUM_BEACON_CHAIN -> BeaconChainSpecific + TON -> TonHttpSpecific COSMOS -> CosmosChainSpecific BITCOIN -> throw IllegalArgumentException("bitcoin should use custom streams implementation") UNKNOWN -> throw IllegalArgumentException("unknown chain") diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonHttpSpecific.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonHttpSpecific.kt new file mode 100644 index 000000000..c6da05195 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonHttpSpecific.kt @@ -0,0 +1,134 @@ +package io.emeraldpay.dshackle.upstream.ton + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +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.SingleValidator +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.UpstreamAvailability +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 + +fun generateRandomString(length: Int): String { + val allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + return (1..length) + .map { allowedChars.random() } + .joinToString("") +} + +object TonHttpSpecific : AbstractPollChainSpecific() { + override fun getFromHeader(data: ByteArray, upstreamId: String, api: ChainReader): Mono { + throw NotImplementedError() + } + + override fun listenNewHeadsRequest(): ChainRequest { + throw NotImplementedError() + } + + override fun unsubscribeNewHeadsRequest(subId: String): ChainRequest { + throw NotImplementedError() + } + + override fun lowerBoundService(chain: Chain, upstream: Upstream): LowerBoundService { + return TonLowerBoundService(chain, upstream) + } + + override fun latestBlockRequest(): ChainRequest { + return ChainRequest("GET#/getMasterchainInfo", RestParams.emptyParams()) + } + + override fun parseBlock(data: ByteArray, upstreamId: String, api: ChainReader): Mono { + val blockHeader = Global.objectMapper.readValue(data) + + return Mono.just( + BlockContainer( + height = blockHeader.result.last.seqno, + hash = BlockId.fromBase64(blockHeader.result.last.root_hash), + difficulty = BigInteger.ZERO, + timestamp = Instant.EPOCH, + full = false, + json = data, + parsed = blockHeader, + transactions = emptyList(), + upstreamId = upstreamId, + parentHash = BlockId.fromBase64(generateRandomString(32)), + ), + ) + } + + override fun upstreamValidators( + chain: Chain, + upstream: Upstream, + options: Options, + config: ChainConfig, + ): List> { + return emptyList() + } + + override fun upstreamSettingsValidators( + chain: Chain, + upstream: Upstream, + options: Options, + config: ChainConfig, + ): List> { + // add check generic block + return emptyList() + } +} + +data class TonMasterchainInfoResultLast( + val workchain: Int, + val shard: String, + val seqno: Long, + val root_hash: String, + val file_hash: String, +) + +data class TonMasterchainInfoResult( + val last: TonMasterchainInfoResultLast, +) + +data class TonMasterchainInfo( + val ok: Boolean, + val result: TonMasterchainInfoResult, +) + +class TonMasterchainInfoDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): TonMasterchainInfo { + val node = p.readValueAsTree() + val ok = node["ok"].booleanValue() + val workchain = node["result"]["last"]["workchain"].intValue() + val shard = node["result"]["last"]["shard"].textValue() + val seqno = node["result"]["last"]["seqno"].longValue() + val root_hash = node["result"]["last"]["root_hash"].textValue() + val file_hash = node["result"]["last"]["file_hash"].textValue() + + return TonMasterchainInfo( + ok, + TonMasterchainInfoResult( + TonMasterchainInfoResultLast( + workchain, + shard, + seqno, + root_hash, + file_hash, + ), + ), + ) + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonLowerBoundService.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonLowerBoundService.kt new file mode 100644 index 000000000..f85e69c59 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ton/TonLowerBoundService.kt @@ -0,0 +1,15 @@ +package io.emeraldpay.dshackle.upstream.ton + +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 TonLowerBoundService( + chain: Chain, + upstream: Upstream, +) : LowerBoundService(chain, upstream) { + override fun detectors(): List { + return listOf() + } +}