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 2022/06/17 17:46:50 UTC

[incubator-tuweni] branch main updated: Make JSONRPC use long numbers to match usage Make JSONRPC requests/responses support string ids instead of numbers

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 3da4a7ac Make JSONRPC use long numbers to match usage Make JSONRPC requests/responses support string ids instead of numbers
     new a33dcdf7 Merge pull request #414 from atoulme/jsonrpc-proxy
3da4a7ac is described below

commit 3da4a7ac40fd92c7c59fd1519656c8d955cc8b61
Author: Antoine Toulme <an...@lunar-ocean.com>
AuthorDate: Thu Jun 16 20:16:37 2022 -0700

    Make JSONRPC use long numbers to match usage
    Make JSONRPC requests/responses support string ids instead of numbers
---
 .../java/org/apache/tuweni/eth/EthJsonModule.java  | 36 +++++++++
 .../java/org/apache/tuweni/eth/StringOrLong.java   | 57 +++++++++++++
 .../kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt | 10 ++-
 .../org/apache/tuweni/eth/JSONRPCResponse.kt       | 18 ++---
 .../java/org/apache/tuweni/eth/JsonRpcTest.java    | 42 ++++++++++
 .../org/apache/tuweni/jsonrpc/app/JSONRPCApp.kt    |  5 +-
 jsonrpc-app/src/main/resources/logback.xml         |  4 +
 .../org/apache/tuweni/jsonrpc/JSONRPCClient.kt     | 25 ++++--
 .../org/apache/tuweni/jsonrpc/JSONRPCServer.kt     |  2 +
 .../tuweni/jsonrpc/methods/MethodsHandler.kt       | 20 ++++-
 .../org/apache/tuweni/jsonrpc/methods/Web3.kt      |  5 +-
 .../org/apache/tuweni/jsonrpc/JSONRPCClientTest.kt |  5 +-
 .../org/apache/tuweni/jsonrpc/JSONRPCServerTest.kt |  5 +-
 .../tuweni/jsonrpc/methods/MethodsHandlerTest.kt   | 94 +++++++++++++---------
 .../org/apache/tuweni/jsonrpc/methods/NetTest.kt   |  7 +-
 .../org/apache/tuweni/stratum/StratumServerApp.kt  |  9 ++-
 16 files changed, 272 insertions(+), 72 deletions(-)

diff --git a/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java b/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java
index e701e9f8..476beb39 100644
--- a/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java
+++ b/eth/src/main/java/org/apache/tuweni/eth/EthJsonModule.java
@@ -24,6 +24,7 @@ import java.time.Instant;
 
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.KeyDeserializer;
 import com.fasterxml.jackson.databind.SerializerProvider;
@@ -261,6 +262,39 @@ public class EthJsonModule extends SimpleModule {
     }
   }
 
+  static class StringOrLongDeserializer extends StdDeserializer<StringOrLong> {
+
+    public StringOrLongDeserializer() {
+      super(StringOrLong.class);
+    }
+
+    @Override
+    public StringOrLong deserialize(JsonParser p, DeserializationContext ctxt) throws IOException,
+        JsonProcessingException {
+      if (p.currentToken().isNumeric()) {
+        return new StringOrLong(p.getLongValue());
+      } else {
+        return new StringOrLong(p.getValueAsString());
+      }
+    }
+  }
+
+  static class StringOrLongSerializer extends StdSerializer<StringOrLong> {
+
+    public StringOrLongSerializer() {
+      super(StringOrLong.class);
+    }
+
+    @Override
+    public void serialize(StringOrLong value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+      if (value.getValueAsString() == null) {
+        gen.writeNumber(value.getValueAsLong());
+      } else {
+        gen.writeString(value.getValueAsString());
+      }
+    }
+  }
+
   public EthJsonModule() {
     addSerializer(Hash.class, new HashSerializer());
     addDeserializer(Hash.class, new HashDeserializer());
@@ -281,5 +315,7 @@ public class EthJsonModule extends SimpleModule {
     addDeserializer(Bytes.class, new BytesDeserializer());
     addDeserializer(Bytes32.class, new Bytes32Deserializer());
     addSerializer(SECP256K1.PublicKey.class, new PublicKeySerializer());
+    addSerializer(StringOrLong.class, new StringOrLongSerializer());
+    addDeserializer(StringOrLong.class, new StringOrLongDeserializer());
   }
 }
diff --git a/eth/src/main/java/org/apache/tuweni/eth/StringOrLong.java b/eth/src/main/java/org/apache/tuweni/eth/StringOrLong.java
new file mode 100644
index 00000000..43d456b9
--- /dev/null
+++ b/eth/src/main/java/org/apache/tuweni/eth/StringOrLong.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.eth;
+
+import java.util.Objects;
+
+/**
+ * Class representing the identifier of a JSON-RPC request or response.
+ *
+ * The identifier can be a string or a number, but not both at the same time.
+ */
+public class StringOrLong {
+
+  private Long valueAsLong;
+  private String valueAsString;
+
+  public StringOrLong(String value) {
+    this.valueAsString = value;
+  }
+
+  public StringOrLong(Long value) {
+    this.valueAsLong = value;
+  }
+
+  public String getValueAsString() {
+    return valueAsString;
+  }
+
+  public Long getValueAsLong() {
+    return valueAsLong;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (!(o instanceof StringOrLong))
+      return false;
+    StringOrLong that = (StringOrLong) o;
+    return Objects.equals(valueAsLong, that.valueAsLong) || Objects.equals(valueAsString, that.valueAsString);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(valueAsLong, valueAsString);
+  }
+}
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 20d5804b..20694907 100644
--- a/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt
+++ b/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCRequest.kt
@@ -19,14 +19,16 @@ package org.apache.tuweni.eth
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties
 import com.fasterxml.jackson.annotation.JsonProperty
 
+data class Id(val idAsString: String?, val idAsLong: Long?)
+
 /**
  * JSONRPCRequest represents a JSON-RPC request to a JSON-RPC service.
  */
 @JsonIgnoreProperties(ignoreUnknown = true)
-data class JSONRPCRequest(
-  @JsonProperty("id") val id: Int,
+data class JSONRPCRequest constructor(
+  @JsonProperty("id") val id: StringOrLong,
   @JsonProperty("method") val method: String,
-  @JsonProperty("params") val params: Array<String>,
+  @JsonProperty("params") val params: Array<Any>,
   @JsonProperty("jsonrpc") val jsonrpc: String = "2.0"
 ) {
 
@@ -43,7 +45,7 @@ data class JSONRPCRequest(
      */
     fun deserialize(serialized: String): JSONRPCRequest {
       val segments = serialized.split("|")
-      return JSONRPCRequest(id = 0, method = segments[0], params = segments[1].split(",").toTypedArray())
+      return JSONRPCRequest(id = StringOrLong(0), method = segments[0], params = segments[1].split(",").toTypedArray())
     }
   }
 
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 203587e2..b94ba922 100644
--- a/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCResponse.kt
+++ b/eth/src/main/kotlin/org/apache/tuweni/eth/JSONRPCResponse.kt
@@ -20,14 +20,14 @@ 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: JSONRPCError? = null, @JsonProperty("jsonrpc") val jsonrpc: String = "2.0")
+data class JSONRPCResponse(@JsonProperty("id") val id: StringOrLong, @JsonProperty("result") val result: Any? = null, @JsonProperty("error") val error: JSONRPCError? = null, @JsonProperty("jsonrpc") val jsonrpc: String = "2.0")
 
-data class JSONRPCError(@JsonProperty("code") val code: Int, @JsonProperty("message") val message: String)
+data class JSONRPCError(@JsonProperty("code") val code: Long, @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"))
-val tooManyRequests = JSONRPCResponse(id = 0, error = JSONRPCError(code = -32000, message = "Too many requests"))
-val methodNotEnabled = JSONRPCResponse(id = 0, error = JSONRPCError(-32604, "Method not enabled"))
+val parseError = JSONRPCResponse(id = StringOrLong(0), error = JSONRPCError(-32700, "Parse error"))
+val invalidRequest = JSONRPCResponse(id = StringOrLong(0), error = JSONRPCError(-32600, "Invalid Request"))
+val methodNotFound = JSONRPCResponse(id = StringOrLong(0), error = JSONRPCError(-32601, "Method not found"))
+val invalidParams = JSONRPCResponse(id = StringOrLong(0), error = JSONRPCError(-32602, "Invalid params"))
+val internalError = JSONRPCResponse(id = StringOrLong(0), error = JSONRPCError(-32603, "Internal error"))
+val tooManyRequests = JSONRPCResponse(id = StringOrLong(0), error = JSONRPCError(code = -32000, message = "Too many requests"))
+val methodNotEnabled = JSONRPCResponse(id = StringOrLong(0), error = JSONRPCError(-32604, "Method not enabled"))
diff --git a/eth/src/test/java/org/apache/tuweni/eth/JsonRpcTest.java b/eth/src/test/java/org/apache/tuweni/eth/JsonRpcTest.java
new file mode 100644
index 00000000..744dab51
--- /dev/null
+++ b/eth/src/test/java/org/apache/tuweni/eth/JsonRpcTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.tuweni.eth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+public class JsonRpcTest {
+
+  private static final ObjectMapper mapper = new ObjectMapper();
+  static {
+    mapper.registerModule(new EthJsonModule());
+  }
+
+  @Test
+  void testJsonRpcRequestRoundtrip() throws Exception {
+    JSONRPCRequest req = new JSONRPCRequest(new StringOrLong("3"), "foo_method", new Object[] {"foo", "bar"}, "2.0");
+    String value = mapper.writeValueAsString(req);
+    JSONRPCRequest req2 = mapper.readValue(value, JSONRPCRequest.class);
+    assertEquals(req, req2);
+  }
+
+  @Test
+  void testJsonRpcResponseRoundtrip() throws Exception {
+    JSONRPCResponse resp = new JSONRPCResponse(new StringOrLong("3"), "result", null, "2.0");
+    String value = mapper.writeValueAsString(resp);
+    JSONRPCResponse resp2 = mapper.readValue(value, JSONRPCResponse.class);
+    assertEquals(resp, resp2);
+  }
+}
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 d729db13..afb8ea66 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
@@ -31,6 +31,7 @@ import org.apache.tuweni.eth.JSONRPCResponse
 import org.apache.tuweni.jsonrpc.JSONRPCClient
 import org.apache.tuweni.jsonrpc.JSONRPCServer
 import org.apache.tuweni.jsonrpc.methods.CachingHandler
+import org.apache.tuweni.jsonrpc.methods.LoggingHandler
 import org.apache.tuweni.jsonrpc.methods.MeteredHandler
 import org.apache.tuweni.jsonrpc.methods.MethodAllowListHandler
 import org.apache.tuweni.jsonrpc.methods.ThrottlingHandler
@@ -150,7 +151,9 @@ class JSONRPCApplication(
 
     val throttlingHandler = ThrottlingHandler(config.maxConcurrentRequests(), nextHandler)
 
-    val handler = MeteredHandler(successCounter, failureCounter, throttlingHandler::handleRequest)
+    val loggingHandler = LoggingHandler(throttlingHandler::handleRequest, "jsonrpclog")
+
+    val handler = MeteredHandler(successCounter, failureCounter, loggingHandler::handleRequest)
     val server = JSONRPCServer(
       vertx,
       config.port(), config.networkInterface(),
diff --git a/jsonrpc-app/src/main/resources/logback.xml b/jsonrpc-app/src/main/resources/logback.xml
index cb63fd52..89f01051 100644
--- a/jsonrpc-app/src/main/resources/logback.xml
+++ b/jsonrpc-app/src/main/resources/logback.xml
@@ -21,6 +21,10 @@
         </encoder>
     </appender>
 
+    <logger level="info" name="jsonrpclog" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
     <root level="info">
         <appender-ref ref="STDOUT" />
     </root>
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 147bd8ca..b5318ef1 100644
--- a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt
+++ b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClient.kt
@@ -27,13 +27,15 @@ import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
 import org.apache.tuweni.eth.Address
+import org.apache.tuweni.eth.EthJsonModule
 import org.apache.tuweni.eth.JSONRPCRequest
 import org.apache.tuweni.eth.JSONRPCResponse
+import org.apache.tuweni.eth.StringOrLong
 import org.apache.tuweni.eth.Transaction
 import org.apache.tuweni.units.bigints.UInt256
 import java.io.Closeable
 import java.util.Base64
-import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
 import kotlin.coroutines.CoroutineContext
 
 val mapper = ObjectMapper()
@@ -51,7 +53,13 @@ class JSONRPCClient(
   override val coroutineContext: CoroutineContext = vertx.dispatcher(),
 ) : Closeable, CoroutineScope {
 
-  val requestCounter = AtomicInteger(1)
+  companion object {
+    private val mapper = ObjectMapper()
+    init {
+      mapper.registerModule(EthJsonModule())
+    }
+  }
+  val requestCounter = AtomicLong(1)
   val client = WebClient.create(
     vertx,
     WebClientOptions().setUserAgent(userAgent).setTryUseCompression(true)
@@ -73,7 +81,8 @@ class JSONRPCClient(
       if (response.failed()) {
         deferred.completeExceptionally(response.cause())
       } else {
-        val jsonResponse = response.result().bodyAsJson(JSONRPCResponse::class.java)
+        println(response.result().bodyAsString())
+        val jsonResponse = mapper.readValue(response.result().bodyAsString(), JSONRPCResponse::class.java)
         deferred.complete(jsonResponse)
       }
     }
@@ -89,7 +98,7 @@ class JSONRPCClient(
    * @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 body = JSONRPCRequest(StringOrLong(nextId()), "eth_sendRawTransaction", arrayOf(tx.toBytes().toHexString()))
     val jsonResponse = sendRequest(body).await()
     val err = jsonResponse.error
     if (err != null) {
@@ -108,7 +117,7 @@ class JSONRPCClient(
    * @throws ConnectException if it cannot dial the remote client
    */
   suspend fun getBalance_latest(address: Address): UInt256 {
-    val body = JSONRPCRequest(nextId(), "eth_getBalance", arrayOf(address.toHexString(), "latest"))
+    val body = JSONRPCRequest(StringOrLong(nextId()), "eth_getBalance", arrayOf(address.toHexString(), "latest"))
     val jsonResponse = sendRequest(body).await()
     val err = jsonResponse.error
     if (err != null) {
@@ -127,7 +136,7 @@ class JSONRPCClient(
    * @throws ConnectException if it cannot dial the remote client
    */
   suspend fun getTransactionCount_latest(address: Address): UInt256 {
-    val body = JSONRPCRequest(nextId(), "eth_getTransactionCount", arrayOf(address.toHexString(), "latest"))
+    val body = JSONRPCRequest(StringOrLong(nextId()), "eth_getTransactionCount", arrayOf(address.toHexString(), "latest"))
     val jsonResponse = sendRequest(body).await()
     val err = jsonResponse.error
     if (err != null) {
@@ -142,9 +151,9 @@ class JSONRPCClient(
     client.close()
   }
 
-  private fun nextId(): Int {
+  private fun nextId(): Long {
     val next = requestCounter.incrementAndGet()
-    if (next == Int.MAX_VALUE) {
+    if (next == Long.MAX_VALUE) {
       requestCounter.set(1)
     }
     return next
diff --git a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServer.kt b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServer.kt
index 561c6e7b..4bb638c0 100644
--- a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServer.kt
+++ b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServer.kt
@@ -48,6 +48,7 @@ import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
+import org.apache.tuweni.eth.EthJsonModule
 import org.apache.tuweni.eth.JSONRPCRequest
 import org.apache.tuweni.eth.JSONRPCResponse
 import org.apache.tuweni.eth.internalError
@@ -79,6 +80,7 @@ class JSONRPCServer(
     val mapper = ObjectMapper()
 
     init {
+      mapper.registerModule(EthJsonModule())
       mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
     }
   }
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 2feab0d5..2206f1aa 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
@@ -26,6 +26,7 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import org.apache.tuweni.eth.JSONRPCRequest
 import org.apache.tuweni.eth.JSONRPCResponse
+import org.apache.tuweni.eth.StringOrLong
 import org.apache.tuweni.eth.methodNotEnabled
 import org.apache.tuweni.eth.methodNotFound
 import org.apache.tuweni.eth.tooManyRequests
@@ -167,9 +168,9 @@ class CachingPollingHandler(
   private fun poll() {
     launch {
       try {
-        var id = 1337
+        var id = 1337L
         for (cachedRequest in cachedRequests) {
-          val newResponse = delegateHandler(cachedRequest.copy(id = id))
+          val newResponse = delegateHandler(cachedRequest.copy(id = StringOrLong(id)))
           id++
           if (newResponse.error == null) {
             cacheStore.put(cachedRequest, newResponse)
@@ -209,6 +210,21 @@ class CachingPollingHandler(
   }
 }
 
+class LoggingHandler(
+  private val delegateHandler: suspend (JSONRPCRequest) -> JSONRPCResponse,
+  loggerName: String,
+) {
+
+  private val logger = LoggerFactory.getLogger(loggerName)
+
+  suspend fun handleRequest(request: JSONRPCRequest): JSONRPCResponse {
+    logger.info(request.toString())
+    val response = delegateHandler.invoke(request)
+    logger.info(response.toString())
+    return response
+  }
+}
+
 class ConstantStringResult(val result: String) {
   suspend fun handle(request: JSONRPCRequest): JSONRPCResponse {
     return JSONRPCResponse(id = request.id, result = result)
diff --git a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/Web3.kt b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/Web3.kt
index aa75ec72..34a920e4 100644
--- a/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/Web3.kt
+++ b/jsonrpc/src/main/kotlin/org/apache/tuweni/jsonrpc/methods/Web3.kt
@@ -31,8 +31,11 @@ suspend fun sha3(request: JSONRPCRequest): JSONRPCResponse {
   if (request.params.size != 1) {
     return invalidParams.copy(id = request.id)
   }
+  if (!(request.params[0] is String)) {
+    return invalidParams.copy(id = request.id)
+  }
   try {
-    val input = Bytes.fromHexString(request.params[0])
+    val input = Bytes.fromHexString(request.params[0] as String)
     return JSONRPCResponse(id = request.id, result = Hash.hash(input).toHexString())
   } catch (e: IllegalArgumentException) {
     return invalidParams.copy(id = request.id)
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 a267f59b..a9e9868f 100644
--- a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClientTest.kt
+++ b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCClientTest.kt
@@ -27,6 +27,7 @@ import org.apache.tuweni.crypto.SECP256K1
 import org.apache.tuweni.eth.Address
 import org.apache.tuweni.eth.JSONRPCRequest
 import org.apache.tuweni.eth.JSONRPCResponse
+import org.apache.tuweni.eth.StringOrLong
 import org.apache.tuweni.eth.Transaction
 import org.apache.tuweni.junit.BouncyCastleExtension
 import org.apache.tuweni.junit.VertxExtension
@@ -60,7 +61,7 @@ class JSONRPCClientTest {
         vertx, port = 0,
         methodHandler = {
           handler.get().handle(it)
-          JSONRPCResponse(3, "")
+          JSONRPCResponse(StringOrLong(3), "")
         },
         coroutineContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
       )
@@ -91,7 +92,7 @@ class JSONRPCClientTest {
       )
       val sent = CompletableDeferred<String>()
       handler.set { request ->
-        sent.complete(request.params.get(0))
+        sent.complete(request.params.get(0) as String)
         JSONRPCResponse(request.id, "")
       }
 
diff --git a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServerTest.kt b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServerTest.kt
index 76f49391..4e497d82 100644
--- a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServerTest.kt
+++ b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/JSONRPCServerTest.kt
@@ -23,6 +23,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.runBlocking
 import org.apache.tuweni.concurrent.coroutines.await
 import org.apache.tuweni.eth.JSONRPCResponse
+import org.apache.tuweni.eth.StringOrLong
 import org.apache.tuweni.io.Base64
 import org.apache.tuweni.junit.VertxExtension
 import org.apache.tuweni.junit.VertxInstance
@@ -53,7 +54,7 @@ class JSONRPCServerTest {
     val server = JSONRPCServer(
       vertx, port = 0,
       methodHandler = {
-        JSONRPCResponse(3, "")
+        JSONRPCResponse(StringOrLong(3), "")
       },
       coroutineContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
     )
@@ -73,7 +74,7 @@ class JSONRPCServerTest {
     val server = JSONRPCServer(
       vertx, port = 0,
       methodHandler = {
-        JSONRPCResponse(3, "")
+        JSONRPCResponse(StringOrLong(3), "")
       },
       useBasicAuthentication = true,
       basicAuthenticationPassword = "pass",
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 ef881027..1e1f92b8 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
@@ -26,6 +26,7 @@ import kotlinx.coroutines.runBlocking
 import org.apache.tuweni.eth.JSONRPCError
 import org.apache.tuweni.eth.JSONRPCRequest
 import org.apache.tuweni.eth.JSONRPCResponse
+import org.apache.tuweni.eth.StringOrLong
 import org.apache.tuweni.eth.methodNotFound
 import org.apache.tuweni.junit.BouncyCastleExtension
 import org.apache.tuweni.kv.MapKeyValueStore
@@ -42,13 +43,22 @@ class MethodsHandlerTest {
   @Test
   fun testMissingMethod() = runBlocking {
     val methodsRouter = MethodsRouter(emptyMap())
-    assertEquals(methodNotFound, methodsRouter.handleRequest(JSONRPCRequest(1, "web3_sha3", arrayOf("0xdeadbeef"))))
+    assertEquals(
+      methodNotFound,
+      methodsRouter.handleRequest(JSONRPCRequest(StringOrLong(1), "web3_sha3", arrayOf("0xdeadbeef")))
+    )
   }
 
   @Test
   fun testRouteMethod() = runBlocking {
     val methodsRouter = MethodsRouter(mapOf(Pair("web3_sha3", ::sha3)))
-    assertEquals(JSONRPCResponse(1, result = "0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1"), methodsRouter.handleRequest(JSONRPCRequest(1, "web3_sha3", arrayOf("0xdeadbeef"))))
+    assertEquals(
+      JSONRPCResponse(
+        StringOrLong(1),
+        result = "0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1"
+      ),
+      methodsRouter.handleRequest(JSONRPCRequest(StringOrLong(1), "web3_sha3", arrayOf("0xdeadbeef")))
+    )
   }
 
   @Test
@@ -66,9 +76,9 @@ class MethodsHandlerTest {
     val successCounter = meter.longCounterBuilder("success").build()
     val failCounter = meter.longCounterBuilder("fail").build()
     val meteredHandler = MeteredHandler(successCounter, failCounter) {
-      JSONRPCResponse(1)
+      JSONRPCResponse(StringOrLong(1))
     }
-    meteredHandler.handleRequest(JSONRPCRequest(1, "foo", emptyArray()))
+    meteredHandler.handleRequest(JSONRPCRequest(StringOrLong(1), "foo", emptyArray()))
     Thread.sleep(1200)
     var metricValue = 0L
     for (metric in exporter.finishedMetricItems) {
@@ -94,9 +104,9 @@ class MethodsHandlerTest {
     val successCounter = meter.longCounterBuilder("success").build()
     val failCounter = meter.longCounterBuilder("fail").build()
     val meteredHandler = MeteredHandler(successCounter, failCounter) {
-      JSONRPCResponse(1, error = JSONRPCError(123, "foo"))
+      JSONRPCResponse(StringOrLong(1), error = JSONRPCError(123, "foo"))
     }
-    meteredHandler.handleRequest(JSONRPCRequest(1, "foo", emptyArray()))
+    meteredHandler.handleRequest(JSONRPCRequest(StringOrLong(1), "foo", emptyArray()))
     Thread.sleep(1200)
     var metricValue = 0L
     for (metric in exporter.finishedMetricItems) {
@@ -112,15 +122,15 @@ class MethodAllowListHandlerTest {
 
   @Test
   fun testAllowedMethod() = runBlocking {
-    val filter = MethodAllowListHandler(listOf("eth_")) { JSONRPCResponse(1, "foo") }
-    val resp = filter.handleRequest(JSONRPCRequest(1, "eth_client", emptyArray()))
+    val filter = MethodAllowListHandler(listOf("eth_")) { JSONRPCResponse(StringOrLong(1), "foo") }
+    val resp = filter.handleRequest(JSONRPCRequest(StringOrLong(1), "eth_client", emptyArray()))
     assertNull(resp.error)
   }
 
   @Test
   fun testForbiddenMethod() = runBlocking {
-    val filter = MethodAllowListHandler(listOf("eth_")) { JSONRPCResponse(1, "foo") }
-    val resp = filter.handleRequest(JSONRPCRequest(1, "foo_client", emptyArray()))
+    val filter = MethodAllowListHandler(listOf("eth_")) { JSONRPCResponse(StringOrLong(1), "foo") }
+    val resp = filter.handleRequest(JSONRPCRequest(StringOrLong(1), "foo_client", emptyArray()))
     assertNotNull(resp.error)
     val respContents = resp.error as JSONRPCError
     assertEquals(-32604, respContents.code)
@@ -135,34 +145,34 @@ class ThrottlingHandlerTest {
     val handler = ThrottlingHandler(4) {
       runBlocking {
         delay(500)
-        JSONRPCResponse(id = 1)
+        JSONRPCResponse(id = StringOrLong(1))
       }
     }
     async {
-      val response = handler.handleRequest(JSONRPCRequest(2, "foo", arrayOf()))
-      assertEquals(1, response.id)
+      val response = handler.handleRequest(JSONRPCRequest(StringOrLong(2), "foo", arrayOf()))
+      assertEquals(StringOrLong(1), response.id)
     }
     async {
-      val response = handler.handleRequest(JSONRPCRequest(3, "foo", arrayOf()))
-      assertEquals(1, response.id)
+      val response = handler.handleRequest(JSONRPCRequest(StringOrLong(3), "foo", arrayOf()))
+      assertEquals(StringOrLong(1), response.id)
     }
     async {
-      val response = handler.handleRequest(JSONRPCRequest(4, "foo", arrayOf()))
-      assertEquals(1, response.id)
+      val response = handler.handleRequest(JSONRPCRequest(StringOrLong(4), "foo", arrayOf()))
+      assertEquals(StringOrLong(1), response.id)
     }
     async {
-      val response = handler.handleRequest(JSONRPCRequest(5, "foo", arrayOf()))
-      assertEquals(1, response.id)
+      val response = handler.handleRequest(JSONRPCRequest(StringOrLong(5), "foo", arrayOf()))
+      assertEquals(StringOrLong(1), response.id)
     }
     async {
       delay(200)
-      val response = handler.handleRequest(JSONRPCRequest(6, "foo", arrayOf()))
+      val response = handler.handleRequest(JSONRPCRequest(StringOrLong(6), "foo", arrayOf()))
       assertEquals(-32000, response.error?.code)
     }
     async {
       delay(1000)
-      val response = handler.handleRequest(JSONRPCRequest(7, "foo", arrayOf()))
-      assertEquals(1, response.id)
+      val response = handler.handleRequest(JSONRPCRequest(StringOrLong(7), "foo", arrayOf()))
+      assertEquals(StringOrLong(1), response.id)
     }
   }
 }
@@ -175,21 +185,26 @@ class CachingHandlerTest {
     val kv = MapKeyValueStore.open(map)
     val meterSdk = SdkMeterProvider.builder().build()
     val meter = meterSdk.get("handler")
-    val handler = CachingHandler(listOf("foo"), kv, meter.longCounterBuilder("foo").build(), meter.longCounterBuilder("bar").build()) {
+    val handler = CachingHandler(
+      listOf("foo"),
+      kv,
+      meter.longCounterBuilder("foo").build(),
+      meter.longCounterBuilder("bar").build()
+    ) {
       if (it.params.isNotEmpty()) {
-        JSONRPCResponse(id = 1, error = JSONRPCError(1234, ""))
+        JSONRPCResponse(id = StringOrLong(1), error = JSONRPCError(1234, ""))
       } else {
-        JSONRPCResponse(id = 1)
+        JSONRPCResponse(id = StringOrLong(1))
       }
     }
     assertEquals(0, map.size)
-    handler.handleRequest(JSONRPCRequest(id = 1, method = "foo", params = arrayOf()))
+    handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "foo", params = arrayOf()))
     assertEquals(1, map.size)
-    handler.handleRequest(JSONRPCRequest(id = 1, method = "bar", params = arrayOf()))
+    handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "bar", params = arrayOf()))
     assertEquals(1, map.size)
-    handler.handleRequest(JSONRPCRequest(id = 1, method = "foo", params = arrayOf()))
+    handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "foo", params = arrayOf()))
     assertEquals(1, map.size)
-    handler.handleRequest(JSONRPCRequest(id = 1, method = "foo", params = arrayOf("bleh")))
+    handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "foo", params = arrayOf("bleh")))
     assertEquals(1, map.size)
   }
 }
@@ -202,22 +217,29 @@ class CachingPollingHandlerTest {
     val kv = MapKeyValueStore.open(map)
     val meterSdk = SdkMeterProvider.builder().build()
     val meter = meterSdk.get("handler")
-    val handler = CachingPollingHandler(listOf(JSONRPCRequest(1, "foo", arrayOf())), 1000, kv, meter.longCounterBuilder("foo").build(), meter.longCounterBuilder("bar").build()) {
+    val handler = CachingPollingHandler(
+      listOf(JSONRPCRequest(StringOrLong(1), "foo", arrayOf())),
+      1000,
+      kv,
+      meter.longCounterBuilder("foo").build(),
+      meter.longCounterBuilder("bar").build()
+    ) {
       if (it.params.isNotEmpty()) {
-        JSONRPCResponse(id = 1, error = JSONRPCError(1234, ""))
+        JSONRPCResponse(id = StringOrLong(1), error = JSONRPCError(1234, ""))
       } else {
-        JSONRPCResponse(id = 1)
+        JSONRPCResponse(id = StringOrLong(1))
       }
     }
     delay(500)
     assertEquals(1, map.size)
-    handler.handleRequest(JSONRPCRequest(id = 1, method = "foo", params = arrayOf()))
+    handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "foo", params = arrayOf()))
     assertEquals(1, map.size)
-    handler.handleRequest(JSONRPCRequest(id = 1, method = "bar", params = arrayOf()))
+    handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "bar", params = arrayOf()))
     assertEquals(1, map.size)
-    handler.handleRequest(JSONRPCRequest(id = 1, method = "foo", params = arrayOf()))
+    handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "foo", params = arrayOf()))
     assertEquals(1, map.size)
-    val errorResp = handler.handleRequest(JSONRPCRequest(id = 1, method = "foo", params = arrayOf("bleh")))
+    val errorResp =
+      handler.handleRequest(JSONRPCRequest(id = StringOrLong(1), method = "foo", params = arrayOf("bleh")))
     assertEquals(1, map.size)
     assertNotNull(errorResp.error)
   }
diff --git a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/NetTest.kt b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/NetTest.kt
index b9421ada..00d51a2a 100644
--- a/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/NetTest.kt
+++ b/jsonrpc/src/test/kotlin/org/apache/tuweni/jsonrpc/methods/NetTest.kt
@@ -18,6 +18,7 @@ package org.apache.tuweni.jsonrpc.methods
 
 import kotlinx.coroutines.runBlocking
 import org.apache.tuweni.eth.JSONRPCRequest
+import org.apache.tuweni.eth.StringOrLong
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Test
 
@@ -26,8 +27,8 @@ class NetTest {
   @Test
   fun testNetCheck() = runBlocking {
     val net = registerNet("2", true, { 2 })
-    assertEquals("2", net["net_version"]?.invoke(JSONRPCRequest(1, "", arrayOf()))?.result)
-    assertEquals(true, net["net_listening"]?.invoke(JSONRPCRequest(1, "", arrayOf()))?.result)
-    assertEquals("0x2", net["net_peerCount"]?.invoke(JSONRPCRequest(1, "", arrayOf()))?.result)
+    assertEquals("2", net["net_version"]?.invoke(JSONRPCRequest(StringOrLong(1), "", arrayOf()))?.result)
+    assertEquals(true, net["net_listening"]?.invoke(JSONRPCRequest(StringOrLong(1), "", arrayOf()))?.result)
+    assertEquals("0x2", net["net_peerCount"]?.invoke(JSONRPCRequest(StringOrLong(1), "", arrayOf()))?.result)
   }
 }
diff --git a/stratum/proxy/src/main/kotlin/org/apache/tuweni/stratum/StratumServerApp.kt b/stratum/proxy/src/main/kotlin/org/apache/tuweni/stratum/StratumServerApp.kt
index 8a548e5b..1d15fc3a 100644
--- a/stratum/proxy/src/main/kotlin/org/apache/tuweni/stratum/StratumServerApp.kt
+++ b/stratum/proxy/src/main/kotlin/org/apache/tuweni/stratum/StratumServerApp.kt
@@ -24,12 +24,13 @@ import kotlinx.coroutines.withContext
 import org.apache.tuweni.bytes.Bytes
 import org.apache.tuweni.bytes.Bytes32
 import org.apache.tuweni.eth.JSONRPCRequest
+import org.apache.tuweni.eth.StringOrLong
 import org.apache.tuweni.jsonrpc.JSONRPCClient
 import org.apache.tuweni.stratum.server.PoWInput
 import org.apache.tuweni.stratum.server.StratumServer
 import org.apache.tuweni.units.bigints.UInt256
 import org.slf4j.LoggerFactory
-import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
 import java.util.concurrent.atomic.AtomicReference
 import kotlin.system.exitProcess
 
@@ -44,7 +45,7 @@ fun main(args: Array<String>) {
   val client = JSONRPCClient(vertx, "http://localhost:8545", "")
 
   val port = args[0].toInt()
-  val idCounter = AtomicInteger(0)
+  val idCounter = AtomicLong(0)
   val seedReference = AtomicReference<Bytes32>()
   val server = StratumServer(
     vertx, port = port, networkInterface = "0.0.0.0",
@@ -52,7 +53,7 @@ fun main(args: Array<String>) {
       logger.info("Got solution $solution")
       withContext(client.coroutineContext) {
         val req = JSONRPCRequest(
-          id = idCounter.incrementAndGet(),
+          id = StringOrLong(idCounter.incrementAndGet()),
           method = "eth_submitWork",
           params = arrayOf(
             Bytes.ofUnsignedLong(solution.nonce).toHexString(),
@@ -79,7 +80,7 @@ fun main(args: Array<String>) {
         try {
           val response = client.sendRequest(
             JSONRPCRequest(
-              id = idCounter.incrementAndGet(),
+              id = StringOrLong(idCounter.incrementAndGet()),
               method = "eth_getWork",
               params = arrayOf()
             )


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tuweni.apache.org
For additional commands, e-mail: commits-help@tuweni.apache.org