Better handle exceptions for JSON-RPC
diff --git a/dependency-versions.gradle b/dependency-versions.gradle
index 1b45efd..b003a43 100644
--- a/dependency-versions.gradle
+++ b/dependency-versions.gradle
@@ -39,6 +39,7 @@
dependency('io.lettuce:lettuce-core:5.1.3.RELEASE')
dependency('io.vertx:vertx-core:3.9.4')
+ dependency('io.vertx:vertx-web-client:3.9.4')
dependency('io.vertx:vertx-lang-kotlin:3.9.4')
dependency('io.vertx:vertx-lang-kotlin-coroutines:3.9.4')
dependency('io.vertx:vertx-web:3.9.4')
@@ -97,6 +98,9 @@
dependency('org.rocksdb:rocksdbjni:5.17.2')
dependency('org.slf4j:slf4j-api:1.7.30')
+ dependency('org.webjars:bootstrap:4.1.3')
+ dependency('org.webjars:webjars-locator:0.40')
+
dependency('org.xerial.snappy:snappy-java:1.1.7.2')
}
}
diff --git a/jsonrpc/build.gradle b/jsonrpc/build.gradle
index 4f155dc..b7d7168 100644
--- a/jsonrpc/build.gradle
+++ b/jsonrpc/build.gradle
@@ -18,6 +18,7 @@
implementation "com.google.guava:guava"
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation 'io.vertx:vertx-core'
+ implementation 'io.vertx:vertx-web-client'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
implementation 'io.vertx:vertx-lang-kotlin-coroutines'
implementation 'io.vertx:vertx-lang-kotlin'
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 cf351bf..5d146a1 100644
--- a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt
+++ b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt
@@ -19,30 +19,32 @@
import com.fasterxml.jackson.databind.ObjectMapper
import io.vertx.core.Vertx
import io.vertx.core.buffer.Buffer
-import io.vertx.core.http.HttpMethod
-import io.vertx.core.json.JsonObject
-import io.vertx.kotlin.core.http.endAwait
+import io.vertx.ext.web.client.WebClient
import kotlinx.coroutines.CompletableDeferred
import org.apache.tuweni.eth.Address
import org.apache.tuweni.eth.Transaction
import org.apache.tuweni.units.bigints.UInt256
-import org.slf4j.LoggerFactory
import java.io.Closeable
-val logger = LoggerFactory.getLogger(JSONRPCClient::class.java)
val mapper = ObjectMapper()
+
/**
* JSON-RPC client to send requests to an Ethereum client.
*/
-class JSONRPCClient(vertx: Vertx, val serverPort: Int, val serverHost: String) : Closeable {
+class JSONRPCClient(
+ vertx: Vertx,
+ val serverPort: Int,
+ val serverHost: String
+) : Closeable {
- val client = vertx.createHttpClient()
+ val client = WebClient.create(vertx)
/**
* 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 is the request is rejected
+ * @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(
@@ -53,23 +55,22 @@
)
val deferred = CompletableDeferred<String>()
- @Suppress("DEPRECATION")
- client.request(HttpMethod.POST, serverPort, serverHost, "/") { response ->
- response.bodyHandler {
- val jsonResponse = it.toJson() as JsonObject
- if (jsonResponse.containsKey("error")) {
- val err = jsonResponse.getJsonObject("error")
- val errorMessage = "Code ${err.getInteger("code")}: ${err.getString("message")}"
- deferred.completeExceptionally(ClientRequestException(errorMessage))
+ client.post(serverPort, serverHost, "/")
+ .putHeader("Content-Type", "application/json")
+ .sendBuffer(Buffer.buffer(mapper.writeValueAsBytes(body))) { response ->
+ if (response.failed()) {
+ deferred.completeExceptionally(response.cause())
} else {
- deferred.complete(jsonResponse.getString("result"))
+ val jsonResponse = response.result().bodyAsJsonObject()
+ if (jsonResponse.containsKey("error")) {
+ val err = jsonResponse.getJsonObject("error")
+ val errorMessage = "Code ${err.getInteger("code")}: ${err.getString("message")}"
+ deferred.completeExceptionally(ClientRequestException(errorMessage))
+ } else {
+ deferred.complete(jsonResponse.getString("result"))
+ }
}
- }.exceptionHandler {
- deferred.completeExceptionally(it)
}
- }.putHeader("Content-Type", "application/json")
- .exceptionHandler { deferred.completeExceptionally(it) }
- .endAwait(Buffer.buffer(mapper.writeValueAsBytes(body)))
return deferred.await()
}
@@ -78,7 +79,8 @@
* Gets the account balance.
* @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 is the request is rejected
+ * @throws ClientRequestException if the request is rejected
+ * @throws ConnectException if it cannot dial the remote client
*/
suspend fun getBalance_latest(address: Address): UInt256 {
val body = mapOf(
@@ -89,17 +91,16 @@
)
val deferred = CompletableDeferred<UInt256>()
- @Suppress("DEPRECATION")
- client.request(HttpMethod.POST, serverPort, serverHost, "/") { response ->
- response.bodyHandler {
- val jsonResponse = it.toJson() as JsonObject
- deferred.complete(UInt256.fromHexString(jsonResponse.getString("result")))
- }.exceptionHandler {
- deferred.completeExceptionally(it)
+ 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")))
+ }
}
- }.putHeader("Content-Type", "application/json")
- .exceptionHandler { deferred.completeExceptionally(it) }
- .endAwait(Buffer.buffer(mapper.writeValueAsBytes(body)))
return deferred.await()
}
@@ -108,7 +109,8 @@
* Gets the number of transactions sent from an address.
* @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 is the request is rejected
+ * @throws ClientRequestException if the request is rejected
+ * @throws ConnectException if it cannot dial the remote client
*/
suspend fun getTransactionCount_latest(address: Address): UInt256 {
val body = mapOf(
@@ -119,17 +121,16 @@
)
val deferred = CompletableDeferred<UInt256>()
- @Suppress("DEPRECATION")
- client.request(HttpMethod.POST, serverPort, serverHost, "/") { response ->
- response.bodyHandler {
- val jsonResponse = it.toJson() as JsonObject
- deferred.complete(UInt256.fromHexString(jsonResponse.getString("result")))
- }.exceptionHandler {
- deferred.completeExceptionally(it)
+ 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")))
+ }
}
- }.putHeader("Content-Type", "application/json")
- .exceptionHandler { deferred.completeExceptionally(it) }
- .endAwait(Buffer.buffer(mapper.writeValueAsBytes(body)))
return deferred.await()
}
diff --git a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClientTest.kt b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClientTest.kt
index 30ac56d..335ddb5 100644
--- a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClientTest.kt
+++ b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClientTest.kt
@@ -36,7 +36,9 @@
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith
+import java.net.ConnectException
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicReference
@@ -97,4 +99,13 @@
)
}
}
+
+ @Test
+ fun testGetBalanceToMissingClient(@VertxInstance vertx: Vertx) {
+ JSONRPCClient(vertx, 1234, "localhost").use {
+ assertThrows<ConnectException> {
+ runBlocking { it.getBalance_latest(Address.fromHexString("0x0102030405060708090a0b0c0d0e0f0102030405")) }
+ }
+ }
+ }
}