From b3305fdeea8c7e42e89e59ff25b5544dcb153551 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Thu, 13 Oct 2022 17:57:45 +0300 Subject: [PATCH 01/18] fix filter --- .../dshackle/upstream/ethereum/EthereumMultistream.kt | 2 +- .../dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt index fc6426d3..259e88a6 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumMultistream.kt @@ -103,7 +103,7 @@ open class EthereumMultistream( upstreams.filter { matcher.matches(it) }.takeIf { ups -> - ups.all { it.isGrpc() } + ups.isNotEmpty() && ups.all { it.isGrpc() } }?.map { it as GrpcUpstream }?.map { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt index 959b3485..cd872db7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosMultiStream.kt @@ -98,7 +98,7 @@ open class EthereumPosMultiStream( upstreams.filter { matcher.matches(it) }.takeIf { ups -> - ups.all { it.isGrpc() } + ups.isNotEmpty() && ups.all { it.isGrpc() } }?.map { it as GrpcUpstream }?.map { From 4d3826d29b3ed4d13e2b1e91e921791b30305628 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Mon, 17 Oct 2022 10:49:57 +0300 Subject: [PATCH 02/18] add TLS support --- dshackle-cli/README.md | 23 +++++++++++++++++++---- dshackle-cli/package-lock.json | 4 ++-- dshackle-cli/package.json | 2 +- dshackle-cli/src/cli.js | 12 +++++++++--- dshackle-cli/src/grpc-clent.js | 15 +++++++++++++-- 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/dshackle-cli/README.md b/dshackle-cli/README.md index 2aba0f3c..b43b6f94 100644 --- a/dshackle-cli/README.md +++ b/dshackle-cli/README.md @@ -1,21 +1,36 @@ # DSHACKLE CLI -A CLI tool to verify the [dshacle](https://github.com/emeraldpay/dshackle) installation. +A CLI tool to verify the [dshackle](https://github.com/emeraldpay/dshackle) installation. ## Usage ``` -npx dchackle-cli-tool [-p|--print] +npx dshackle-cli-tool [-p|--print][--ca][--cert][--key] ``` ### Options --p | --print - will print the describe response as is +```-p | --print``` will print the describe response as is + +### TLS +```--ca``` the root certificate data + +```--cert``` the client certificate key chain, if available +```--key``` the client certificate private key, if available ## Example ``` -> dchackle-cli-tool localhost:2449 +> dshackle-cli-tool localhost:2449 Connecting to: localhost:2449... Connected to localhost:2449 CHAIN_ETHEREUM -> AVAIL_OK CHAIN_KOVAN -> AVAIL_OK +``` + +With TLS +``` +>dshackle-cli % dshackle-cli-tool --ca ./../out/ca.myhost.dev.crt --cert ./../out/client_1.crt --key ./../out/client_1.key 127.0.0.1:2450 +Using TLS +Connecting to: 127.0.0.1:2450... +Connected to 127.0.0.1:2450 +CHAIN_ETHEREUM -> AVAIL_OK ``` \ No newline at end of file diff --git a/dshackle-cli/package-lock.json b/dshackle-cli/package-lock.json index edee9a4a..f19665b2 100644 --- a/dshackle-cli/package-lock.json +++ b/dshackle-cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "dshackle-cli-tool", - "version": "1.0.3", + "version": "1.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "dshackle-cli-tool", - "version": "1.0.3", + "version": "1.0.7", "license": "MIT", "dependencies": { "@grpc/grpc-js": "^1.6.12", diff --git a/dshackle-cli/package.json b/dshackle-cli/package.json index 29bcd25e..55836293 100644 --- a/dshackle-cli/package.json +++ b/dshackle-cli/package.json @@ -1,6 +1,6 @@ { "name": "dshackle-cli-tool", - "version": "1.0.4", + "version": "1.0.7", "description": "", "main": "src/index.js", "scripts": { diff --git a/dshackle-cli/src/cli.js b/dshackle-cli/src/cli.js index 9f408310..ca83abc8 100644 --- a/dshackle-cli/src/cli.js +++ b/dshackle-cli/src/cli.js @@ -6,10 +6,10 @@ import util from "util"; export function cli(args) { let opts = parseArgumentsIntoOptions(args); if (!opts.url) { - console.log("Err: URL not specified!") + console.log("Err: URL not specified!!!") return } - describe(opts.url, (error, response) => { + describe(opts.url, opts.ca, opts.cert, opts.key, (error, response) => { if (error) { console.error(clc.red('Connection to ' + opts.url + ' failed! [' + error.message + ']')); } else { @@ -42,6 +42,9 @@ function parseArgumentsIntoOptions(rawArgs) { const args = arg( { '--print': Boolean, + '--ca': String, + '--cert': String, + '--key': String, '-p': '--print' }, { @@ -50,7 +53,10 @@ function parseArgumentsIntoOptions(rawArgs) { ); return { print: args['--print'] || false, - url: args._[0] + url: args._[0], + ca: args['--ca'], + cert: args['--cert'], + key: args['--key'] }; } diff --git a/dshackle-cli/src/grpc-clent.js b/dshackle-cli/src/grpc-clent.js index f4a32c64..4f435d2b 100644 --- a/dshackle-cli/src/grpc-clent.js +++ b/dshackle-cli/src/grpc-clent.js @@ -1,6 +1,7 @@ const grpc = require("@grpc/grpc-js"); const path = require('path') const protoLoader = require("@grpc/proto-loader"); +const fs = require('fs'); const PROTO_PATH = path.join(__dirname, "../grpc/proto/blockchain.proto"); @@ -15,11 +16,21 @@ const options = { const packageDefinition = protoLoader.loadSync(PROTO_PATH, options); const emerald = grpc.loadPackageDefinition(packageDefinition).emerald -export function describe(url, handler) { +export function describe(url, ca, cert, key, handler) { + let credentials = grpc.credentials.createInsecure() + if (ca || cert || key) { + console.log("Using TLS") + credentials = grpc.credentials.createSsl( + ca ? fs.readFileSync(ca) : null, + key ? fs.readFileSync(key) : null, + cert ? fs.readFileSync(cert) : null + ); + } const client = new emerald.Blockchain( url, - grpc.credentials.createInsecure() + credentials ); + console.log('Connecting to: ' + url + '...') client.Describe({}, handler); } From 30f1c37901b01455068c7bb77b9eb6095b7fef70 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Tue, 18 Oct 2022 19:20:05 +0300 Subject: [PATCH 03/18] fix chain matcher --- .../io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index 250361d9..e984e91b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -110,7 +110,7 @@ open class ConfiguredUpstreams( fun hasMatchingUpstream(chain: Chain, matcher: Selector.LabelSelectorMatcher): Boolean = config.upstreams.any { up -> - up.chain?.equals(chain.chainName, ignoreCase = true) ?: true && matcher.matches(up.labels) + (up.chain?.let { Global.chainById(it) == chain } ?: true) && matcher.matches(up.labels) } private fun buildDefaultOptions(config: UpstreamsConfig): HashMap { From 4ff3965b79347ec42c5d394c677fc172708802e7 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Wed, 26 Oct 2022 05:10:16 +0300 Subject: [PATCH 04/18] eth_filter methods support --- .../dshackle/quorum/AlwaysQuorum.kt | 6 ++ .../emeraldpay/dshackle/quorum/CallQuorum.kt | 1 + .../dshackle/quorum/NotLaggingQuorum.kt | 5 ++ .../dshackle/quorum/QuorumRpcReader.kt | 7 +- .../dshackle/quorum/ValueAwareQuorum.kt | 6 ++ .../io/emeraldpay/dshackle/rpc/NativeCall.kt | 76 +++++++++++++++-- .../dshackle/startup/ConfiguredUpstreams.kt | 81 +++++++++++++++++-- .../dshackle/upstream/DefaultUpstream.kt | 8 +- .../dshackle/upstream/Multistream.kt | 2 + .../emeraldpay/dshackle/upstream/Selector.kt | 12 +++ .../emeraldpay/dshackle/upstream/Upstream.kt | 2 + .../upstream/bitcoin/BitcoinUpstream.kt | 2 +- .../upstream/calls/DefaultEthereumMethods.kt | 10 ++- .../upstream/calls/EthereumCallSelector.kt | 18 +++++ .../upstream/ethereum/EthereumRpcUpstream.kt | 3 +- .../upstream/ethereum/EthereumUpstream.kt | 3 +- .../ethereum_pos/EthereumPosRpcUpstream.kt | 3 +- .../ethereum_pos/EthereumPosUpstream.kt | 3 +- .../upstream/grpc/EthereumGrpcUpstream.kt | 2 + .../upstream/grpc/EthereumPosGrpcUpstream.kt | 2 + .../dshackle/upstream/grpc/GrpcUpstreams.kt | 5 +- .../dshackle/rpc/NativeCallSpec.groovy | 2 +- .../test/EthereumPosRpcUpstreamMock.groovy | 2 +- .../test/EthereumRpcUpstreamMock.groovy | 2 +- .../dshackle/upstream/FilteredApisSpec.groovy | 1 + .../ethereum/EthereumDirectReaderSpec.groovy | 16 ++-- .../grpc/EthereumGrpcUpstreamSpec.groovy | 8 +- 27 files changed, 251 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt index a2b5d1dc..74ba4456 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/AlwaysQuorum.kt @@ -21,6 +21,7 @@ import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException import io.emeraldpay.dshackle.upstream.signature.ResponseSigner +import java.util.concurrent.ConcurrentLinkedQueue open class AlwaysQuorum : CallQuorum { @@ -28,6 +29,7 @@ open class AlwaysQuorum : CallQuorum { private var result: ByteArray? = null private var rpcError: JsonRpcError? = null private var sig: ResponseSigner.Signature? = null + private val resolvers: MutableCollection = ConcurrentLinkedQueue() override fun init(head: Head) { } @@ -48,6 +50,7 @@ open class AlwaysQuorum : CallQuorum { result = response resolved = true sig = signature + resolvers.add(upstream) return true } @@ -64,6 +67,9 @@ open class AlwaysQuorum : CallQuorum { return rpcError } + override fun getResolvedBy(): List = + resolvers.toList() + override fun toString(): String { return "Quorum: Accept Any" } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt index 58122631..e89f2920 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/CallQuorum.kt @@ -34,4 +34,5 @@ interface CallQuorum { fun getSignature(): ResponseSigner.Signature? fun getResult(): ByteArray? fun getError(): JsonRpcError? + fun getResolvedBy(): Collection } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt index 89b6bbf2..980f3c50 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/NotLaggingQuorum.kt @@ -21,6 +21,7 @@ import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException import io.emeraldpay.dshackle.upstream.signature.ResponseSigner +import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicReference /** @@ -34,6 +35,7 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum { private val failed = AtomicReference(false) private var rpcError: JsonRpcError? = null private var sig: ResponseSigner.Signature? = null + private val resolvers: MutableCollection = ConcurrentLinkedQueue() override fun init(head: Head) { } @@ -51,6 +53,7 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum { if (!lagging) { result.set(response) sig = signature + resolvers.add(upstream) return true } return false @@ -75,6 +78,8 @@ class NotLaggingQuorum(val maxLag: Long = 0) : CallQuorum { return rpcError } + override fun getResolvedBy(): Collection = + resolvers.toList() override fun toString(): String { return "Quorum: late <= $maxLag blocks" } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt index 3b9c9694..7bba2d55 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt @@ -117,9 +117,9 @@ class QuorumRpcReader( return Function { quorumResult -> quorumResult .filter { it.isResolved() } // return nothing if not resolved - .map { + .map { quorum -> // TODO find actual quorum number - QuorumRpcReader.Result(it.getResult()!!, it.getSignature(), 1) + Result(quorum.getResult()!!, quorum.getSignature(), 1, quorum.getResolvedBy().map { it.hash() }) } .switchIfEmpty(defaultResult) } @@ -197,6 +197,7 @@ class QuorumRpcReader( class Result( val value: ByteArray, val signature: ResponseSigner.Signature?, - val quorum: Int + val quorum: Int, + val resolvers: Collection ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt index 2e6233e6..bb8c5d3b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/ValueAwareQuorum.kt @@ -23,6 +23,7 @@ import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException import io.emeraldpay.dshackle.upstream.signature.ResponseSigner import io.emeraldpay.etherjar.rpc.RpcException import org.slf4j.LoggerFactory +import java.util.concurrent.ConcurrentLinkedQueue abstract class ValueAwareQuorum( val clazz: Class @@ -30,6 +31,7 @@ abstract class ValueAwareQuorum( private val log = LoggerFactory.getLogger(ValueAwareQuorum::class.java) private var rpcError: JsonRpcError? = null + private val resolvers: MutableCollection = ConcurrentLinkedQueue() fun extractValue(response: ByteArray, clazz: Class): T? { return Global.objectMapper.readValue(response.inputStream(), clazz) @@ -39,6 +41,7 @@ abstract class ValueAwareQuorum( try { val value = extractValue(response, clazz) recordValue(response, value, signature, upstream) + resolvers.add(upstream) } catch (e: RpcException) { recordError(response, e.rpcMessage, signature, upstream) } catch (e: Exception) { @@ -59,4 +62,7 @@ abstract class ValueAwareQuorum( override fun getError(): JsonRpcError? { return rpcError } + + override fun getResolvedBy(): Collection = + resolvers.toList() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 024030a8..415af364 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -102,7 +102,8 @@ open class NativeCall( } fun parseParams(it: ValidCallContext): ValidCallContext { - val params = extractParams(it.payload.params) + val rawParams = extractParams(it.payload.params) + val params = it.requestDecorator.processRequest(rawParams) return it.withPayload(ParsedCallDetails(it.payload.method, params)) } @@ -234,17 +235,28 @@ open class NativeCall( matcher.withMatcher(heightMatcher) } val nonce = requestItem.nonce.let { if (it == 0L) null else it } + val requestDecorator = getRequestDecorator(requestItem.method) + val resultDecorator = getResultDecorator(requestItem.method) + ValidCallContext( requestItem.id, nonce, upstream, matcher.build(), callQuorum, - RawCallDetails(method, params) + RawCallDetails(method, params), + requestDecorator, + resultDecorator ) } } + private fun getRequestDecorator(method: String): RequestDecorator = + if (method == "eth_getFilterChanges") GetFilterUpdatesDecorator() else NoneRequestDecorator() + + private fun getResultDecorator(method: String): ResultDecorator = + if (CreateFilterDecorator.createFilterMethods.contains(method)) CreateFilterDecorator() else NoneResultDecorator() + fun fetch(ctx: ValidCallContext): Mono { return ctx.upstream.getRoutedApi(ctx.matcher) .flatMap { api -> @@ -283,7 +295,8 @@ open class NativeCall( return reader .read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce)) .map { - CallResult(ctx.id, ctx.nonce, it.value, null, it.signature) + val bytes = ctx.resultDecorator.processResult(it) + CallResult(ctx.id, ctx.nonce, bytes, null, it.signature) } .onErrorResume { t -> val failure = when (t) { @@ -350,14 +363,67 @@ open class NativeCall( fun getError(): CallError } + interface ResultDecorator { + fun processResult(result: QuorumRpcReader.Result): ByteArray + } + + open class NoneResultDecorator : ResultDecorator { + override fun processResult(result: QuorumRpcReader.Result): ByteArray = result.value + } + + open class CreateFilterDecorator : ResultDecorator { + + companion object { + const val quoteCode = '"'.code.toByte() + val createFilterMethods = listOf("eth_getFilterChanges", "eth_newFilter", "eth_newBlockFilter") + } + override fun processResult(result: QuorumRpcReader.Result): ByteArray { + val bytes = result.value + if (bytes.last() == quoteCode) { + val suffix = result.resolvers.first().toUByte().toString(16).padStart(2, padChar = '0').toByteArray() + bytes[bytes.lastIndex] = suffix.first() + return bytes + suffix.last() + quoteCode + } + return bytes + } + } + + interface RequestDecorator { + fun processRequest(request: List): List + } + + open class NoneRequestDecorator : RequestDecorator { + override fun processRequest(request: List): List = request + } + + open class GetFilterUpdatesDecorator : RequestDecorator { + override fun processRequest(request: List): List { + val filterId = request.first().toString() + val sanitized = filterId.substring(0, filterId.lastIndex - 1) + return listOf(sanitized) + } + } + open class ValidCallContext( val id: Int, val nonce: Long?, val upstream: Multistream, val matcher: Selector.Matcher, val callQuorum: CallQuorum, - val payload: T + val payload: T, + val requestDecorator: RequestDecorator, + val resultDecorator: ResultDecorator ) : CallContext { + + constructor( + id: Int, + nonce: Long?, + upstream: Multistream, + matcher: Selector.Matcher, + callQuorum: CallQuorum, + payload: T + ) : this(id, nonce, upstream, matcher, callQuorum, payload, NoneRequestDecorator(), NoneResultDecorator()) + override fun isValid(): Boolean { return true } @@ -371,7 +437,7 @@ open class NativeCall( } fun withPayload(payload: X): ValidCallContext { - return ValidCallContext(id, nonce, upstream, matcher, callQuorum, payload) + return ValidCallContext(id, nonce, upstream, matcher, callQuorum, payload, requestDecorator, resultDecorator) } fun getApis(): ApiSource { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index e984e91b..740dfc11 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -53,7 +53,9 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository import java.net.URI import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Function import javax.annotation.PostConstruct +import kotlin.math.abs @Repository open class ConfiguredUpstreams( @@ -65,6 +67,8 @@ open class ConfiguredUpstreams( private val log = LoggerFactory.getLogger(ConfiguredUpstreams::class.java) private var seq = AtomicInteger(0) + private val hashes: MutableMap = HashMap() + @PostConstruct fun start() { log.debug("Starting upstreams") @@ -90,12 +94,19 @@ open class ConfiguredUpstreams( BlockchainType.ETHEREUM -> { buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options) } + BlockchainType.BITCOIN -> { buildBitcoinUpstream(up.cast(UpstreamsConfig.BitcoinConnection::class.java), chain, options) } + BlockchainType.ETHEREUM_POS -> { - buildEthereumPosUpstream(up.cast(UpstreamsConfig.EthereumPosConnection::class.java), chain, options) + buildEthereumPosUpstream( + up.cast(UpstreamsConfig.EthereumPosConnection::class.java), + chain, + options + ) } + else -> { log.error("Chain is unsupported: ${up.chain}") return@forEach @@ -167,13 +178,26 @@ open class ConfiguredUpstreams( return null } val urls = ArrayList() - val connectorFactory = buildEthereumConnectorFactory(config.id!!, execution, chain, urls, NoChoiceWithPriorityForkChoice(conn.upstreamRating), BlockValidator.ALWAYS_VALID) + val connectorFactory = buildEthereumConnectorFactory( + config.id!!, + execution, + chain, + urls, + NoChoiceWithPriorityForkChoice(conn.upstreamRating), + BlockValidator.ALWAYS_VALID + ) val methods = buildMethods(config, chain) if (connectorFactory == null) { return null } + + val hashUrl = conn.execution!!.let { + if (it.preferHttp) it.rpc?.url ?: it.ws?.url else it.ws?.url ?: it.rpc?.url + } + val hash = getHash(hashUrl!!) val upstream = EthereumPosRpcUpstream( config.id!!, + hash, chain, options, config.role, methods, @@ -236,12 +260,22 @@ open class ConfiguredUpstreams( val urls = ArrayList() val methods = buildMethods(config, chain) - val connectorFactory = buildEthereumConnectorFactory(config.id!!, conn, chain, urls, MostWorkForkChoice(), EthereumBlockValidator()) + val connectorFactory = buildEthereumConnectorFactory( + config.id!!, + conn, + chain, + urls, + MostWorkForkChoice(), + EthereumBlockValidator() + ) if (connectorFactory == null) { return null } + + val hashUrl = if (conn.preferHttp) conn.rpc?.url ?: conn.ws?.url else conn.ws?.url ?: conn.rpc?.url val upstream = EthereumRpcUpstream( config.id!!, + getHash(hashUrl!!), chain, options, config.role, methods, @@ -257,8 +291,10 @@ open class ConfiguredUpstreams( options: UpstreamsConfig.Options ) { val endpoint = config.connection!! + val hash = getHash("${endpoint.host}:${endpoint.port}") val ds = GrpcUpstreams( config.id!!, + hash, config.role, endpoint.host!!, endpoint.port, @@ -289,7 +325,12 @@ open class ConfiguredUpstreams( } } - private fun buildWsFactory(id: String, chain: Chain, conn: UpstreamsConfig.EthereumConnection, urls: ArrayList? = null): EthereumWsFactory? { + private fun buildWsFactory( + id: String, + chain: Chain, + conn: UpstreamsConfig.EthereumConnection, + urls: ArrayList? = null + ): EthereumWsFactory? { return conn.ws?.let { endpoint -> val wsApi = EthereumWsFactory( id, chain, @@ -305,15 +346,43 @@ open class ConfiguredUpstreams( } } - private fun buildEthereumConnectorFactory(id: String, conn: UpstreamsConfig.EthereumConnection, chain: Chain, urls: ArrayList, forkChoice: ForkChoice, blockValidator: BlockValidator): EthereumConnectorFactory? { + private fun buildEthereumConnectorFactory( + id: String, + conn: UpstreamsConfig.EthereumConnection, + chain: Chain, + urls: ArrayList, + forkChoice: ForkChoice, + blockValidator: BlockValidator + ): EthereumConnectorFactory? { val wsFactoryApi = buildWsFactory(id, chain, conn, urls) val httpFactory = buildHttpFactory(conn, urls) log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}") - val connectorFactory = EthereumConnectorFactory(conn.preferHttp, wsFactoryApi, httpFactory, forkChoice, blockValidator) + val connectorFactory = + EthereumConnectorFactory(conn.preferHttp, wsFactoryApi, httpFactory, forkChoice, blockValidator) if (!connectorFactory.isValid()) { log.warn("Upstream configuration is invalid (probably no http endpoint)") return null } return connectorFactory } + + private fun getHash(obj: Any): Byte { + val hashCode = (obj.hashCode() % 255) + val modifiers: List> = listOf( + Function { i -> i }, + Function { i -> (-i) }, + Function { i -> 127 - abs(i) }, + Function { i -> abs(i) - 128 }, + ) + return modifiers.map { + it.apply(hashCode).toByte() + }.firstOrNull { + hashes[it] != true + }?.let { + hashes[it] = true + it + } ?: (Byte.MIN_VALUE..Byte.MAX_VALUE).first { + hashes[it.toByte()] != true + }.toByte() + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt index 36470987..30db6578 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicReference abstract class DefaultUpstream( private val id: String, + private val hash: Byte, defaultLag: Long, defaultAvail: UpstreamAvailability, private val options: UpstreamsConfig.Options, @@ -36,12 +37,14 @@ abstract class DefaultUpstream( constructor( id: String, + hash: Byte, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods? ) : this( id, + hash, Long.MAX_VALUE, UpstreamAvailability.UNAVAILABLE, options, @@ -52,12 +55,13 @@ abstract class DefaultUpstream( constructor( id: String, + hash: Byte, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, node: QuorumForLabels.QuorumItem? ) : - this(id, Long.MAX_VALUE, UpstreamAvailability.UNAVAILABLE, options, role, targets, node) + this(id, hash, Long.MAX_VALUE, UpstreamAvailability.UNAVAILABLE, options, role, targets, node) private val status = AtomicReference(Status(defaultLag, defaultAvail, statusByLag(defaultLag, defaultAvail))) private val statusStream = Sinks.many() @@ -139,6 +143,8 @@ abstract class DefaultUpstream( return targets ?: throw IllegalStateException("Methods are not set") } + override fun hash(): Byte = hash + private val quorumByLabel = node?.let { QuorumForLabels(it) } ?: QuorumForLabels(QuorumForLabels.QuorumItem.empty()) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt index 6319cede..2c5f1fc3 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt @@ -282,6 +282,8 @@ abstract class Multistream( return false } + override fun hash(): Byte = 0 + fun printStatus() { var height: Long? = null try { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt index 4f57a7f3..e92647fb 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt @@ -396,4 +396,16 @@ class Selector { return "Matcher: ${describeInternal()}" } } + + class SameUpstreamMatcher(private val upstreamHash: Byte) : Matcher { + override fun matches(up: Upstream): Boolean = + up.hash() == upstreamHash + + override fun describeInternal(): String = + "upstream hash=$upstreamHash" + + override fun toString(): String { + return "Matcher: ${describeInternal()}" + } + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt index d170acbc..b51b9192 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt @@ -40,4 +40,6 @@ interface Upstream { fun isGrpc(): Boolean fun cast(selfType: Class): T + + fun hash(): Byte } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstream.kt index 6e02efff..b09709ff 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstream.kt @@ -31,7 +31,7 @@ abstract class BitcoinUpstream( callMethods: CallMethods, node: QuorumForLabels.QuorumItem, val esploraClient: EsploraClient? = null -) : DefaultUpstream(id, options, role, callMethods, node) { +) : DefaultUpstream(id, 0.toByte(), options, role, callMethods, node) { constructor( id: String, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt index 66bce62a..d663857c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt @@ -70,7 +70,14 @@ class DefaultEthereumMethods( "eth_feeHistory" ) - private val allowedMethods = anyResponseMethods + firstValueMethods + specialMethods + headVerifiedMethods + private val filterMethods = listOf( + "eth_getFilterChanges", + "eth_newFilter", + "eth_newBlockFilter", + "eth_newPendingTransactionFilter", + ) + + private val allowedMethods = anyResponseMethods + firstValueMethods + specialMethods + headVerifiedMethods + filterMethods private val hardcodedMethods = listOf( "net_version", @@ -88,6 +95,7 @@ class DefaultEthereumMethods( override fun getQuorumFor(method: String): CallQuorum { return when { + filterMethods.contains(method) -> AlwaysQuorum() hardcodedMethods.contains(method) -> AlwaysQuorum() firstValueMethods.contains(method) -> AlwaysQuorum() anyResponseMethods.contains(method) -> NotLaggingQuorum(4) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt index 32b00dcb..d95e0d95 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt @@ -57,10 +57,28 @@ class EthereumCallSelector( return blockTagSelector(params, 1, head) } else if (method == "eth_getStorageAt") { return blockTagSelector(params, 2, head) + } else if (method == "eth_getFilterChanges") { + return sameUpstreamMatcher(params) } return Mono.empty() } + private fun sameUpstreamMatcher(params: String): Mono { + val list = objectMapper.readerFor(Any::class.java).readValues(params).readAll() + if (list.isEmpty()) { + return Mono.empty() + } + val filterId = list[0].toString() + val hashHex = filterId.substring(filterId.length - 2) + val hash = hashHex.toInt(16) + + if (hash < 0 || hash > 255) { + return Mono.empty() + } + + return Mono.just(Selector.SameUpstreamMatcher(hash.toByte())) + } + private fun blockTagSelector(params: String, pos: Int, head: Head): Mono { val list = objectMapper.readerFor(Any::class.java).readValues(params).readAll() if (list.size < pos + 1) { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt index b071af41..3bbe468f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt @@ -36,13 +36,14 @@ import reactor.core.Disposable open class EthereumRpcUpstream( id: String, + hash: Byte, val chain: Chain, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, private val node: QuorumForLabels.QuorumItem?, connectorFactory: ConnectorFactory -) : EthereumUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { +) : EthereumUpstream(id, hash, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { private val log = LoggerFactory.getLogger(EthereumRpcUpstream::class.java) private val validator: EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) private val connector: EthereumConnector = connectorFactory.create(this, validator, chain) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt index c14bd120..a06c7cb7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt @@ -24,11 +24,12 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods abstract class EthereumUpstream( id: String, + hash: Byte, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, private val node: QuorumForLabels.QuorumItem? -) : DefaultUpstream(id, options, role, targets, node) { +) : DefaultUpstream(id, hash, options, role, targets, node) { private val capabilities = if (options.providesBalance != false) { setOf(Capability.RPC, Capability.BALANCE) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt index 7cd6cca6..50f201c2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosRpcUpstream.kt @@ -36,13 +36,14 @@ import reactor.core.Disposable open class EthereumPosRpcUpstream( id: String, + hash: Byte, val chain: Chain, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, private val node: QuorumForLabels.QuorumItem?, connectorFactory: ConnectorFactory -) : EthereumPosUpstream(id, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { +) : EthereumPosUpstream(id, hash, options, role, targets, node), Lifecycle, Upstream, CachesEnabled { private val log = LoggerFactory.getLogger(EthereumPosRpcUpstream::class.java) private val validator: EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions()) private val connector: EthereumConnector = connectorFactory.create(this, validator, chain) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt index 4ce8253a..b33ac76c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum_pos/EthereumPosUpstream.kt @@ -24,11 +24,12 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods abstract class EthereumPosUpstream( id: String, + hash: Byte, options: UpstreamsConfig.Options, role: UpstreamsConfig.UpstreamRole, targets: CallMethods?, private val node: QuorumForLabels.QuorumItem? -) : DefaultUpstream(id, options, role, targets, node) { +) : DefaultUpstream(id, hash, options, role, targets, node) { private val capabilities = if (options.providesBalance != false) { setOf(Capability.RPC, Capability.BALANCE) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt index ab4084c5..d407d3b5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt @@ -51,6 +51,7 @@ import java.util.function.Function open class EthereumGrpcUpstream( private val parentId: String, + hash: Byte, role: UpstreamsConfig.UpstreamRole, private val chain: Chain, private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, @@ -58,6 +59,7 @@ open class EthereumGrpcUpstream( overrideLabels: UpstreamsConfig.Labels? ) : EthereumUpstream( "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", + hash, UpstreamsConfig.Options.getDefaults(), role, null, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt index 43acab2f..10a005cc 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumPosGrpcUpstream.kt @@ -51,6 +51,7 @@ import java.util.function.Function open class EthereumPosGrpcUpstream( private val parentId: String, + hash: Byte, role: UpstreamsConfig.UpstreamRole, private val chain: Chain, private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, @@ -59,6 +60,7 @@ open class EthereumPosGrpcUpstream( overrideLabels: UpstreamsConfig.Labels? ) : EthereumPosUpstream( "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", + hash, UpstreamsConfig.Options.getDefaults(), role, null, null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt index 446dd487..3cb4df36 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt @@ -52,6 +52,7 @@ import kotlin.concurrent.withLock class GrpcUpstreams( private val id: String, + private val hash: Byte, private val role: UpstreamsConfig.UpstreamRole, private val host: String, private val port: Int, @@ -206,7 +207,7 @@ class GrpcUpstreams( val current = known[chain] return if (current == null) { val rpcClient = JsonRpcGrpcClient(client!!, chain, metrics) - val created = EthereumGrpcUpstream(id, role, chain, client!!, rpcClient, labels) + val created = EthereumGrpcUpstream(id, hash, role, chain, client!!, rpcClient, labels) created.timeout = this.timeout known[chain] = created created.start() @@ -222,7 +223,7 @@ class GrpcUpstreams( val current = known[chain] return if (current == null) { val rpcClient = JsonRpcGrpcClient(client!!, chain, metrics) - val created = EthereumPosGrpcUpstream(id, role, chain, client!!, rpcClient, nodeRating, labels) + val created = EthereumPosGrpcUpstream(id, hash, role, chain, client!!, rpcClient, nodeRating, labels) created.timeout = this.timeout known[chain] = created created.start() diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 8a7c9e1d..401af57d 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -118,7 +118,7 @@ class NativeCallSpec extends Specification { def nativeCall = nativeCall() nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { 1 * create(_, _, _) >> Mock(Reader) { - 1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"foo\"".bytes, null, 1)) + 1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"foo\"".bytes, null, 1, Collections.singletonList((byte) 1))) } } def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumPosRpcUpstreamMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumPosRpcUpstreamMock.groovy index 7c1f4954..e611b3d9 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumPosRpcUpstreamMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumPosRpcUpstreamMock.groovy @@ -69,7 +69,7 @@ class EthereumPosRpcUpstreamMock extends EthereumPosRpcUpstream { } EthereumPosRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods, Map labels) { - super(id, chain, + super(id, (byte)id.hashCode(), chain, UpstreamsConfig.Options.getDefaults(), UpstreamsConfig.UpstreamRole.PRIMARY, methods, diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumRpcUpstreamMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumRpcUpstreamMock.groovy index 89bfa684..369dc612 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumRpcUpstreamMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumRpcUpstreamMock.groovy @@ -60,7 +60,7 @@ class EthereumRpcUpstreamMock extends EthereumRpcUpstream { } EthereumRpcUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull Reader api, CallMethods methods) { - super(id, chain, + super(id, id.hashCode().byteValue(), chain, UpstreamsConfig.Options.getDefaults(), UpstreamsConfig.UpstreamRole.PRIMARY, methods, diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy index d86b035d..ecec9e4b 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/FilteredApisSpec.groovy @@ -51,6 +51,7 @@ class FilteredApisSpec extends Specification { def connectorFactory = new EthereumConnectorFactory(false, null, httpFactory, new MostWorkForkChoice(), BlockValidator.@Companion.ALWAYS_VALID) new EthereumRpcUpstream( "test", + (byte)123, Chain.ETHEREUM, new UpstreamsConfig.Options(), UpstreamsConfig.UpstreamRole.PRIMARY, diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy index ac2781f5..78bcbe27 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy @@ -30,6 +30,7 @@ class EthereumDirectReaderSpec extends Specification { String hash1 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5" String address1 = "0xe0aadb0a012dbcdc529c4c743d3e0385a0b54d3d" + List resolvers = Collections.singletonList((byte)1) def "Reads block by hash"() { setup: @@ -53,8 +54,7 @@ class EthereumDirectReaderSpec extends Specification { 1 * create(_, _, _) >> Mock(Reader) { 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just( new QuorumRpcReader.Result( - Global.objectMapper.writeValueAsBytes(json), null, 1 - ) + Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers) ) } } @@ -84,7 +84,7 @@ class EthereumDirectReaderSpec extends Specification { 1 * create(_, _, _) >> Mock(Reader) { 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just( new QuorumRpcReader.Result( - Global.objectMapper.writeValueAsBytes(null), null, 1 + Global.objectMapper.writeValueAsBytes(null), null, 1, resolvers ) ) } @@ -119,7 +119,7 @@ class EthereumDirectReaderSpec extends Specification { 1 * create(_, _, _) >> Mock(Reader) { 1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just( new QuorumRpcReader.Result( - Global.objectMapper.writeValueAsBytes(json), null, 1 + Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers ) ) } @@ -155,7 +155,7 @@ class EthereumDirectReaderSpec extends Specification { 1 * create(_, _, _) >> Mock(Reader) { 1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just( new QuorumRpcReader.Result( - Global.objectMapper.writeValueAsBytes(json), null, 1 + Global.objectMapper.writeValueAsBytes(json), null, 1, resolvers ) ) } @@ -186,7 +186,7 @@ class EthereumDirectReaderSpec extends Specification { 1 * create(_, _, _) >> Mock(Reader) { 1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just( new QuorumRpcReader.Result( - Global.objectMapper.writeValueAsBytes(null), null, 1 + Global.objectMapper.writeValueAsBytes(null), null, 1, resolvers ) ) } @@ -217,7 +217,7 @@ class EthereumDirectReaderSpec extends Specification { 1 * create(_, _, _) >> Mock(Reader) { 1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just( new QuorumRpcReader.Result( - Global.objectMapper.writeValueAsBytes("0x100"), null, 1 + Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolvers ) ) } @@ -249,7 +249,7 @@ class EthereumDirectReaderSpec extends Specification { 1 * create(_, _, _) >> Mock(Reader) { 1 * read(new JsonRpcRequest("eth_getBalance", [address1, "0xa8c9bb"])) >> Mono.just( new QuorumRpcReader.Result( - Global.objectMapper.writeValueAsBytes("0x100"), null, 1 + Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolvers ) ) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy index 5418684b..af98978b 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy @@ -50,6 +50,8 @@ class EthereumGrpcUpstreamSpec extends Specification { Counter.builder("test2").register(TestingCommons.meterRegistry) ) + def hash = (byte)123 + def "Subscribe to head"() { setup: def callData = [:] @@ -81,7 +83,7 @@ class EthereumGrpcUpstreamSpec extends Specification { ) } }) - def upstream = new EthereumGrpcUpstream("test", UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null) + def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null) upstream.setLag(0) upstream.update(BlockchainOuterClass.DescribeChain.newBuilder() .setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId)) @@ -139,7 +141,7 @@ class EthereumGrpcUpstreamSpec extends Specification { ) } }) - def upstream = new EthereumGrpcUpstream("test", UpstreamsConfig.UpstreamRole.PRIMARY, Chain.ETHEREUM, client, new JsonRpcGrpcClient(client, Chain.ETHEREUM, metrics), null) + def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, Chain.ETHEREUM, client, new JsonRpcGrpcClient(client, Chain.ETHEREUM, metrics), null) upstream.setLag(0) upstream.update(BlockchainOuterClass.DescribeChain.newBuilder() .setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId)) @@ -201,7 +203,7 @@ class EthereumGrpcUpstreamSpec extends Specification { finished.complete(true) } }) - def upstream = new EthereumGrpcUpstream("test", UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null) + def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null) upstream.setLag(0) upstream.update(BlockchainOuterClass.DescribeChain.newBuilder() .setStatus(BlockchainOuterClass.ChainStatus.newBuilder().setQuorum(1).setAvailabilityValue(UpstreamAvailability.OK.grpcId)) From cff0627020190fb2ed530e10c96ef65aa400fc85 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Thu, 27 Oct 2022 04:30:44 +0300 Subject: [PATCH 05/18] eth_filter fix review comments and tests --- .../dshackle/config/UpstreamsConfig.kt | 1 + .../dshackle/config/UpstreamsConfigReader.kt | 14 +- .../dshackle/quorum/QuorumRpcReader.kt | 2 +- .../dshackle/startup/ConfiguredUpstreams.kt | 55 ++++---- .../dshackle/upstream/DefaultUpstream.kt | 2 +- .../dshackle/upstream/Multistream.kt | 2 +- .../emeraldpay/dshackle/upstream/Selector.kt | 13 +- .../emeraldpay/dshackle/upstream/Upstream.kt | 2 +- .../upstream/calls/EthereumCallSelector.kt | 12 +- .../config/UpstreamsConfigReaderSpec.groovy | 14 ++ .../dshackle/rpc/NativeCallSpec.groovy | 120 ++++++++++++++++++ .../startup/ConfiguredUpstreamsSpec.groovy | 33 +++++ .../dshackle/upstream/SelectorSpec.groovy | 23 ++++ .../calls/EthereumCallSelectorSpec.groovy | 18 +++ src/test/resources/upstreams-node-id.yaml | 34 +++++ 15 files changed, 306 insertions(+), 39 deletions(-) create mode 100644 src/test/resources/upstreams-node-id.yaml diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index 168f2f30..c7ca9831 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -69,6 +69,7 @@ open class UpstreamsConfig { class Upstream { var id: String? = null + var nodeId: Int? = null var chain: String? = null var options: Options? = null var isEnabled = true diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index 91ea1d48..c5e5686f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -31,6 +31,7 @@ class UpstreamsConfigReader( private val log = LoggerFactory.getLogger(UpstreamsConfigReader::class.java) private val authConfigReader = AuthConfigReader() + private val knownNodeIds: MutableSet = HashSet() fun read(input: InputStream): UpstreamsConfig? { val configNode = readNode(input) @@ -236,11 +237,22 @@ class UpstreamsConfigReader( log.warn("Invalid id: $id") return false } - return true + return upstream.nodeId?.let { + if (it !in 1..255) { + log.warn("Invalid node-id: $it. Must be in range [1, 255].") + false + } else if (!knownNodeIds.add(it)) { + log.warn("Duplicated node-id: $it. Must be in unique.") + false + } else { + true + } + } ?: true } internal fun readUpstreamCommon(upNode: MappingNode, upstream: UpstreamsConfig.Upstream<*>) { upstream.id = getValueAsString(upNode, "id") + upstream.nodeId = getValueAsInt(upNode, "node-id") upstream.options = tryReadOptions(upNode) upstream.methods = tryReadMethods(upNode) getValueAsBool(upNode, "enabled")?.let { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt index 7bba2d55..cac1a03d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/quorum/QuorumRpcReader.kt @@ -119,7 +119,7 @@ class QuorumRpcReader( .filter { it.isResolved() } // return nothing if not resolved .map { quorum -> // TODO find actual quorum number - Result(quorum.getResult()!!, quorum.getSignature(), 1, quorum.getResolvedBy().map { it.hash() }) + Result(quorum.getResult()!!, quorum.getSignature(), 1, quorum.getResolvedBy().map { it.nodeId() }) } .switchIfEmpty(defaultResult) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index 740dfc11..1bf6ecbd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -16,6 +16,7 @@ */ package io.emeraldpay.dshackle.startup +import com.google.common.annotations.VisibleForTesting import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.config.UpstreamsConfig @@ -81,7 +82,7 @@ open class ConfiguredUpstreams( log.debug("Start upstream ${up.id}") if (up.connection is UpstreamsConfig.GrpcConnection) { val options = up.options ?: UpstreamsConfig.Options() - buildGrpcUpstream(up.cast(UpstreamsConfig.GrpcConnection::class.java), options) + buildGrpcUpstream(up.nodeId, up.cast(UpstreamsConfig.GrpcConnection::class.java), options) } else { val chain = Global.chainById(up.chain) if (chain == Chain.UNSPECIFIED) { @@ -92,7 +93,7 @@ open class ConfiguredUpstreams( .merge(defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults()) val upstream = when (BlockchainType.from(chain)) { BlockchainType.ETHEREUM -> { - buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options) + buildEthereumUpstream(up.nodeId, up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options) } BlockchainType.BITCOIN -> { @@ -101,6 +102,7 @@ open class ConfiguredUpstreams( BlockchainType.ETHEREUM_POS -> { buildEthereumPosUpstream( + up.nodeId, up.cast(UpstreamsConfig.EthereumPosConnection::class.java), chain, options @@ -167,6 +169,7 @@ open class ConfiguredUpstreams( } private fun buildEthereumPosUpstream( + nodeId: Int?, config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options @@ -194,7 +197,7 @@ open class ConfiguredUpstreams( val hashUrl = conn.execution!!.let { if (it.preferHttp) it.rpc?.url ?: it.ws?.url else it.ws?.url ?: it.rpc?.url } - val hash = getHash(hashUrl!!) + val hash = getHash(nodeId, hashUrl!!) val upstream = EthereumPosRpcUpstream( config.id!!, hash, @@ -251,6 +254,7 @@ open class ConfiguredUpstreams( } private fun buildEthereumUpstream( + nodeId: Int?, config: UpstreamsConfig.Upstream, chain: Chain, options: UpstreamsConfig.Options @@ -275,7 +279,7 @@ open class ConfiguredUpstreams( val hashUrl = if (conn.preferHttp) conn.rpc?.url ?: conn.ws?.url else conn.ws?.url ?: conn.rpc?.url val upstream = EthereumRpcUpstream( config.id!!, - getHash(hashUrl!!), + getHash(nodeId, hashUrl!!), chain, options, config.role, methods, @@ -287,11 +291,12 @@ open class ConfiguredUpstreams( } private fun buildGrpcUpstream( + nodeId: Int?, config: UpstreamsConfig.Upstream, options: UpstreamsConfig.Options ) { val endpoint = config.connection!! - val hash = getHash("${endpoint.host}:${endpoint.port}") + val hash = getHash(nodeId, "${endpoint.host}:${endpoint.port}") val ds = GrpcUpstreams( config.id!!, hash, @@ -366,23 +371,25 @@ open class ConfiguredUpstreams( return connectorFactory } - private fun getHash(obj: Any): Byte { - val hashCode = (obj.hashCode() % 255) - val modifiers: List> = listOf( - Function { i -> i }, - Function { i -> (-i) }, - Function { i -> 127 - abs(i) }, - Function { i -> abs(i) - 128 }, - ) - return modifiers.map { - it.apply(hashCode).toByte() - }.firstOrNull { - hashes[it] != true - }?.let { - hashes[it] = true - it - } ?: (Byte.MIN_VALUE..Byte.MAX_VALUE).first { - hashes[it.toByte()] != true - }.toByte() - } + @VisibleForTesting + private fun getHash(nodeId: Int?, obj: Any): Byte = + nodeId?.toByte() ?: (obj.hashCode() % 255).let { + if (it == 0) 1 else it + }.let { nonZeroHash -> + listOf>( + Function { i -> i }, + Function { i -> (-i) }, + Function { i -> 127 - abs(i) }, + Function { i -> abs(i) - 128 }, + ).map { + it.apply(nonZeroHash).toByte() + }.firstOrNull { + hashes[it] != true + }?.let { + hashes[it] = true + it + } ?: (Byte.MIN_VALUE..Byte.MAX_VALUE).first { + it != 0 && hashes[it.toByte()] != true + }.toByte() + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt index 30db6578..451a53b4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt @@ -143,7 +143,7 @@ abstract class DefaultUpstream( return targets ?: throw IllegalStateException("Methods are not set") } - override fun hash(): Byte = hash + override fun nodeId(): Byte = hash private val quorumByLabel = node?.let { QuorumForLabels(it) } ?: QuorumForLabels(QuorumForLabels.QuorumItem.empty()) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt index 2c5f1fc3..c04dd9e0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt @@ -282,7 +282,7 @@ abstract class Multistream( return false } - override fun hash(): Byte = 0 + override fun nodeId(): Byte = 0 fun printStatus() { var height: Long? = null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt index e92647fb..483b20db 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt @@ -397,15 +397,22 @@ class Selector { } } - class SameUpstreamMatcher(private val upstreamHash: Byte) : Matcher { + class SameNodeMatcher(private val upstreamHash: Byte) : Matcher { override fun matches(up: Upstream): Boolean = - up.hash() == upstreamHash + up.nodeId() == upstreamHash override fun describeInternal(): String = - "upstream hash=$upstreamHash" + "upstream node-id=${upstreamHash.toUByte()}" override fun toString(): String { return "Matcher: ${describeInternal()}" } + + override fun equals(other: Any?): Boolean { + if (other === this) return true + if (other !is SameNodeMatcher) return false + return other.upstreamHash == upstreamHash + } + } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt index b51b9192..e8de7910 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt @@ -41,5 +41,5 @@ interface Upstream { fun cast(selfType: Class): T - fun hash(): Byte + fun nodeId(): Byte } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt index d95e0d95..840314ad 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt @@ -69,14 +69,12 @@ class EthereumCallSelector( return Mono.empty() } val filterId = list[0].toString() - val hashHex = filterId.substring(filterId.length - 2) - val hash = hashHex.toInt(16) - - if (hash < 0 || hash > 255) { - return Mono.empty() + if(filterId.length < 4) { + return Mono.just(Selector.SameNodeMatcher(0.toByte())) } - - return Mono.just(Selector.SameUpstreamMatcher(hash.toByte())) + val hashHex = filterId.substring(filterId.length - 2) + val nodeId = hashHex.toInt(16) + return Mono.just(Selector.SameNodeMatcher(nodeId.toByte())) } private fun blockTagSelector(params: String, pos: Int, head: Head): Mono { diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy index 2ffdb0fb..5b4589a8 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy @@ -401,4 +401,18 @@ class UpstreamsConfigReaderSpec extends Specification { act.upstreams.get(0).role == UpstreamsConfig.UpstreamRole.PRIMARY act.upstreams.get(1).role == UpstreamsConfig.UpstreamRole.PRIMARY } + + def "Parse node id"() { + setup: + def config = this.class.getClassLoader().getResourceAsStream("upstreams-node-id.yaml") + when: + def act = reader.read(config) + then: + act != null + act.upstreams.size() == 2 + act.upstreams[0].nodeId == 1 + act.upstreams[0].id == "has_node_id" + act.upstreams[1].nodeId == null + act.upstreams[1].id == "has_no_node_id" + } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 401af57d..604e561f 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -395,6 +395,68 @@ class NativeCallSpec extends Specification { } } + def "Prepare call adds decorator for eth_newFilter"() { + setup: + def methods = new ManagedCallMethods( + new DefaultEthereumMethods(Chain.ETHEREUM), + ["eth_newFilter"] as Set, [] as Set + ) + methods.setQuorum("eth_newFilter", "always") + def multistream = new MultistreamHolderMock.EthereumMultistreamMock(Chain.ETHEREUM, TestingCommons.upstream()) + multistream.customMethods = methods + multistream.customHead = Mock(Head) + def multistreamHolder = Mock(MultistreamHolder) { + _ * it.observeChains() >> Flux.empty() + } + def nativeCall = nativeCall(multistreamHolder) + + def req = BlockchainOuterClass.NativeCallRequest.newBuilder() + .setChain(Common.ChainRef.CHAIN_ETHEREUM) + .addItems( + BlockchainOuterClass.NativeCallItem.newBuilder() + .setId(1) + .setMethod("eth_newFilter") + ) + .build() + when: + def act = nativeCall.prepareCall(req, multistream) + .collectList().block(Duration.ofSeconds(1)).first() + then: + act instanceof NativeCall.ValidCallContext + act.resultDecorator instanceof NativeCall.CreateFilterDecorator + } + + def "Prepare call adds decorator for eth_getFilterChanges"() { + setup: + def methods = new ManagedCallMethods( + new DefaultEthereumMethods(Chain.ETHEREUM), + ["eth_getFilterChanges"] as Set, [] as Set + ) + methods.setQuorum("eth_getFilterChanges", "always") + def multistream = new MultistreamHolderMock.EthereumMultistreamMock(Chain.ETHEREUM, TestingCommons.upstream()) + multistream.customMethods = methods + multistream.customHead = Mock(Head) + def multistreamHolder = Mock(MultistreamHolder) { + _ * it.observeChains() >> Flux.empty() + } + def nativeCall = nativeCall(multistreamHolder) + + def req = BlockchainOuterClass.NativeCallRequest.newBuilder() + .setChain(Common.ChainRef.CHAIN_ETHEREUM) + .addItems( + BlockchainOuterClass.NativeCallItem.newBuilder() + .setId(1) + .setMethod("eth_getFilterChanges") + ) + .build() + when: + def act = nativeCall.prepareCall(req, multistream) + .collectList().block(Duration.ofSeconds(1)).first() + then: + act instanceof NativeCall.ValidCallContext + act.requestDecorator instanceof NativeCall.GetFilterUpdatesDecorator + } + def "Parse empty params"() { setup: def nativeCall = nativeCall() @@ -447,6 +509,64 @@ class NativeCallSpec extends Specification { act.payload.method == "eth_test" } + def "Decorate eth_getFilterUpdates params"() { + setup: + def nativeCall = nativeCall() + def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), Selector.empty, new AlwaysQuorum(), + new NativeCall.RawCallDetails("eth_getFilterUpdates", '["0xabcd"]'), + new NativeCall.GetFilterUpdatesDecorator(), new NativeCall.NoneResultDecorator()) + when: + def act = nativeCall.parseParams(ctx) + then: + act.id == 1 + act.payload.params == ["0xab"] + act.payload.method == "eth_getFilterUpdates" + } + + def "Decorate eth_newFilter result"() { + setup: + def quorum = new AlwaysQuorum() + + def nativeCall = nativeCall() + nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { + 1 * create(_, _, _) >> Mock(Reader) { + 1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, Collections.singletonList((byte)255))) + } + } + def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, + new NativeCall.ParsedCallDetails("eth_getFilterChanges", []), + new NativeCall.GetFilterUpdatesDecorator(), new NativeCall.CreateFilterDecorator()) + + when: + def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1)) + def act = objectMapper.readValue(resp.result, Object) + then: + act == "0xabff" + resp.nonce == 10 + } + + def "Decorate eth_newFilter result with short nodeId"() { + setup: + def quorum = new AlwaysQuorum() + + def nativeCall = nativeCall() + nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { + 1 * create(_, _, _) >> Mock(Reader) { + 1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, Collections.singletonList((byte)1))) + } + } + def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, + new NativeCall.ParsedCallDetails("eth_getFilterChanges", []), + new NativeCall.GetFilterUpdatesDecorator(), new NativeCall.CreateFilterDecorator()) + + when: + def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1)) + def act = objectMapper.readValue(resp.result, Object) + then: + act == "0xab01" + resp.nonce == 10 + } + @Ignore //TODO def "Calls cache before remote"() { diff --git a/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy index 106541c6..4e6f6eea 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/startup/ConfiguredUpstreamsSpec.groovy @@ -58,4 +58,37 @@ class ConfiguredUpstreamsSpec extends Specification { act instanceof ManagedCallMethods new String(act.executeHardcoded("foo_bar")) == "\"static_response\"" } + + def "Calculate node-id"() { + setup: + def configurer = new ConfiguredUpstreams(Stub(CurrentMultistreamHolder), Stub(FileResolver), Stub(UpstreamsConfig) + ) + expect: + configurer.getHash(node, src) == expected + + where: + node | src | expected + 1 | "" | 1 + 9 | "hohoho" | 9 + null | "hohoho" | 120 + } + + def "Calculate node-id conflicting results"() { + setup: + def configurer = new ConfiguredUpstreams(Stub(CurrentMultistreamHolder), Stub(FileResolver), Stub(UpstreamsConfig) + ) + when: + def h1 = configurer.getHash(null, "hohoho") + def h2 = configurer.getHash(null, "hohoho") + def h3 = configurer.getHash(null, "hohoho") + def h4 = configurer.getHash(null, "hohoho") + def h5 = configurer.getHash(null, "hohoho") + + then: + h1 == (byte)120 + h2 == (byte)-120 + h3 == (byte)-9 + h4 == (byte)8 + h5 == (byte)-128 + } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/SelectorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/SelectorSpec.groovy index ab21834f..fd8de016 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/SelectorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/SelectorSpec.groovy @@ -349,4 +349,27 @@ class SelectorSpec extends Specification { !act } + def "Matches same nodeId"() { + setup: + def up = Mock(Upstream) { + nodeId() >> (byte)5 + } + def matcher = new Selector.SameNodeMatcher((byte)5) + when: + def act = matcher.matches(up) + then: + act + } + + def "Not matches nodeId"() { + setup: + def up = Mock(Upstream) { + nodeId() >> (byte)5 + } + def matcher = new Selector.SameNodeMatcher((byte)1) + when: + def act = matcher.matches(up) + then: + !act + } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy index ea5d15cb..10eb1280 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy @@ -179,4 +179,22 @@ class EthereumCallSelectorSpec extends Specification { then: act == new Selector.HeightMatcher(100) } + + def "Get same matcher for getFilterChanges method"() { + setup: + def callSelector = new EthereumCallSelector(Mock(Reader)) + def head = Mock(Head) + + expect: + callSelector.getMatcher("eth_getFilterChanges", param, head).block() + == new Selector.SameNodeMatcher((byte)hash) + + where: + param | hash + '["0xff09"]' | 9 + '["0xff"]' | 255 + '[]' | 0 + '[""]' | 0 + '["0x0"]' | 0 + } } diff --git a/src/test/resources/upstreams-node-id.yaml b/src/test/resources/upstreams-node-id.yaml new file mode 100644 index 00000000..cfea4978 --- /dev/null +++ b/src/test/resources/upstreams-node-id.yaml @@ -0,0 +1,34 @@ +version: v1 +upstreams: + - id: has_node_id + node-id: 1 + chain: ethereum + connection: + ethereum: + rpc: + url: "http://localhost:8545" + - id: has_no_node_id + chain: ethereum + connection: + ethereum: + rpc: + url: "http://localhost:8545" + ws: + url: "ws://localhost:8546" + - id: conflicted_node_id + node-id: 1 + chain: ethereum + connection: + prefer-http: true + ethereum: + rpc: + url: "http://localhost:9545" + ws: + url: "ws://localhost:9546" + - id: invalid_node_id + node-id: 256 + chain: ethereum + connection: + grpc: + host: "localhost" + port: 2449 \ No newline at end of file From 0826e647c47fe7b9e7a1646964b4296cf649a246 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Thu, 27 Oct 2022 11:37:12 +0300 Subject: [PATCH 06/18] eth_filter fix lint and tests --- .../io/emeraldpay/dshackle/upstream/Selector.kt | 1 - .../dshackle/upstream/calls/EthereumCallSelector.kt | 2 +- .../upstream/calls/EthereumCallSelectorSpec.groovy | 13 ++++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt index 483b20db..6c105505 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt @@ -413,6 +413,5 @@ class Selector { if (other !is SameNodeMatcher) return false return other.upstreamHash == upstreamHash } - } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt index 840314ad..79748acf 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt @@ -69,7 +69,7 @@ class EthereumCallSelector( return Mono.empty() } val filterId = list[0].toString() - if(filterId.length < 4) { + if (filterId.length < 4) { return Mono.just(Selector.SameNodeMatcher(0.toByte())) } val hashHex = filterId.substring(filterId.length - 2) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy index 10eb1280..8aded145 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy @@ -193,8 +193,19 @@ class EthereumCallSelectorSpec extends Specification { param | hash '["0xff09"]' | 9 '["0xff"]' | 255 - '[]' | 0 '[""]' | 0 '["0x0"]' | 0 } + + def "Get empty matcher for getFilterChanges method without params"() { + setup: + def callSelector = new EthereumCallSelector(Mock(Reader)) + def head = Mock(Head) + + when: + def act = callSelector.getMatcher("eth_getFilterChanges", "[]", head).block() + + then: + act == null + } } From fc1b9b9b07a15189f39ff574e0c7f2fe907cc6bd Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Thu, 27 Oct 2022 13:44:36 +0300 Subject: [PATCH 07/18] eth_filter config docs --- docs/04-upstream-config.adoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/04-upstream-config.adoc b/docs/04-upstream-config.adoc index eb6d3544..1a1279a9 100644 --- a/docs/04-upstream-config.adoc +++ b/docs/04-upstream-config.adoc @@ -51,6 +51,7 @@ cluster: min-peers: 2 upstreams: - id: us-nodes + node-id: 1 chain: auto connection: grpc: @@ -61,6 +62,7 @@ cluster: certificate: client.crt key: client.p8.key - id: infura-eth + node-id: 2 chain: ethereum role: fallback labels: @@ -80,6 +82,7 @@ cluster: username: ${INFURA_USER} password: ${INFURA_PASSWD} - id: ethereum-pos + node-id: 3 chain: ropsten connection: ethereum-pos: @@ -110,6 +113,12 @@ In the example above we have: ** label `[provider: infura]` is set for that particular upstream, which can be selected during a request.For example for some requests you may want to use nodes with that label only, i.e. _"send that tx to infura nodes only"_, or _"read only from archive node, with label [archive: true]"_ ** upstream validation (peers, sync status, etc) is disabled for that particular upstream +=== Nodes +`[node-id: 1]` is numeric node identifier defined in a range [1..255] and used to forward +`eth_getFilterChanges` request to the node where one of `eth_newFilter`, `eth_newBlockFilter` or `eth_newPendingTransactionFilter` methods was executed (because filter is s stateful method). + +*It's kindly recommended* to strictly associate _node-id_ parameter with a physical node and keep it during any configuration changes + === Roles and Fallback upstream By default, the Dshackle connects to each upstream in a Round-Robin basis, i.e. sequentially one by one. @@ -206,6 +215,10 @@ Dshackle currently supports - `eth_getUncleByBlockNumberAndIndex` - `eth_feeHistory` - `eth_getLogs` +- `eth_getFilterChanges` +- `eth_newFilter` +- `eth_newBlockFilter` +- `eth_newPendingTransactionFilter` .Plus following methods are answered directly by Dshackle - `net_version` From 36ac0b84c97f05405d41be5991622f9923b4ec1f Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Tue, 8 Nov 2022 19:04:10 +0300 Subject: [PATCH 08/18] support evm networks: Arbitrum, Optimism, Binance Smart Chain --- .../kotlin/io/emeraldpay/dshackle/Global.kt | 11 +++++++++-- .../emeraldpay/dshackle/config/TokensConfig.kt | 2 +- .../io/emeraldpay/dshackle/rpc/NativeCall.kt | 17 ++++++++++------- .../emeraldpay/dshackle/rpc/NativeSubscribe.kt | 2 +- .../dshackle/rpc/TrackERC20Address.kt | 2 +- .../dshackle/rpc/TrackEthereumAddress.kt | 2 +- .../emeraldpay/dshackle/rpc/TrackEthereumTx.kt | 2 +- .../dshackle/startup/ConfiguredUpstreams.kt | 4 ++-- .../upstream/CurrentMultistreamHolder.kt | 8 ++++---- .../upstream/calls/DefaultEthereumMethods.kt | 4 ++-- .../upstream/ethereum/EthereumBlockValidator.kt | 2 +- .../dshackle/upstream/ethereum/WsConnection.kt | 1 + .../dshackle/upstream/grpc/GrpcUpstreams.kt | 4 ++-- .../dshackle/test/MultistreamHolderMock.groovy | 4 +--- 14 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt index 1ec1c3df..b1ebd933 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt @@ -44,8 +44,15 @@ class Global { "ethereum" to Chain.ETHEREUM, "ethereum-classic" to Chain.ETHEREUM_CLASSIC, "eth" to Chain.ETHEREUM, - "polygon" to Chain.MATIC, - "matic" to Chain.MATIC, + "polygon" to Chain.POLYGON, + "matic" to Chain.POLYGON, + "arbitrum" to Chain.ARBITRUM, + "arb" to Chain.ARBITRUM, + "optimism" to Chain.OPTIMISM, + "binance" to Chain.BSC, + "bsc" to Chain.BSC, + "bnb-smart-chan" to Chain.BSC, + "etc" to Chain.ETHEREUM_CLASSIC, "etc" to Chain.ETHEREUM_CLASSIC, "morden" to Chain.TESTNET_MORDEN, "kovan" to Chain.TESTNET_KOVAN, diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/TokensConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/TokensConfig.kt index 55560767..d0944965 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/TokensConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/TokensConfig.kt @@ -41,7 +41,7 @@ class TokensConfig( type == null -> type address.isNullOrBlank() -> "address" blockchain != null && - (BlockchainType.from(blockchain!!) == BlockchainType.ETHEREUM_POS || BlockchainType.from(blockchain!!) == BlockchainType.ETHEREUM) && + (BlockchainType.from(blockchain!!) == BlockchainType.EVM_POS || BlockchainType.from(blockchain!!) == BlockchainType.EVM_POW) && !Address.isValidAddress(address) -> "address" else -> null } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 024030a8..292bdbbb 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -31,6 +31,7 @@ import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.calls.EthereumCallSelector +import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException @@ -66,14 +67,16 @@ open class NativeCall( private val ethereumCallSelectors = EnumMap(Chain::class.java) init { + val casting = mapOf( + BlockchainType.EVM_POS to EthereumPosMultiStream::class.java, + BlockchainType.EVM_POW to EthereumMultistream::class.java, + ) + multistreamHolder.observeChains().subscribe { chain -> - if ((BlockchainType.from(chain) == BlockchainType.ETHEREUM_POS || BlockchainType.from(chain) == BlockchainType.ETHEREUM) && !ethereumCallSelectors.containsKey( - chain - ) - ) { + casting[BlockchainType.from(chain)]?.let { cast -> multistreamHolder.getUpstream(chain)?.let { up -> - val reader = up.cast(EthereumPosMultiStream::class.java).getReader() - ethereumCallSelectors[chain] = EthereumCallSelector(reader.heightByHash()) + val reader = up.cast(cast).getReader() + ethereumCallSelectors.putIfAbsent(chain, EthereumCallSelector(reader.heightByHash())) } } } @@ -212,7 +215,7 @@ open class NativeCall( } // for ethereum the actual block needed for the call may be specified in the call parameters val callSpecificMatcher: Mono = - if (BlockchainType.from(upstream.chain) == BlockchainType.ETHEREUM_POS || BlockchainType.from(upstream.chain) == BlockchainType.ETHEREUM) { + if (BlockchainType.from(upstream.chain) == BlockchainType.EVM_POS || BlockchainType.from(upstream.chain) == BlockchainType.EVM_POW) { ethereumCallSelectors[chain]?.getMatcher(method, params, upstream.getHead()) } else { null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt index f57b0fd7..6edd8027 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeSubscribe.kt @@ -57,7 +57,7 @@ open class NativeSubscribe( fun start(request: BlockchainOuterClass.NativeSubscribeRequest): Publisher { val chain = Chain.byId(request.chainValue) - if (BlockchainType.from(chain) != BlockchainType.ETHEREUM_POS && BlockchainType.from(chain) != BlockchainType.ETHEREUM) { + if (BlockchainType.from(chain) != BlockchainType.EVM_POS && BlockchainType.from(chain) != BlockchainType.EVM_POW) { return Mono.error(UnsupportedOperationException("Native subscribe is not supported for ${chain.chainCode}")) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt index 0f47b6e2..6de9b522 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackERC20Address.kt @@ -70,7 +70,7 @@ class TrackERC20Address( override fun isSupported(chain: Chain, asset: String): Boolean { return tokens.containsKey(TokenId(chain, asset.lowercase(Locale.getDefault()))) && - (BlockchainType.from(chain) == BlockchainType.ETHEREUM_POS || BlockchainType.from(chain) == BlockchainType.ETHEREUM) && multistreamHolder.isAvailable(chain) + (BlockchainType.from(chain) == BlockchainType.EVM_POS || BlockchainType.from(chain) == BlockchainType.EVM_POW) && multistreamHolder.isAvailable(chain) } override fun getBalance(request: BlockchainOuterClass.BalanceRequest): Flux { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt index 7a03d93e..4bef295d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt @@ -43,7 +43,7 @@ class TrackEthereumAddress( override fun isSupported(chain: Chain, asset: String): Boolean { return asset == "ether" && - (BlockchainType.from(chain) == BlockchainType.ETHEREUM_POS || BlockchainType.from(chain) == BlockchainType.ETHEREUM) && multistreamHolder.isAvailable(chain) + (BlockchainType.from(chain) == BlockchainType.EVM_POS || BlockchainType.from(chain) == BlockchainType.EVM_POW) && multistreamHolder.isAvailable(chain) } override fun getBalance(request: BlockchainOuterClass.BalanceRequest): Flux { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt index b3cea3cd..75401d78 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt @@ -62,7 +62,7 @@ class TrackEthereumTx( private val log = LoggerFactory.getLogger(TrackEthereumTx::class.java) override fun isSupported(chain: Chain): Boolean { - return (BlockchainType.from(chain) == BlockchainType.ETHEREUM_POS || BlockchainType.from(chain) == BlockchainType.ETHEREUM) && multistreamHolder.isAvailable(chain) + return (BlockchainType.from(chain) == BlockchainType.EVM_POS || BlockchainType.from(chain) == BlockchainType.EVM_POW) && multistreamHolder.isAvailable(chain) } override fun subscribe(request: BlockchainOuterClass.TxStatusRequest): Flux { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index e984e91b..bda626e2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -87,13 +87,13 @@ open class ConfiguredUpstreams( val options = (up.options ?: UpstreamsConfig.Options()) .merge(defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults()) val upstream = when (BlockchainType.from(chain)) { - BlockchainType.ETHEREUM -> { + BlockchainType.EVM_POW -> { buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options) } BlockchainType.BITCOIN -> { buildBitcoinUpstream(up.cast(UpstreamsConfig.BitcoinConnection::class.java), chain, options) } - BlockchainType.ETHEREUM_POS -> { + BlockchainType.EVM_POS -> { buildEthereumPosUpstream(up.cast(UpstreamsConfig.EthereumPosConnection::class.java), chain, options) } else -> { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt index fbf75e53..7587d6c2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentMultistreamHolder.kt @@ -62,7 +62,7 @@ open class CurrentMultistreamHolder( val chain = change.chain try { when (BlockchainType.from(chain)) { - BlockchainType.ETHEREUM -> { + BlockchainType.EVM_POW -> { val up = change.upstream.cast(EthereumUpstream::class.java) val current = chainMapping[chain] val factory = Callable { @@ -70,7 +70,7 @@ open class CurrentMultistreamHolder( } processUpdate(change, up, current, factory) } - BlockchainType.ETHEREUM_POS -> { + BlockchainType.EVM_POS -> { val up = change.upstream.cast(EthereumPosUpstream::class.java) val current = chainMapping[chain] val factory = Callable { @@ -145,9 +145,9 @@ open class CurrentMultistreamHolder( fun setupDefaultMethods(chain: Chain): CallMethods { val created = when (BlockchainType.from(chain)) { - BlockchainType.ETHEREUM -> DefaultEthereumMethods(chain) + BlockchainType.EVM_POW -> DefaultEthereumMethods(chain) BlockchainType.BITCOIN -> DefaultBitcoinMethods() - BlockchainType.ETHEREUM_POS -> DefaultEthereumMethods(chain) + BlockchainType.EVM_POS -> DefaultEthereumMethods(chain) else -> throw IllegalStateException("Unsupported chain: $chain") } callTargets[chain] = created diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt index 66bce62a..f3a65f90 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt @@ -125,7 +125,7 @@ class DefaultEthereumMethods( Chain.ETHEREUM_CLASSIC == chain -> { "\"1\"" } - Chain.MATIC == chain -> { + Chain.POLYGON == chain -> { "\"137\"" } Chain.TESTNET_MORDEN == chain -> { @@ -151,7 +151,7 @@ class DefaultEthereumMethods( Chain.ETHEREUM == chain -> { "\"0x1\"" } - Chain.MATIC == chain -> { + Chain.POLYGON == chain -> { "\"0x89\"" } Chain.TESTNET_ROPSTEN == chain -> { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt index eb5113b7..81d41144 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumBlockValidator.kt @@ -111,7 +111,7 @@ class EthereumBlockValidator : BlockValidator { ) } - val timestampValid = it.timestamp > cur.timestamp + val timestampValid = it.timestamp >= cur.timestamp if (!timestampValid) { log.warn( "Block timestamp {} not valid for {}. Must be greater than {}", diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt index b2b6bec2..44eec5d0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt @@ -240,6 +240,7 @@ open class WsConnection( .aggregateFrames(msgSizeLimit) .receiveFrames() .map { ByteBufInputStream(it.content()).readAllBytes() } + .filter{ it.isNotEmpty() } .flatMap { try { val msg = parser.parse(it) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt index 446dd487..db8774ad 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt @@ -190,11 +190,11 @@ class GrpcUpstreams( ) val blockchainType = BlockchainType.from(chain) - if (blockchainType == BlockchainType.ETHEREUM) { + if (blockchainType == BlockchainType.EVM_POW) { return getOrCreateEthereum(chain, metrics) } else if (blockchainType == BlockchainType.BITCOIN) { return getOrCreateBitcoin(chain, metrics) - } else if (blockchainType == BlockchainType.ETHEREUM_POS) { + } else if (blockchainType == BlockchainType.EVM_POS) { return getOrCreateEthereumPos(chain, metrics) } else { throw IllegalArgumentException("Unsupported blockchain: $chain") diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy index 59414f04..660b7611 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/MultistreamHolderMock.groovy @@ -27,11 +27,9 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.MultistreamHolder -import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosRpcUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumReader -import io.emeraldpay.dshackle.upstream.ethereum.EthereumRpcUpstream import io.emeraldpay.grpc.BlockchainType import io.emeraldpay.grpc.Chain import org.jetbrains.annotations.NotNull @@ -48,7 +46,7 @@ class MultistreamHolderMock implements MultistreamHolder { Multistream addUpstream(@NotNull Chain chain, @NotNull Upstream up) { if (!upstreams.containsKey(chain)) { - if (BlockchainType.from(chain) == BlockchainType.ETHEREUM_POS) { + if (BlockchainType.from(chain) == BlockchainType.EVM_POS) { if (up instanceof EthereumPosMultiStream) { upstreams[chain] = up } else if (up instanceof EthereumPosRpcUpstream) { From 1eed11bd7aa67f783275322ed88537d1dd1bb74e Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Wed, 9 Nov 2022 02:30:15 +0300 Subject: [PATCH 09/18] fix typo --- src/main/kotlin/io/emeraldpay/dshackle/Global.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt index b1ebd933..7ee182f5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt @@ -51,7 +51,7 @@ class Global { "optimism" to Chain.OPTIMISM, "binance" to Chain.BSC, "bsc" to Chain.BSC, - "bnb-smart-chan" to Chain.BSC, + "bnb-smart-chain" to Chain.BSC, "etc" to Chain.ETHEREUM_CLASSIC, "etc" to Chain.ETHEREUM_CLASSIC, "morden" to Chain.TESTNET_MORDEN, From e8d12c5584947d5ec81a173fea3c4ddd83ae7055 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Wed, 9 Nov 2022 03:06:06 +0300 Subject: [PATCH 10/18] update submodule ref --- dshackle-cli/grpc | 2 +- emerald-java-client | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dshackle-cli/grpc b/dshackle-cli/grpc index 4ed721cb..4060e539 160000 --- a/dshackle-cli/grpc +++ b/dshackle-cli/grpc @@ -1 +1 @@ -Subproject commit 4ed721cb908a782e4d99c49979087f29b7e5d54a +Subproject commit 4060e53997b32121670617595bad7b4276f599e8 diff --git a/emerald-java-client b/emerald-java-client index 39765fd0..35196f35 160000 --- a/emerald-java-client +++ b/emerald-java-client @@ -1 +1 @@ -Subproject commit 39765fd040f6cb417ad7e4fe7c05b508a2e6ee1a +Subproject commit 35196f35f3d0546d59b51acfeb895174eac170f9 From 87381b9946b5c74053756184bb6d868b1c00d92d Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Wed, 9 Nov 2022 03:10:42 +0300 Subject: [PATCH 11/18] fix lint --- .../io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt index 44eec5d0..975813b0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/WsConnection.kt @@ -240,7 +240,7 @@ open class WsConnection( .aggregateFrames(msgSizeLimit) .receiveFrames() .map { ByteBufInputStream(it.content()).readAllBytes() } - .filter{ it.isNotEmpty() } + .filter { it.isNotEmpty() } .flatMap { try { val msg = parser.parse(it) From 8fcd4febcc72560fdba570886270105c77725398 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Wed, 9 Nov 2022 03:22:49 +0300 Subject: [PATCH 12/18] inc libs --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 02076f63..5bd66d99 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,16 +2,16 @@ detekt = "1.18.1" etherjar = "0.11.1" groovy = "2.5.14" -protoc = "3.9.0" +protoc = "3.21.7" slf4j = "1.7.32" jackson = "2.11.0" -grpc = "1.38.0" +grpc = "1.49.2" reactive-grpc = "1.2.0" spring-boot = "2.5.6" spring-security = "5.5.3" reactor = "3.4.10" netty = "4.1.70.Final" -netty-tcnative = "2.0.45.Final" +netty-tcnative = "2.0.47.Final" kotlin = "1.5.31" httpcomponents = "4.5.8" From 01211c6f4794746681cc34b82e05ed9517d753c4 Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Wed, 9 Nov 2022 13:42:34 +0300 Subject: [PATCH 13/18] testing dshackle --- dshackle-cli/package-lock.json | 875 +-------------------------------- dshackle-cli/package.json | 3 +- dshackle-cli/src/cli.js | 111 ++++- dshackle-cli/src/grpc-clent.js | 39 +- 4 files changed, 125 insertions(+), 903 deletions(-) diff --git a/dshackle-cli/package-lock.json b/dshackle-cli/package-lock.json index f19665b2..23c555c4 100644 --- a/dshackle-cli/package-lock.json +++ b/dshackle-cli/package-lock.json @@ -13,8 +13,7 @@ "@grpc/proto-loader": "^0.7.2", "arg": "^5.0.2", "cli-color": "^2.0.3", - "esm": "^3.2.25", - "inquirer": "^9.1.1" + "esm": "^3.2.25" }, "bin": { "dshackle-cli-tool": "bin/dshackle-cli-tool" @@ -114,115 +113,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" }, - "node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "dependencies": { - "type-fest": "^1.0.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", - "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bl": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.0.0.tgz", - "integrity": "sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, "node_modules/cli-color": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", @@ -238,39 +133,6 @@ "node": ">=0.10" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.0.0.tgz", - "integrity": "sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==", - "engines": { - "node": ">= 12" - } - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -348,14 +210,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -381,24 +235,6 @@ "type": "^1.0.1" } }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, "node_modules/es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", @@ -451,17 +287,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -492,34 +317,6 @@ "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -528,66 +325,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/inquirer": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.1.1.tgz", - "integrity": "sha512-hfS9EJ1pVkGNbYKqzdGwMj0Dqosd36Qvxd5pFy4657QT23gmtFTSqoYBisZR75DReeSMWPNa8J0Lf6TQCz8PvA==", - "dependencies": { - "ansi-escapes": "^5.0.0", - "chalk": "^5.0.1", - "cli-cursor": "^4.0.0", - "cli-width": "^4.0.0", - "external-editor": "^3.0.3", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^6.1.2", - "run-async": "^2.4.0", - "rxjs": "^7.5.6", - "string-width": "^5.1.2", - "strip-ansi": "^7.0.1", - "through": "^2.3.6", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -596,58 +333,16 @@ "node": ">=8" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, - "node_modules/is-unicode-supported": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.2.0.tgz", - "integrity": "sha512-wH+U77omcRzevfIG8dDhTS0V9zZyweakfD01FULl97+0EHiJTTZtJqxPSkIIo/SDPv/i07k/C9jAPY+jwLLeUQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, - "node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -676,68 +371,11 @@ "timers-ext": "^0.1.7" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz", - "integrity": "sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==", - "dependencies": { - "bl": "^5.0.0", - "chalk": "^5.0.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.6.1", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.1.0", - "log-symbols": "^5.1.0", - "strip-ansi": "^7.0.1", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/protobufjs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.0.tgz", @@ -766,19 +404,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -787,109 +412,6 @@ "node": ">=0.10.0" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, "node_modules/timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", @@ -899,67 +421,11 @@ "next-tick": "1" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, - "node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/wrap-ansi": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.0.1.tgz", - "integrity": "sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -1117,63 +583,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", "integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==" }, - "ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "requires": { - "type-fest": "^1.0.2" - } - }, - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" - }, - "ansi-styles": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", - "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==" - }, "arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bl": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.0.0.tgz", - "integrity": "sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==", - "requires": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==" - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, "cli-color": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", @@ -1186,24 +600,6 @@ "timers-ext": "^0.1.7" } }, - "cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "requires": { - "restore-cursor": "^4.0.0" - } - }, - "cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==" - }, - "cli-width": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.0.0.tgz", - "integrity": "sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==" - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -1262,11 +658,6 @@ } } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1289,24 +680,6 @@ "type": "^1.0.1" } }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "requires": { - "clone": "^1.0.2" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, "es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", @@ -1352,11 +725,6 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" - }, "esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -1386,109 +754,26 @@ } } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "requires": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - } - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "inquirer": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.1.1.tgz", - "integrity": "sha512-hfS9EJ1pVkGNbYKqzdGwMj0Dqosd36Qvxd5pFy4657QT23gmtFTSqoYBisZR75DReeSMWPNa8J0Lf6TQCz8PvA==", - "requires": { - "ansi-escapes": "^5.0.0", - "chalk": "^5.0.1", - "cli-cursor": "^4.0.0", - "cli-width": "^4.0.0", - "external-editor": "^3.0.3", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^6.1.2", - "run-async": "^2.4.0", - "rxjs": "^7.5.6", - "string-width": "^5.1.2", - "strip-ansi": "^7.0.1", - "through": "^2.3.6", - "wrap-ansi": "^8.0.1" - } - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, - "is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==" - }, "is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, - "is-unicode-supported": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.2.0.tgz", - "integrity": "sha512-wH+U77omcRzevfIG8dDhTS0V9zZyweakfD01FULl97+0EHiJTTZtJqxPSkIIo/SDPv/i07k/C9jAPY+jwLLeUQ==" - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, - "log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "requires": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - } - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -1517,50 +802,11 @@ "timers-ext": "^0.1.7" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, "next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "ora": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz", - "integrity": "sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==", - "requires": { - "bl": "^5.0.0", - "chalk": "^5.0.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.6.1", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.1.0", - "log-symbols": "^5.1.0", - "strip-ansi": "^7.0.1", - "wcwidth": "^1.0.1" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" - }, "protobufjs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.0.tgz", @@ -1587,89 +833,11 @@ } } }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, - "restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, - "rxjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", - "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", - "requires": { - "tslib": "^2.1.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, "timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", @@ -1679,52 +847,11 @@ "next-tick": "1" } }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, - "type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "requires": { - "defaults": "^1.0.3" - } - }, - "wrap-ansi": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.0.1.tgz", - "integrity": "sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==", - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/dshackle-cli/package.json b/dshackle-cli/package.json index 55836293..495305f4 100644 --- a/dshackle-cli/package.json +++ b/dshackle-cli/package.json @@ -18,8 +18,7 @@ "@grpc/proto-loader": "^0.7.2", "arg": "^5.0.2", "cli-color": "^2.0.3", - "esm": "^3.2.25", - "inquirer": "^9.1.1" + "esm": "^3.2.25" }, "files": [ "bin/", diff --git a/dshackle-cli/src/cli.js b/dshackle-cli/src/cli.js index ca83abc8..88d47e35 100644 --- a/dshackle-cli/src/cli.js +++ b/dshackle-cli/src/cli.js @@ -1,47 +1,109 @@ -import {describe} from "./grpc-clent"; +import {describe, connect, nativeCall} from "./grpc-clent"; import arg from 'arg'; import clc from "cli-color"; import util from "util"; +const chains = { + CHAIN_BSC: 1006, + CHAIN_OPTIMISM: 1005, + CHAIN_ARBITRUM: 1004, + CHAIN_MATIC: 1002, + CHAIN_ETHEREUM: 100 +} + export function cli(args) { let opts = parseArgumentsIntoOptions(args); if (!opts.url) { console.log("Err: URL not specified!!!") return } - describe(opts.url, opts.ca, opts.cert, opts.key, (error, response) => { - if (error) { - console.error(clc.red('Connection to ' + opts.url + ' failed! [' + error.message + ']')); - } else { - console.error(clc.green('Connected to ', opts.url)); - if (opts.print) { - console.log(util.inspect(response, false, null, true /* enable colors */)); - } else { - response.chains.forEach(function (item) { - var state = item.status.availability; - switch (state) { - case 'AVAIL_OK': - state = clc.green(state); - break - case 'AVAIL_UNKNOWN': - case 'AVAIL_UNAVAILABLE': - state = clc.red(state); - break - default: - state = clc.yellow(state); - } - console.log(item.status.chain + ' -> ' + clc.bold(state)) - }) + const client = connect(opts.url, opts.ca, opts.cert, opts.key) + describe(client, (error, response) => { + if (error) { + console.error(clc.red('Connection to ' + opts.url + ' failed: ' + error.message)); + return + } + console.log(clc.green('Connected to ', opts.url)); + if (opts.print) { + console.log(util.inspect(response, false, null, true /* enable colors */)); + } + processDescribe(client, response, opts.testRun) + }) +} + +function processDescribe(client, response, testRun) { + let promises = [] + let statuses = new Map() + + response.chains.forEach((item) => { + const state = item.status.availability + const chain = item.status.chain + let status = { + state: 'AVAIL_UNKNOWN', + grpc: clc.yellow('UNKNOWN'), + failed: false + } + + switch (state) { + case 'AVAIL_OK': + status.state = clc.green(state); + break + case 'AVAIL_UNKNOWN': + case 'AVAIL_UNAVAILABLE': + status.state = clc.red(state); + break + default: + status.state = clc.yellow(state); + } + + if (state === 'AVAIL_OK') { + promises.push(nativeCall(client, chains[chain], chain)) + } else { + status.failed = true + } + statuses.set(chain, status) + }) + + Promise.all(promises).then(responses => { + responses.forEach((resp) => { + let status = statuses.get(resp.chain) + if (resp.error) { + status.failed = true + status.grpc = clc.red(resp.error.message) + } else { + if (resp.payload.succeed) { + status.grpc = clc.green('OK') + } else { + status.grpc = clc.red('FAILED') + status.failed = true + } } + }) + + let hasError = false + statuses.forEach((status, chain) => { + printState(chain, status) + if (status.failed) { + hasError = true + } + }) + + if (hasError && testRun) { + process.exit(1) } }) } +function printState(chain, status) { + console.log(chain + ' -> ' + "state: " + clc.bold(status.state) + " gRPC: " + clc.bold(status.grpc)) +} + function parseArgumentsIntoOptions(rawArgs) { const args = arg( { '--print': Boolean, + '--test-run': Boolean, '--ca': String, '--cert': String, '--key': String, @@ -53,6 +115,7 @@ function parseArgumentsIntoOptions(rawArgs) { ); return { print: args['--print'] || false, + testRun: args['--test-run'] || false, url: args._[0], ca: args['--ca'], cert: args['--cert'], diff --git a/dshackle-cli/src/grpc-clent.js b/dshackle-cli/src/grpc-clent.js index 4f435d2b..a4b33d92 100644 --- a/dshackle-cli/src/grpc-clent.js +++ b/dshackle-cli/src/grpc-clent.js @@ -16,7 +16,10 @@ const options = { const packageDefinition = protoLoader.loadSync(PROTO_PATH, options); const emerald = grpc.loadPackageDefinition(packageDefinition).emerald -export function describe(url, ca, cert, key, handler) { + +var id = 100 + +export function connect(url, ca, cert, key) { let credentials = grpc.credentials.createInsecure() if (ca || cert || key) { console.log("Using TLS") @@ -26,11 +29,41 @@ export function describe(url, ca, cert, key, handler) { cert ? fs.readFileSync(cert) : null ); } - const client = new emerald.Blockchain( + console.log('Connecting to: ' + url + '...') + return new emerald.Blockchain( url, credentials ); +} - console.log('Connecting to: ' + url + '...') +export function describe(client, handler) { client.Describe({}, handler); } + +export function nativeCall(client, chainCode, chain) { + return new Promise((resolve, reject) => { + const call = client.NativeCall({ + chain: chainCode, + items: [{ + id: id++, + method: "eth_getBalance", + payload: "WyIweDhEOTc2ODlDOTgxODg5MkI3MDBlMjdGMzE2Y2MzRTQxZTE3ZkJlYjkiLCAibGF0ZXN0Il0=" + }], + quorum: 1, + min_availability: 0 + }) + call.on('data', (item) => { + resolve(toResult(chain, item, null)) + }) + call.on('end', () => resolve(toResult(chain, null, null))) + call.on('error', (e) => reject(toResult(chain, null, e))) + }) +} + +function toResult(chain, obj, err) { + return { + chain: chain, + payload: obj, + error: err + } +} From 9903e2d124b7af48150cb199adce7fcf84e60c3a Mon Sep 17 00:00:00 2001 From: Maksim Fomenkov Date: Thu, 10 Nov 2022 11:54:36 +0300 Subject: [PATCH 14/18] fix review --- src/main/kotlin/io/emeraldpay/dshackle/Global.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt index 7ee182f5..29ff88cc 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/Global.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/Global.kt @@ -53,7 +53,6 @@ class Global { "bsc" to Chain.BSC, "bnb-smart-chain" to Chain.BSC, "etc" to Chain.ETHEREUM_CLASSIC, - "etc" to Chain.ETHEREUM_CLASSIC, "morden" to Chain.TESTNET_MORDEN, "kovan" to Chain.TESTNET_KOVAN, "kovan-testnet" to Chain.TESTNET_KOVAN, From a559416e9dd8dc82e11db061c317eeaef67708f9 Mon Sep 17 00:00:00 2001 From: a10zn8 Date: Fri, 11 Nov 2022 17:48:35 +0400 Subject: [PATCH 15/18] Added support of eth_uninstallFilter --- .../kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt | 11 +++++++++-- .../dshackle/upstream/calls/DefaultEthereumMethods.kt | 1 + .../dshackle/upstream/calls/EthereumCallSelector.kt | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 78ec44ac..33e29573 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -255,7 +255,10 @@ open class NativeCall( } private fun getRequestDecorator(method: String): RequestDecorator = - if (method == "eth_getFilterChanges") GetFilterUpdatesDecorator() else NoneRequestDecorator() + if (method == "eth_getFilterChanges" || method == "eth_uninstallFilter") + GetFilterUpdatesDecorator() + else + NoneRequestDecorator() private fun getResultDecorator(method: String): ResultDecorator = if (CreateFilterDecorator.createFilterMethods.contains(method)) CreateFilterDecorator() else NoneResultDecorator() @@ -378,7 +381,11 @@ open class NativeCall( companion object { const val quoteCode = '"'.code.toByte() - val createFilterMethods = listOf("eth_getFilterChanges", "eth_newFilter", "eth_newBlockFilter") + val createFilterMethods = listOf( + "eth_getFilterChanges", + "eth_newFilter", + "eth_newBlockFilter" + ) } override fun processResult(result: QuorumRpcReader.Result): ByteArray { val bytes = result.value diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt index 45067a48..6f78cfe0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DefaultEthereumMethods.kt @@ -75,6 +75,7 @@ class DefaultEthereumMethods( "eth_newFilter", "eth_newBlockFilter", "eth_newPendingTransactionFilter", + "eth_uninstallFilter" ) private val allowedMethods = anyResponseMethods + firstValueMethods + specialMethods + headVerifiedMethods + filterMethods diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt index 79748acf..abf684de 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt @@ -57,7 +57,7 @@ class EthereumCallSelector( return blockTagSelector(params, 1, head) } else if (method == "eth_getStorageAt") { return blockTagSelector(params, 2, head) - } else if (method == "eth_getFilterChanges") { + } else if (method == "eth_getFilterChanges" || method == "eth_uninstallFilter") { return sameUpstreamMatcher(params) } return Mono.empty() From f3ce82f080f2790846470e7de0c39f0bb2f3c3b0 Mon Sep 17 00:00:00 2001 From: a10zn8 Date: Fri, 11 Nov 2022 17:57:21 +0400 Subject: [PATCH 16/18] add missed method to createFilterMethods --- src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 33e29573..e4873a36 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -384,7 +384,8 @@ open class NativeCall( val createFilterMethods = listOf( "eth_getFilterChanges", "eth_newFilter", - "eth_newBlockFilter" + "eth_newBlockFilter", + "eth_newPendingTransactionFilter" ) } override fun processResult(result: QuorumRpcReader.Result): ByteArray { From 3987660266b764b8a2ce030456b6bfaf7492a7fc Mon Sep 17 00:00:00 2001 From: a10zn8 Date: Fri, 11 Nov 2022 18:19:24 +0400 Subject: [PATCH 17/18] added test for eth_uninstallFilter decoration --- .../dshackle/rpc/NativeCallSpec.groovy | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 604e561f..43352b51 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -457,6 +457,37 @@ class NativeCallSpec extends Specification { act.requestDecorator instanceof NativeCall.GetFilterUpdatesDecorator } + def "Prepare call adds decorator for eth_uninstallFilter"() { + setup: + def methods = new ManagedCallMethods( + new DefaultEthereumMethods(Chain.ETHEREUM), + ["eth_uninstallFilter"] as Set, [] as Set + ) + methods.setQuorum("eth_uninstallFilter", "always") + def multistream = new MultistreamHolderMock.EthereumMultistreamMock(Chain.ETHEREUM, TestingCommons.upstream()) + multistream.customMethods = methods + multistream.customHead = Mock(Head) + def multistreamHolder = Mock(MultistreamHolder) { + _ * it.observeChains() >> Flux.empty() + } + def nativeCall = nativeCall(multistreamHolder) + + def req = BlockchainOuterClass.NativeCallRequest.newBuilder() + .setChain(Common.ChainRef.CHAIN_ETHEREUM) + .addItems( + BlockchainOuterClass.NativeCallItem.newBuilder() + .setId(1) + .setMethod("eth_uninstallFilter") + ) + .build() + when: + def act = nativeCall.prepareCall(req, multistream) + .collectList().block(Duration.ofSeconds(1)).first() + then: + act instanceof NativeCall.ValidCallContext + act.requestDecorator instanceof NativeCall.GetFilterUpdatesDecorator + } + def "Parse empty params"() { setup: def nativeCall = nativeCall() From 92e91c2232c06a843416b3ae70cbf5562afdabf4 Mon Sep 17 00:00:00 2001 From: a10zn8 Date: Mon, 14 Nov 2022 14:45:54 +0400 Subject: [PATCH 18/18] remove unnecessary decoration of getFilterChanges call --- src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index e4873a36..956b187d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -382,7 +382,6 @@ open class NativeCall( companion object { const val quoteCode = '"'.code.toByte() val createFilterMethods = listOf( - "eth_getFilterChanges", "eth_newFilter", "eth_newBlockFilter", "eth_newPendingTransactionFilter"