near protocol support

This commit is contained in:
a10zn8
2024-02-26 22:26:38 +03:00
parent f84add8948
commit 8c5e69a7f5
10 changed files with 242 additions and 4 deletions

View File

@@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
import io.emeraldpay.dshackle.BlockchainType.NEAR
import io.emeraldpay.dshackle.BlockchainType.POLKADOT
import io.emeraldpay.dshackle.BlockchainType.SOLANA
import io.emeraldpay.dshackle.BlockchainType.STARKNET
@@ -29,6 +30,7 @@ class CallTargetsHolder {
STARKNET -> DefaultStarknetMethods(chain)
POLKADOT -> DefaultPolkadotMethods()
SOLANA -> DefaultSolanaMethods()
NEAR -> DefaultNearMethods()
UNKNOWN -> throw IllegalArgumentException("unknown chain")
}
callTargets[chain] = created

View File

@@ -0,0 +1,63 @@
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
import io.emeraldpay.dshackle.quorum.BroadcastQuorum
import io.emeraldpay.dshackle.quorum.CallQuorum
import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.ethereum.rpc.RpcException
class DefaultNearMethods : CallMethods {
private val all = setOf(
"view_access_key",
"query",
"EXPERIMENTAL_changes",
"block",
"chunk",
"EXPERIMENTAL_changes_in_block",
"gas_price",
"status",
"network_info",
"validators",
"tx",
"EXPERIMENTAL_tx_status",
"EXPERIMENTAL_receipt",
)
private val add = setOf(
"broadcast_tx_async",
"broadcast_tx_commit",
)
private val allowedMethods: Set<String> = all + add
override fun createQuorumFor(method: String): CallQuorum {
return when {
add.contains(method) -> BroadcastQuorum()
all.contains(method) -> AlwaysQuorum()
else -> AlwaysQuorum()
}
}
override fun isCallable(method: String): Boolean {
return allowedMethods.contains(method)
}
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> =
when (groupName) {
"default" -> getSupportedMethods()
else -> emptyList()
}.toSet()
override fun getSupportedMethods(): Set<String> {
return allowedMethods.toSortedSet()
}
}

View File

@@ -2,6 +2,7 @@ package io.emeraldpay.dshackle.upstream.generic
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
import io.emeraldpay.dshackle.BlockchainType.NEAR
import io.emeraldpay.dshackle.BlockchainType.POLKADOT
import io.emeraldpay.dshackle.BlockchainType.SOLANA
import io.emeraldpay.dshackle.BlockchainType.STARKNET
@@ -26,6 +27,7 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.calls.CallSelector
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainSpecific
import io.emeraldpay.dshackle.upstream.ethereum.WsSubscriptions
import io.emeraldpay.dshackle.upstream.near.NearChainSpecific
import io.emeraldpay.dshackle.upstream.polkadot.PolkadotChainSpecific
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.solana.SolanaChainSpecific
@@ -74,6 +76,7 @@ object ChainSpecificRegistry {
STARKNET -> StarknetChainSpecific
POLKADOT -> PolkadotChainSpecific
SOLANA -> SolanaChainSpecific
NEAR -> NearChainSpecific
BITCOIN -> throw IllegalArgumentException("bitcoin should use custom streams implementation")
UNKNOWN -> throw IllegalArgumentException("unknown chain")
}

View File

@@ -0,0 +1,110 @@
package io.emeraldpay.dshackle.upstream.near
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
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.upstream.LowerBoundBlockDetector
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.generic.AbstractPollChainSpecific
import io.emeraldpay.dshackle.upstream.generic.GenericUpstreamValidator
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import java.math.BigInteger
import java.time.Instant
import java.util.concurrent.TimeUnit
object NearChainSpecific : AbstractPollChainSpecific() {
override fun parseBlock(data: ByteArray, upstreamId: String): BlockContainer {
val block = Global.objectMapper.readValue(data, NearBlock::class.java).header
return BlockContainer(
height = block.height,
hash = BlockId.fromBase64(block.hash),
difficulty = BigInteger.ZERO,
timestamp = Instant.ofEpochMilli(TimeUnit.MILLISECONDS.convert(block.timestamp, TimeUnit.NANOSECONDS)),
full = false,
json = data,
parsed = block,
transactions = emptyList(),
upstreamId = upstreamId,
parentHash = BlockId.fromBase64(block.prevHash),
)
}
override fun parseHeader(data: ByteArray, upstreamId: String): BlockContainer {
throw NotImplementedError()
}
override fun listenNewHeadsRequest(): JsonRpcRequest {
throw NotImplementedError()
}
override fun unsubscribeNewHeadsRequest(subId: String): JsonRpcRequest {
throw NotImplementedError()
}
override fun validator(
chain: Chain,
upstream: Upstream,
options: Options,
config: ChainConfig,
): UpstreamValidator {
return GenericUpstreamValidator(
upstream,
options,
SingleCallValidator(
JsonRpcRequest("status", listOf()),
) { data ->
validate(data)
},
)
}
override fun lowerBoundBlockDetector(chain: Chain, upstream: Upstream): LowerBoundBlockDetector {
return NearLowerBoundBlockDetector(chain, upstream)
}
fun validate(data: ByteArray): UpstreamAvailability {
val resp = Global.objectMapper.readValue(data, NearStatus::class.java)
return if (resp.syncInfo.syncing) {
UpstreamAvailability.SYNCING
} else {
UpstreamAvailability.OK
}
}
override fun latestBlockRequest(): JsonRpcRequest =
JsonRpcRequest("block", mapOf("finality" to "optimistic"))
}
@JsonIgnoreProperties(ignoreUnknown = true)
data class NearBlock(
@JsonProperty("header") var header: NearHeader,
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class NearHeader(
@JsonProperty("height") var height: Long,
@JsonProperty("hash") var hash: String,
@JsonProperty("prev_hash") var prevHash: String,
@JsonProperty("timestamp") var timestamp: Long,
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class NearStatus(
@JsonProperty("sync_info") var syncInfo: NearSync,
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class NearSync(
@JsonProperty("syncing") var syncing: Boolean,
@JsonProperty("earliest_block_height") var earliestHeight: Long,
@JsonProperty("earliest_block_time") var earliestBlockTime: Instant,
)

View File

@@ -0,0 +1,25 @@
package io.emeraldpay.dshackle.upstream.near
import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.upstream.LowerBoundBlockDetector
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import reactor.core.publisher.Mono
class NearLowerBoundBlockDetector(
chain: Chain,
val upstream: Upstream,
) : LowerBoundBlockDetector(chain, upstream) {
override fun lowerBlockDetect(): Mono<LowerBlockData> {
return upstream.getIngressReader().read(JsonRpcRequest("status", listOf())).map {
val resp = Global.objectMapper.readValue(it.getResult(), NearStatus::class.java)
LowerBlockData(resp.syncInfo.earliestHeight, null, resp.syncInfo.earliestBlockTime.toEpochMilli())
}
}
override fun periodRequest(): Long {
return 120
}
}

View File

@@ -29,6 +29,7 @@ data class JsonRpcRequest(
val nonce: Long?,
val selector: BlockchainOuterClass.Selector?,
val isStreamed: Boolean = false,
val objParams: Map<Any, Any>? = null,
) {
@JvmOverloads constructor(
@@ -39,12 +40,17 @@ data class JsonRpcRequest(
isStreamed: Boolean = false,
) : this(method, params, 1, nonce, selectors, isStreamed)
constructor(
method: String,
objParams: Map<Any, Any>,
) : this(method, listOf(), 1, null, null, false, objParams)
fun toJson(): ByteArray {
val json = mapOf(
"jsonrpc" to "2.0",
"id" to id,
"method" to method,
"params" to params,
"params" to (objParams ?: params),
)
return Global.objectMapper.writeValueAsBytes(json)
}