add aztec networks (#785)

* add aztec networks

* fix linter errors & checkout from main (public)

* fix AztecLowerBoundService
This commit is contained in:
Artem Rootman
2026-01-30 18:04:05 +02:00
committed by GitHub
parent 116807054a
commit 4d4a2ed655
10 changed files with 321 additions and 3 deletions

View File

@@ -128,6 +128,7 @@ open class CodeGen(private val config: ChainsConfig) {
private fun type(type: String): String {
return when(type) {
"aztec" -> "BlockchainType.AZTEC"
"eth" -> "BlockchainType.ETHEREUM"
"bitcoin" -> "BlockchainType.BITCOIN"
"starknet" -> "BlockchainType.STARKNET"

View File

@@ -5,6 +5,7 @@ enum class BlockchainType(
) {
UNKNOWN(ApiType.JSON_RPC),
BITCOIN(ApiType.JSON_RPC),
AZTEC(ApiType.JSON_RPC),
ETHEREUM(ApiType.JSON_RPC),
STARKNET(ApiType.JSON_RPC),
POLKADOT(ApiType.JSON_RPC),
@@ -19,4 +20,4 @@ enum class BlockchainType(
enum class ApiType {
JSON_RPC, REST;
}
}

View File

@@ -1,5 +1,6 @@
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.BlockchainType.AZTEC
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
import io.emeraldpay.dshackle.BlockchainType.COSMOS
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
@@ -16,6 +17,7 @@ import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.foundation.ChainOptions
import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.calls.DefaultAztecMethods
import io.emeraldpay.dshackle.upstream.calls.DefaultBeaconChainMethods
import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods
import io.emeraldpay.dshackle.upstream.calls.DefaultCosmosMethods
@@ -47,6 +49,7 @@ class CallTargetsHolder {
): CallMethods {
val created = when (chain.type) {
BITCOIN -> DefaultBitcoinMethods(options.providesBalance == true)
AZTEC -> DefaultAztecMethods()
ETHEREUM -> DefaultEthereumMethods(chain)
STARKNET -> DefaultStarknetMethods(chain)
POLKADOT -> DefaultPolkadotMethods(chain)

View File

@@ -0,0 +1,177 @@
package io.emeraldpay.dshackle.upstream.aztec
import com.fasterxml.jackson.databind.JsonNode
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.ValidateUpstreamSettingsResult
import io.emeraldpay.dshackle.upstream.generic.AbstractPollChainSpecific
import io.emeraldpay.dshackle.upstream.lowerbound.LowerBoundService
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
import java.math.BigInteger
import java.time.Instant
object AztecChainSpecific : AbstractPollChainSpecific() {
private val log = LoggerFactory.getLogger(AztecChainSpecific::class.java)
override fun parseBlock(data: ByteArray, upstreamId: String, api: ChainReader): Mono<BlockContainer> {
val root = Global.objectMapper.readTree(data)
val height = parseLong(
findNode(
root,
"number",
"header.number",
"blockNumber",
"header.blockNumber",
"header.globalVariables.blockNumber",
),
) ?: 0L
val hashValue = parseText(findNode(root, "hash", "header.hash", "blockHash", "header.blockHash"))
val parentValue = parseText(
findNode(
root,
"parentHash",
"header.parentHash",
"parent_hash",
"header.parent_hash",
"prevHash",
"header.prevHash",
),
)
val timestamp = parseInstant(
findNode(
root,
"timestamp",
"header.timestamp",
"header.globalVariables.timestamp",
),
) ?: Instant.EPOCH
if (hashValue == null) {
log.warn("Aztec block hash is missing in response from upstream {}", upstreamId)
}
return Mono.just(
BlockContainer(
height = height,
hash = BlockId.from(hashValue ?: "0x0"),
difficulty = BigInteger.ZERO,
timestamp = timestamp,
full = false,
json = data,
parsed = root,
transactions = emptyList(),
upstreamId = upstreamId,
parentHash = parentValue?.let { BlockId.from(it) },
),
)
}
override fun getFromHeader(data: ByteArray, upstreamId: String, api: ChainReader): Mono<BlockContainer> {
throw NotImplementedError()
}
override fun listenNewHeadsRequest(): ChainRequest {
throw NotImplementedError()
}
override fun unsubscribeNewHeadsRequest(subId: Any): ChainRequest {
throw NotImplementedError()
}
override fun upstreamValidators(
chain: Chain,
upstream: Upstream,
options: Options,
config: ChainConfig,
): List<SingleValidator<UpstreamAvailability>> {
return listOf(
GenericSingleCallValidator(
ChainRequest("node_isReady", ListParams()),
upstream,
) { data ->
val raw = Global.objectMapper.readTree(data)
val ready = when {
raw.isBoolean -> raw.asBoolean()
raw.isTextual -> raw.asText().equals("true", ignoreCase = true)
else -> raw.asBoolean(false)
}
if (ready) UpstreamAvailability.OK else UpstreamAvailability.SYNCING
},
)
}
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 AztecLowerBoundService(chain, upstream)
}
override fun latestBlockRequest(): ChainRequest =
ChainRequest("node_getBlock", ListParams("latest"))
private fun findNode(root: JsonNode, vararg paths: String): JsonNode? {
for (path in paths) {
var current: JsonNode? = root
for (part in path.split(".")) {
current = current?.get(part)
if (current == null || current.isMissingNode) {
break
}
}
if (current != null && !current.isMissingNode && !current.isNull) {
return current
}
}
return null
}
private fun parseText(node: JsonNode?): String? {
if (node == null || node.isNull || node.isMissingNode) {
return null
}
return node.asText().ifBlank { null }
}
private fun parseLong(node: JsonNode?): Long? {
if (node == null || node.isNull || node.isMissingNode) {
return null
}
return when {
node.isNumber -> node.asLong()
node.isTextual -> parseNumericString(node.asText())
else -> null
}
}
private fun parseNumericString(value: String): Long? {
val trimmed = value.trim()
if (trimmed.isEmpty()) return null
val isHex = trimmed.startsWith("0x") || trimmed.startsWith("0X")
val raw = if (isHex) trimmed.substring(2) else trimmed
return runCatching { BigInteger(raw, if (isHex) 16 else 10).toLong() }.getOrNull()
}
private fun parseInstant(node: JsonNode?): Instant? {
val ts = parseLong(node) ?: return null
return if (ts >= 1_000_000_000_000L) Instant.ofEpochMilli(ts) else Instant.ofEpochSecond(ts)
}
}

View File

@@ -0,0 +1,15 @@
package io.emeraldpay.dshackle.upstream.aztec
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 AztecLowerBoundService(
chain: Chain,
private val upstream: Upstream,
) : LowerBoundService(chain, upstream) {
override fun detectors(): List<LowerBoundDetector> {
return listOf(AztecLowerBoundStateDetector(upstream.getChain()))
}
}

View File

@@ -0,0 +1,24 @@
package io.emeraldpay.dshackle.upstream.aztec
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 AztecLowerBoundStateDetector(
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)
}
}

View File

@@ -0,0 +1,94 @@
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 DefaultAztecMethods : CallMethods {
private val broadcast = setOf(
"node_sendTx",
)
private val allowedMethods: Set<String> = setOf(
"node_getBlockNumber",
"node_getProvenBlockNumber",
"node_getL2Tips",
"node_getBlock",
"node_getBlocks",
"node_getBlockHeader",
"node_sendTx",
"node_getTxReceipt",
"node_getTxEffect",
"node_getTxByHash",
"node_getPendingTxs",
"node_getPendingTxCount",
"node_isValidTx",
"node_simulatePublicCalls",
"node_getPublicStorageAt",
"node_getWorldStateSyncStatus",
"node_findLeavesIndexes",
"node_getNullifierSiblingPath",
"node_getNoteHashSiblingPath",
"node_getArchiveSiblingPath",
"node_getPublicDataSiblingPath",
"node_getNullifierMembershipWitness",
"node_getLowNullifierMembershipWitness",
"node_getPublicDataWitness",
"node_getArchiveMembershipWitness",
"node_getNoteHashMembershipWitness",
"node_getL1ToL2MessageMembershipWitness",
"node_getL1ToL2MessageBlock",
"node_isL1ToL2MessageSynced",
"node_getL2ToL1Messages",
"node_getPrivateLogs",
"node_getPublicLogs",
"node_getContractClassLogs",
"node_getLogsByTags",
"node_getContractClass",
"node_getContract",
"node_isReady",
"node_getNodeInfo",
"node_getNodeVersion",
"node_getVersion",
"node_getChainId",
"node_getL1ContractAddresses",
"node_getProtocolContractAddresses",
"node_getEncodedEnr",
"node_getCurrentBaseFees",
"node_getValidatorsStats",
"node_getValidatorStats",
"node_registerContractFunctionSignatures",
"node_getAllowedPublicSetup",
)
override fun createQuorumFor(method: String): CallQuorum {
return when {
broadcast.contains(method) -> BroadcastQuorum()
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 -> emptySet()
}
override fun getSupportedMethods(): Set<String> {
return allowedMethods.toSortedSet()
}
}

View File

@@ -1,5 +1,6 @@
package io.emeraldpay.dshackle.upstream.generic
import io.emeraldpay.dshackle.BlockchainType.AZTEC
import io.emeraldpay.dshackle.BlockchainType.BITCOIN
import io.emeraldpay.dshackle.BlockchainType.COSMOS
import io.emeraldpay.dshackle.BlockchainType.ETHEREUM
@@ -32,6 +33,7 @@ import io.emeraldpay.dshackle.upstream.UpstreamRpcMethodsDetector
import io.emeraldpay.dshackle.upstream.UpstreamSettingsDetector
import io.emeraldpay.dshackle.upstream.UpstreamValidator
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
import io.emeraldpay.dshackle.upstream.aztec.AztecChainSpecific
import io.emeraldpay.dshackle.upstream.beaconchain.BeaconChainSpecific
import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.calls.CallSelector
@@ -113,6 +115,7 @@ object ChainSpecificRegistry {
@JvmStatic
fun resolve(chain: Chain): ChainSpecific {
return when (chain.type) {
AZTEC -> AztecChainSpecific
ETHEREUM -> EthereumChainSpecific
STARKNET -> StarknetChainSpecific
POLKADOT -> PolkadotChainSpecific