Skip to content

Commit

Permalink
[ETCM-139] Fix json rpc error serialization, json4s - thank you for b…
Browse files Browse the repository at this point in the history
…eing so unhelpful there
  • Loading branch information
kapke committed Oct 28, 2020
1 parent d1acb6f commit b3165d2
Show file tree
Hide file tree
Showing 20 changed files with 215 additions and 170 deletions.
10 changes: 7 additions & 3 deletions build.sbt
Expand Up @@ -68,8 +68,12 @@ val root = {
libraryDependencies ++= dep
)
.settings(executableScriptName := name.value)
.settings(inConfig(Integration)(Defaults.testSettings
++ org.scalafmt.sbt.ScalafmtPlugin.scalafmtConfigSettings :+ (Test / parallelExecution := false)): _*)
.settings(
inConfig(Integration)(
Defaults.testSettings
++ org.scalafmt.sbt.ScalafmtPlugin.scalafmtConfigSettings :+ (Test / parallelExecution := false)
): _*
)
.settings(inConfig(Benchmark)(Defaults.testSettings :+ (Test / parallelExecution := false)): _*)
.settings(inConfig(Evm)(Defaults.testSettings :+ (Test / parallelExecution := false)): _*)
.settings(inConfig(Ets)(Defaults.testSettings :+ (Test / parallelExecution := false)): _*)
Expand Down Expand Up @@ -158,8 +162,8 @@ addCommandAlias(
addCommandAlias(
"pp",
""";compile-all
|;test
|;scalastyle
|;test:scalastyle
|;test
|""".stripMargin
)
Expand Up @@ -3,16 +3,16 @@ package io.iohk.ethereum.jsonrpc
import akka.util.ByteString
import io.iohk.ethereum.crypto.ECDSASignature
import io.iohk.ethereum.jsonrpc.CheckpointingService._
import io.iohk.ethereum.jsonrpc.JsonRpcController.Codec
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
import io.iohk.ethereum.jsonrpc.JsonSerializers.QuantitiesSerializer
import io.iohk.ethereum.jsonrpc.serialization.JsonMethodCodec
import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers.QuantitiesSerializer
import org.json4s.JsonAST._
import org.json4s.{Extraction, JsonAST}

object CheckpointingJsonMethodsImplicits extends JsonMethodsImplicits {

implicit val checkpointing_getLatestBlock: Codec[GetLatestBlockRequest, GetLatestBlockResponse] =
new Codec[GetLatestBlockRequest, GetLatestBlockResponse] {
implicit val checkpointing_getLatestBlock: JsonMethodCodec[GetLatestBlockRequest, GetLatestBlockResponse] =
new JsonMethodCodec[GetLatestBlockRequest, GetLatestBlockResponse] {
override def decodeJson(
params: Option[JsonAST.JArray]
): Either[JsonRpcError, GetLatestBlockRequest] =
Expand All @@ -30,8 +30,8 @@ object CheckpointingJsonMethodsImplicits extends JsonMethodsImplicits {
Extraction.decompose(resp)(formats - QuantitiesSerializer)
}

implicit val checkpointing_pushCheckpoint: Codec[PushCheckpointRequest, PushCheckpointResponse] =
new Codec[PushCheckpointRequest, PushCheckpointResponse] {
implicit val checkpointing_pushCheckpoint: JsonMethodCodec[PushCheckpointRequest, PushCheckpointResponse] =
new JsonMethodCodec[PushCheckpointRequest, PushCheckpointResponse] {
override def decodeJson(
params: Option[JsonAST.JArray]
): Either[JsonRpcError, PushCheckpointRequest] =
Expand Down
@@ -1,14 +1,14 @@
package io.iohk.ethereum.jsonrpc

import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse}
import io.iohk.ethereum.jsonrpc.JsonRpcController.{Codec, JsonEncoder}
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonDecoder.NoParamsDecoder
import io.iohk.ethereum.jsonrpc.serialization.JsonMethodDecoder.NoParamsMethodDecoder
import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec}
import org.json4s.JsonAST.{JArray, JString, JValue}

object DebugJsonMethodsImplicits extends JsonMethodsImplicits {

implicit val debug_listPeersInfo: Codec[ListPeersInfoRequest, ListPeersInfoResponse] =
new NoParamsDecoder(ListPeersInfoRequest()) with JsonEncoder[ListPeersInfoResponse] {
implicit val debug_listPeersInfo: JsonMethodCodec[ListPeersInfoRequest, ListPeersInfoResponse] =
new NoParamsMethodDecoder(ListPeersInfoRequest()) with JsonEncoder[ListPeersInfoResponse] {
def encodeJson(t: ListPeersInfoResponse): JValue =
JArray(t.peers.map(a => JString(a.toString)))
}
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -2,9 +2,9 @@ package io.iohk.ethereum.jsonrpc

import akka.util.ByteString
import io.iohk.ethereum.jsonrpc.EthService._
import io.iohk.ethereum.jsonrpc.JsonRpcController.{JsonDecoder, JsonEncoder}
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
import io.iohk.ethereum.jsonrpc.PersonalService.{InvalidAddress, SendIeleTransactionRequest}
import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodDecoder}
import org.json4s.JsonAST.{JArray, JObject, JString, JValue}

object IeleJsonMethodsImplicits extends JsonMethodsImplicits {
Expand Down Expand Up @@ -34,7 +34,7 @@ object IeleJsonMethodsImplicits extends JsonMethodsImplicits {
)
}

implicit val iele_call = new JsonDecoder[IeleCallRequest] with JsonEncoder[IeleCallResponse] {
implicit val iele_call = new JsonMethodDecoder[IeleCallRequest] with JsonEncoder[IeleCallResponse] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, IeleCallRequest] =
params match {
case Some(JArray((txObj: JObject) :: (blockValue: JValue) :: Nil)) =>
Expand Down Expand Up @@ -93,7 +93,7 @@ object IeleJsonMethodsImplicits extends JsonMethodsImplicits {
} yield IeleTransactionRequest(from, to, value, gas, gasPrice, nonce, function, arguments, contractCode)
}

implicit val iele_sendTransaction = new JsonDecoder[SendIeleTransactionRequest] {
implicit val iele_sendTransaction = new JsonMethodDecoder[SendIeleTransactionRequest] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, SendIeleTransactionRequest] =
params match {
case Some(JArray(JObject(tx) :: _)) =>
Expand Down
44 changes: 18 additions & 26 deletions src/main/scala/io/iohk/ethereum/jsonrpc/JsonMethodsImplicits.scala
Expand Up @@ -6,30 +6,21 @@ import akka.util.ByteString
import io.iohk.ethereum.crypto.ECDSASignature
import io.iohk.ethereum.domain.Address
import io.iohk.ethereum.jsonrpc.EthService.BlockParam
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonDecoder.NoParamsDecoder
import io.iohk.ethereum.jsonrpc.JsonRpcController.{Codec, JsonDecoder, JsonEncoder}
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
import io.iohk.ethereum.jsonrpc.JsonSerializers.{
AddressJsonSerializer,
OptionNoneToJNullSerializer,
QuantitiesSerializer,
UnformattedDataJsonSerializer
}
import io.iohk.ethereum.jsonrpc.NetService._
import io.iohk.ethereum.jsonrpc.PersonalService._
import io.iohk.ethereum.jsonrpc.Web3Service.{ClientVersionRequest, ClientVersionResponse, Sha3Request, Sha3Response}
import io.iohk.ethereum.jsonrpc.serialization.JsonMethodDecoder.NoParamsMethodDecoder
import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodCodec, JsonMethodDecoder, JsonSerializers}
import io.iohk.ethereum.utils.BigIntExtensionMethods.BigIntAsUnsigned
import org.bouncycastle.util.encoders.Hex
import org.json4s.JsonAST._
import org.json4s.JsonDSL._
import org.json4s.{DefaultFormats, Formats}
import org.bouncycastle.util.encoders.Hex

import scala.util.Try

trait JsonMethodsImplicits {

implicit val formats: Formats = DefaultFormats.preservingEmptyValues + OptionNoneToJNullSerializer +
QuantitiesSerializer + UnformattedDataJsonSerializer + AddressJsonSerializer
implicit val formats = JsonSerializers.formats

protected def encodeAsHex(input: ByteString): JString =
JString(s"0x${Hex.toHexString(input.toArray[Byte])}")
Expand Down Expand Up @@ -154,7 +145,7 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {

import JsonRpcError._

implicit val web3_sha3 = new JsonDecoder[Sha3Request] with JsonEncoder[Sha3Response] {
implicit val web3_sha3 = new JsonMethodDecoder[Sha3Request] with JsonEncoder[Sha3Response] {
override def decodeJson(params: Option[JArray]): Either[JsonRpcError, Sha3Request] =
params match {
case Some(JArray((input: JString) :: Nil)) => extractBytes(input).map(Sha3Request)
Expand All @@ -164,24 +155,25 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {
override def encodeJson(t: Sha3Response): JValue = encodeAsHex(t.data)
}

implicit val web3_clientVersion = new NoParamsDecoder(ClientVersionRequest())
implicit val web3_clientVersion = new NoParamsMethodDecoder(ClientVersionRequest())
with JsonEncoder[ClientVersionResponse] {
override def encodeJson(t: ClientVersionResponse): JValue = t.value
}

implicit val net_version = new NoParamsDecoder(VersionRequest()) with JsonEncoder[VersionResponse] {
implicit val net_version = new NoParamsMethodDecoder(VersionRequest()) with JsonEncoder[VersionResponse] {
override def encodeJson(t: VersionResponse): JValue = t.value
}

implicit val net_listening = new NoParamsDecoder(ListeningRequest()) with JsonEncoder[ListeningResponse] {
implicit val net_listening = new NoParamsMethodDecoder(ListeningRequest()) with JsonEncoder[ListeningResponse] {
override def encodeJson(t: ListeningResponse): JValue = t.value
}

implicit val net_peerCount = new NoParamsDecoder(PeerCountRequest()) with JsonEncoder[PeerCountResponse] {
implicit val net_peerCount = new NoParamsMethodDecoder(PeerCountRequest()) with JsonEncoder[PeerCountResponse] {
override def encodeJson(t: PeerCountResponse): JValue = encodeAsHex(t.value)
}

implicit val personal_importRawKey = new JsonDecoder[ImportRawKeyRequest] with JsonEncoder[ImportRawKeyResponse] {
implicit val personal_importRawKey = new JsonMethodDecoder[ImportRawKeyRequest]
with JsonEncoder[ImportRawKeyResponse] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, ImportRawKeyRequest] =
params match {
case Some(JArray(JString(key) :: JString(passphrase) :: _)) =>
Expand All @@ -194,7 +186,7 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {
JString(t.address.toString)
}

implicit val personal_newAccount = new JsonDecoder[NewAccountRequest] with JsonEncoder[NewAccountResponse] {
implicit val personal_newAccount = new JsonMethodDecoder[NewAccountRequest] with JsonEncoder[NewAccountResponse] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, NewAccountRequest] =
params match {
case Some(JArray(JString(passphrase) :: _)) =>
Expand All @@ -207,14 +199,14 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {
JString(t.address.toString)
}

implicit val personal_listAccounts = new NoParamsDecoder(ListAccountsRequest())
implicit val personal_listAccounts = new NoParamsMethodDecoder(ListAccountsRequest())
with JsonEncoder[ListAccountsResponse] {
def encodeJson(t: ListAccountsResponse): JValue =
JArray(t.addresses.map(a => JString(a.toString)))
}

implicit val personal_sendTransaction =
new Codec[SendTransactionWithPassphraseRequest, SendTransactionWithPassphraseResponse] {
new JsonMethodCodec[SendTransactionWithPassphraseRequest, SendTransactionWithPassphraseResponse] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, SendTransactionWithPassphraseRequest] =
params match {
case Some(JArray(JObject(tx) :: JString(passphrase) :: _)) =>
Expand All @@ -227,7 +219,7 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {
encodeAsHex(t.txHash)
}

implicit val personal_sign = new Codec[SignRequest, SignResponse] {
implicit val personal_sign = new JsonMethodCodec[SignRequest, SignResponse] {
override def encodeJson(t: SignResponse): JValue = {
import t.signature._
encodeAsHex(ByteString(r.toUnsignedByteArray ++ s.toUnsignedByteArray :+ v))
Expand All @@ -245,7 +237,7 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {
}
}

implicit val personal_ecRecover = new Codec[EcRecoverRequest, EcRecoverResponse] {
implicit val personal_ecRecover = new JsonMethodCodec[EcRecoverRequest, EcRecoverResponse] {

def decodeJson(params: Option[JArray]): Either[JsonRpcError, EcRecoverRequest] =
params match {
Expand Down Expand Up @@ -274,7 +266,7 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {
encodeAsHex(t.address.bytes)
}

implicit val personal_unlockAccount = new Codec[UnlockAccountRequest, UnlockAccountResponse] {
implicit val personal_unlockAccount = new JsonMethodCodec[UnlockAccountRequest, UnlockAccountResponse] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, UnlockAccountRequest] = {
params match {
case Some(JArray(JString(addr) :: JString(passphrase) :: JNull :: _)) =>
Expand All @@ -295,7 +287,7 @@ object JsonMethodsImplicits extends JsonMethodsImplicits {
JBool(t.result)
}

implicit val personal_lockAccount = new Codec[LockAccountRequest, LockAccountResponse] {
implicit val personal_lockAccount = new JsonMethodCodec[LockAccountRequest, LockAccountResponse] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, LockAccountRequest] = {
params match {
case Some(JArray(JString(addr) :: _)) =>
Expand Down
67 changes: 15 additions & 52 deletions src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcController.scala
@@ -1,71 +1,34 @@
package io.iohk.ethereum.jsonrpc

import java.util.concurrent.TimeUnit

import com.typesafe.config.{Config => TypesafeConfig}
import io.iohk.ethereum.jsonrpc.CheckpointingService._
import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse}
import io.iohk.ethereum.jsonrpc.EthService._
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonRpcConfig
import io.iohk.ethereum.jsonrpc.NetService._
import io.iohk.ethereum.jsonrpc.PersonalService._
import io.iohk.ethereum.jsonrpc.Web3Service._
import io.iohk.ethereum.utils.Logger
import org.json4s.JsonAST.{JArray, JValue}
import org.json4s.JsonDSL._
import com.typesafe.config.{Config => TypesafeConfig}
import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse}
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
import io.iohk.ethereum.jsonrpc.QAService.{
GenerateCheckpointRequest,
GenerateCheckpointResponse,
GetFederationMembersInfoRequest,
GetFederationMembersInfoResponse
}
import io.iohk.ethereum.jsonrpc.TestService._
import io.iohk.ethereum.jsonrpc.Web3Service._
import io.iohk.ethereum.jsonrpc.serialization.{JsonEncoder, JsonMethodDecoder}
import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpServer.JsonRpcHttpServerConfig
import io.iohk.ethereum.jsonrpc.server.ipc.JsonRpcIpcServer.JsonRpcIpcServerConfig
import java.util.concurrent.TimeUnit

import io.iohk.ethereum.jsonrpc.CheckpointingService._
import io.iohk.ethereum.utils.Logger
import org.json4s.JsonDSL._

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success}

object JsonRpcController {

trait JsonDecoder[T] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, T]
}
object JsonDecoder {
abstract class NoParamsDecoder[T](request: => T) extends JsonDecoder[T] {
def decodeJson(params: Option[JArray]): Either[JsonRpcError, T] =
params match {
case None | Some(JArray(Nil)) => Right(request)
case _ => Left(InvalidParams(s"No parameters expected"))
}
}
}

trait JsonEncoder[T] {
def encodeJson(t: T): JValue
}
object JsonEncoder {
def apply[T](implicit encoder: JsonEncoder[T]): JsonEncoder[T] = encoder

implicit def listEncoder[T](implicit itemEncoder: JsonEncoder[T]): JsonEncoder[List[T]] = list =>
JArray(list.map(itemEncoder.encodeJson))
}

trait Codec[Req, Res] extends JsonDecoder[Req] with JsonEncoder[Res]
object Codec {
import scala.language.implicitConversions

implicit def decoderWithEncoderIntoCodec[Req, Res](
decEnc: JsonDecoder[Req] with JsonEncoder[Res]
): Codec[Req, Res] = new Codec[Req, Res] {
def decodeJson(params: Option[JArray]) = decEnc.decodeJson(params)
def encodeJson(t: Res) = decEnc.encodeJson(t)
}
}

trait JsonRpcConfig {
def apis: Seq[String]
def accountTransactionsMaxBlocks: Int
Expand Down Expand Up @@ -127,15 +90,15 @@ class JsonRpcController(
config: JsonRpcConfig
) extends Logger {

import JsonRpcController._
import CheckpointingJsonMethodsImplicits._
import DebugJsonMethodsImplicits._
import EthJsonMethodsImplicits._
import TestJsonMethodsImplicits._
import IeleJsonMethodsImplicits._
import JsonMethodsImplicits._
import JsonRpcController._
import JsonRpcError._
import DebugJsonMethodsImplicits._
import QAJsonMethodsImplicits._
import CheckpointingJsonMethodsImplicits._
import TestJsonMethodsImplicits._

lazy val apisHandleFns: Map[String, PartialFunction[JsonRpcRequest, Future[JsonRpcResponse]]] = Map(
Apis.Eth -> handleEthRequest,
Expand Down Expand Up @@ -415,7 +378,7 @@ class JsonRpcController(
private def handle[Req, Res](
fn: Req => Future[Either[JsonRpcError, Res]],
rpcReq: JsonRpcRequest
)(implicit dec: JsonDecoder[Req], enc: JsonEncoder[Res]): Future[JsonRpcResponse] = {
)(implicit dec: JsonMethodDecoder[Req], enc: JsonEncoder[Res]): Future[JsonRpcResponse] = {
dec.decodeJson(rpcReq.params) match {
case Right(req) =>
fn(req)
Expand Down
10 changes: 8 additions & 2 deletions src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcError.scala
@@ -1,8 +1,8 @@
package io.iohk.ethereum.jsonrpc

import io.iohk.ethereum.consensus.Protocol
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonEncoder
import org.json4s.{JInt, JString, JObject, JValue}
import io.iohk.ethereum.jsonrpc.serialization.JsonEncoder
import org.json4s.{JInt, JObject, JString, JValue}

case class JsonRpcError(code: Int, message: String, data: Option[JValue])

Expand All @@ -12,6 +12,12 @@ object JsonRpcError {
def apply[T: JsonEncoder](code: Int, message: String, data: T): JsonRpcError =
JsonRpcError(code, message, Some(JsonEncoder[T].encodeJson(data)))

implicit val jsonRpcErrorEncoder: JsonEncoder[JsonRpcError] = err =>
JObject(
List("code" -> JsonEncoder.encode(err.code), "message" -> JsonEncoder.encode(err.message)) ++
err.data.map("data" -> _)
)

val ParseError = JsonRpcError(-32700, "An error occurred on the server while parsing the JSON text", None)
val InvalidRequest = JsonRpcError(-32600, "The JSON sent is not a valid Request object", None)
val MethodNotFound = JsonRpcError(-32601, "The method does not exist / is not available", None)
Expand Down

0 comments on commit b3165d2

Please sign in to comment.