You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tuweni.apache.org by to...@apache.org on 2021/07/29 14:23:23 UTC
[incubator-tuweni] branch main updated: Add JSON-RPC client to talk
to endpoint
This is an automated email from the ASF dual-hosted git repository.
toulmean pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git
The following commit(s) were added to refs/heads/main by this push:
new c0aea6f Add JSON-RPC client to talk to endpoint
new 4ce263a Merge pull request #323 from atoulme/add_client
c0aea6f is described below
commit c0aea6f5860737f4f41d3ed486c0d8ae47f2ff03
Author: Antoine Toulme <an...@lunar-ocean.com>
AuthorDate: Wed Jul 28 23:45:45 2021 -0700
Add JSON-RPC client to talk to endpoint
---
.../kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt | 3 +-
.../org/apache/tuweni/eth/JSONRPCResponse.kt | 14 ++--
.../org/apache/tuweni/jsonrpc/app/JSONRPCApp.kt | 17 ++--
.../org/apache/tuweni/jsonrpc/app/JSONRPCConfig.kt | 5 ++
.../org/apache/tuweni/jsonrpc/JSONRPCClient.kt | 92 +++++++++++-----------
.../tuweni/jsonrpc/methods/MethodsHandler.kt | 3 +-
.../tuweni/jsonrpc/methods/MethodsHandlerTest.kt | 9 ++-
7 files changed, 78 insertions(+), 65 deletions(-)
diff --git a/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt b/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt
index 7b73959..5305818 100644
--- a/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt
+++ b/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt
@@ -23,5 +23,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
data class JSONRPCRequest(
@JsonProperty("id") val id: Int,
@JsonProperty("method") val method: String,
- @JsonProperty("params") val params: Array<String>
+ @JsonProperty("params") val params: Array<String>,
+ @JsonProperty("jsonrpc") val jsonrpc: String = "2.0"
)
diff --git a/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCResponse.kt b/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCResponse.kt
index e97f108..d5d0362 100644
--- a/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCResponse.kt
+++ b/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCResponse.kt
@@ -20,10 +20,12 @@ import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty
@JsonInclude(JsonInclude.Include.NON_NULL)
-data class JSONRPCResponse(@JsonProperty("id") val id: Int, @JsonProperty("result") val result: Any? = null, @JsonProperty("error") val error: Any? = null)
+data class JSONRPCResponse(@JsonProperty("id") val id: Int, @JsonProperty("result") val result: Any? = null, @JsonProperty("error") val error: JSONRPCError? = null)
-val parseError = JSONRPCResponse(id = 0, error = mapOf(Pair("code", -32700), Pair("message", "Parse error")))
-val invalidRequest = JSONRPCResponse(id = 0, error = mapOf(Pair("code", -32600), Pair("message", "Invalid Request")))
-val methodNotFound = JSONRPCResponse(id = 0, error = mapOf(Pair("code", -32601), Pair("message", "Method not found")))
-val invalidParams = JSONRPCResponse(id = 0, error = mapOf(Pair("code", -32602), Pair("message", "Invalid params")))
-val internalError = JSONRPCResponse(id = 0, error = mapOf(Pair("code", -32603), Pair("message", "Internal error")))
+data class JSONRPCError(@JsonProperty("code") val code: Int, @JsonProperty("message") val message: String)
+
+val parseError = JSONRPCResponse(id = 0, error = JSONRPCError(-32700, "Parse error"))
+val invalidRequest = JSONRPCResponse(id = 0, error = JSONRPCError(-32600, "Invalid Request"))
+val methodNotFound = JSONRPCResponse(id = 0, error = JSONRPCError(-32601, "Method not found"))
+val invalidParams = JSONRPCResponse(id = 0, error = JSONRPCError(-32602, "Invalid params"))
+val internalError = JSONRPCResponse(id = 0, error = JSONRPCError(-32603, "Internal error"))
diff --git a/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCApp.kt b/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCApp.kt
index b2ba1c7..fc4748c 100644
--- a/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCApp.kt
+++ b/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCApp.kt
@@ -21,8 +21,7 @@ import io.vertx.core.VertxOptions
import io.vertx.tracing.opentelemetry.OpenTelemetryOptions
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
-import org.apache.tuweni.eth.JSONRPCRequest
-import org.apache.tuweni.eth.JSONRPCResponse
+import org.apache.tuweni.jsonrpc.JSONRPCClient
import org.apache.tuweni.jsonrpc.JSONRPCServer
import org.apache.tuweni.jsonrpc.methods.MeteredHandler
import org.apache.tuweni.jsonrpc.methods.MethodAllowListHandler
@@ -74,14 +73,20 @@ class JSONRPCApplication(
) {
fun run() {
+ val client = JSONRPCClient(vertx, config.endpointPort(), config.endpointHost())
// TODO allow more options such as allowlist of certificates, enforce client authentication.
val trustOptions = VertxTrustOptions.recordClientFingerprints(config.clientFingerprintsFile())
- val allowListHandler = MethodAllowListHandler(config.allowedMethods(), this::handleRequest)
+ val allowListHandler = MethodAllowListHandler(config.allowedMethods()) { req ->
+ runBlocking {
+ client.sendRequest(req).await()
+ }
+ }
val meter = metricsService.meterSdkProvider.get("jsonrpc")
val successCounter = meter.longCounterBuilder("success").build()
val failureCounter = meter.longCounterBuilder("failure").build()
+
val handler = MeteredHandler(successCounter, failureCounter, allowListHandler::handleRequest)
val server = JSONRPCServer(
vertx, config.port(), config.networkInterface(),
@@ -105,6 +110,7 @@ class JSONRPCApplication(
Thread {
runBlocking {
server.stop().await()
+ client.close()
}
}
)
@@ -113,9 +119,4 @@ class JSONRPCApplication(
logger.info("JSON-RPC server started")
}
}
-
- private fun handleRequest(request: JSONRPCRequest): JSONRPCResponse {
- logger.info("Received request {}", request)
- return JSONRPCResponse(1) // TODO implement
- }
}
diff --git a/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCConfig.kt b/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCConfig.kt
index 130efc8..4ba4122 100644
--- a/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCConfig.kt
+++ b/jsonrpc-app/src/main/kotlin/org/apache/tuweni/jsonrpc/app/JSONRPCConfig.kt
@@ -47,6 +47,8 @@ class JSONRPCConfig(val filePath: Path) {
.addListOfString("allowedMethods", Collections.emptyList(), "Allowed JSON-RPC methods", null)
.addListOfString("allowedRanges", Collections.singletonList("0.0.0.0/0"), "Allowed IP ranges", null)
.addListOfString("rejectedRanges", Collections.emptyList(), "Rejected IP ranges", null)
+ .addInteger("endpointPort", 8545, "JSON-RPC endpoint port", PropertyValidator.isValidPort())
+ .addString("endpointHost", "localhost", "JSON-RPC endpoint host", null)
.toSchema()
}
@@ -69,4 +71,7 @@ class JSONRPCConfig(val filePath: Path) {
fun allowedMethods() = config.getListOfString("allowedMethods")
fun allowedRanges() = config.getListOfString("allowedRanges")
fun rejectedRanges() = config.getListOfString("rejectedRanges")
+
+ fun endpointPort() = config.getInteger("endpointPort")
+ fun endpointHost() = config.getString("endpointHost")
}
diff --git a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt
index 0440271..25ece27 100644
--- a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt
+++ b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt
@@ -23,10 +23,14 @@ import io.vertx.core.tracing.TracingPolicy
import io.vertx.ext.web.client.WebClient
import io.vertx.ext.web.client.WebClientOptions
import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
import org.apache.tuweni.eth.Address
+import org.apache.tuweni.eth.JSONRPCRequest
+import org.apache.tuweni.eth.JSONRPCResponse
import org.apache.tuweni.eth.Transaction
import org.apache.tuweni.units.bigints.UInt256
import java.io.Closeable
+import java.util.concurrent.atomic.AtomicInteger
val mapper = ObjectMapper()
@@ -40,42 +44,43 @@ class JSONRPCClient(
val userAgent: String = "Apache Tuweni JSON-RPC Client",
) : Closeable {
+ val requestCounter = AtomicInteger(1)
val client = WebClient.create(vertx, WebClientOptions().setUserAgent(userAgent).setTryUseCompression(true).setTracingPolicy(TracingPolicy.ALWAYS) as WebClientOptions)
- /**
- * Sends a signed transaction to the Ethereum network.
- * @param tx the transaction object to send
- * @return the hash of the transaction, or an empty string if the hash is not available yet.
- * @throws ClientRequestException if the request is rejected
- * @throws ConnectException if it cannot dial the remote client
- */
- suspend fun sendRawTransaction(tx: Transaction): String {
- val body = mapOf(
- Pair("jsonrpc", "2.0"),
- Pair("method", "eth_sendRawTransaction"),
- Pair("id", 1),
- Pair("params", listOf(tx.toBytes().toHexString()))
- )
- val deferred = CompletableDeferred<String>()
+ suspend fun sendRequest(request: JSONRPCRequest): Deferred<JSONRPCResponse> {
+ val deferred = CompletableDeferred<JSONRPCResponse>()
client.post(serverPort, serverHost, "/")
.putHeader("Content-Type", "application/json")
- .sendBuffer(Buffer.buffer(mapper.writeValueAsBytes(body))) { response ->
+ .sendBuffer(Buffer.buffer(mapper.writeValueAsBytes(request))) { response ->
if (response.failed()) {
deferred.completeExceptionally(response.cause())
} else {
- val jsonResponse = response.result().bodyAsJsonObject()
- val err = jsonResponse.getJsonObject("error")
- if (err != null) {
- val errorMessage = "Code ${err.getInteger("code")}: ${err.getString("message")}"
- deferred.completeExceptionally(ClientRequestException(errorMessage))
- } else {
- deferred.complete(jsonResponse.getString("result"))
- }
+ val jsonResponse = response.result().bodyAsJson(JSONRPCResponse::class.java)
+ deferred.complete(jsonResponse)
}
}
- return deferred.await()
+ return deferred
+ }
+
+ /**
+ * Sends a signed transaction to the Ethereum network.
+ * @param tx the transaction object to send
+ * @return the hash of the transaction, or an empty string if the hash is not available yet.
+ * @throws ClientRequestException if the request is rejected
+ * @throws ConnectException if it cannot dial the remote client
+ */
+ suspend fun sendRawTransaction(tx: Transaction): String {
+ val body = JSONRPCRequest(nextId(), "eth_sendRawTransaction", arrayOf(tx.toBytes().toHexString()))
+ val jsonResponse = sendRequest(body).await()
+ val err = jsonResponse.error
+ if (err != null) {
+ val errorMessage = "Code ${err.code}: ${err.message}"
+ throw ClientRequestException(errorMessage)
+ } else {
+ return jsonResponse.result.toString()
+ }
}
/**
@@ -116,29 +121,26 @@ class JSONRPCClient(
* @throws ConnectException if it cannot dial the remote client
*/
suspend fun getTransactionCount_latest(address: Address): UInt256 {
- val body = mapOf(
- Pair("jsonrpc", "2.0"),
- Pair("method", "eth_getTransactionCount"),
- Pair("id", 1),
- Pair("params", listOf(address.toHexString(), "latest"))
- )
- val deferred = CompletableDeferred<UInt256>()
-
- client.post(serverPort, serverHost, "/")
- .putHeader("Content-Type", "application/json")
- .sendBuffer(Buffer.buffer(mapper.writeValueAsBytes(body))) { response ->
- if (response.failed()) {
- deferred.completeExceptionally(response.cause())
- } else {
- val jsonResponse = response.result().bodyAsJsonObject()
- deferred.complete(UInt256.fromHexString(jsonResponse.getString("result")))
- }
- }
-
- return deferred.await()
+ val body = JSONRPCRequest(nextId(), "eth_getTransactionCount", arrayOf(address.toHexString(), "latest"))
+ val jsonResponse = sendRequest(body).await()
+ val err = jsonResponse.error
+ if (err != null) {
+ val errorMessage = "Code ${err.code}: ${err.message}"
+ throw ClientRequestException(errorMessage)
+ } else {
+ return UInt256.fromHexString(jsonResponse.result.toString())
+ }
}
override fun close() {
client.close()
}
+
+ private fun nextId(): Int {
+ val next = requestCounter.incrementAndGet()
+ if (next == Int.MAX_VALUE) {
+ requestCounter.set(1)
+ }
+ return next
+ }
}
diff --git a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandler.kt b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandler.kt
index ea50ed5..9dc92a0 100644
--- a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandler.kt
+++ b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandler.kt
@@ -18,6 +18,7 @@ package org.apache.tuweni.jsonrpc.methods
import io.opentelemetry.api.metrics.LongCounter
import io.opentelemetry.api.metrics.common.Labels
+import org.apache.tuweni.eth.JSONRPCError
import org.apache.tuweni.eth.JSONRPCRequest
import org.apache.tuweni.eth.JSONRPCResponse
import org.apache.tuweni.eth.methodNotFound
@@ -59,7 +60,7 @@ class MethodAllowListHandler(private val allowedMethods: List<String>, private v
}
}
if (!found) {
- return JSONRPCResponse(request.id, null, mapOf(Pair("code", -32604), Pair("message", "Method not enabled")))
+ return JSONRPCResponse(request.id, null, JSONRPCError(-32604, "Method not enabled"))
}
return delegateHandler(request)
}
diff --git a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandlerTest.kt b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandlerTest.kt
index fe0a2a2..c9778f8 100644
--- a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandlerTest.kt
+++ b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/MethodsHandlerTest.kt
@@ -20,6 +20,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider
import io.opentelemetry.sdk.metrics.export.IntervalMetricReader
import io.opentelemetry.sdk.metrics.export.MetricProducer
import io.opentelemetry.sdk.metrics.testing.InMemoryMetricExporter
+import org.apache.tuweni.eth.JSONRPCError
import org.apache.tuweni.eth.JSONRPCRequest
import org.apache.tuweni.eth.JSONRPCResponse
import org.apache.tuweni.eth.methodNotFound
@@ -89,7 +90,7 @@ class MethodsHandlerTest {
val successCounter = meter.longCounterBuilder("success").build()
val failCounter = meter.longCounterBuilder("fail").build()
val meteredHandler = MeteredHandler(successCounter, failCounter) {
- JSONRPCResponse(1, error = "foo")
+ JSONRPCResponse(1, error = JSONRPCError(123, "foo"))
}
meteredHandler.handleRequest(JSONRPCRequest(1, "foo", emptyArray()))
Thread.sleep(1200)
@@ -117,8 +118,8 @@ class MethodAllowListHandlerTest {
val filter = MethodAllowListHandler(listOf("eth_")) { JSONRPCResponse(1, "foo") }
val resp = filter.handleRequest(JSONRPCRequest(1, "foo_client", emptyArray()))
assertNotNull(resp.error)
- val respContents = resp.error as Map<*, *>
- assertEquals(-32604, respContents["code"])
- assertEquals("Method not enabled", respContents["message"])
+ val respContents = resp.error as JSONRPCError
+ assertEquals(-32604, respContents.code)
+ assertEquals("Method not enabled", respContents.message)
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tuweni.apache.org
For additional commands, e-mail: commits-help@tuweni.apache.org