Skip to content

Commit aad5850

Browse files
committed
[ETCM-139] Report precise information on rpc when account data is missing
1 parent ae398e1 commit aad5850

19 files changed

+123
-69
lines changed

src/main/scala/io/iohk/ethereum/jsonrpc/CheckpointingJsonMethodsImplicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import akka.util.ByteString
44
import io.iohk.ethereum.crypto.ECDSASignature
55
import io.iohk.ethereum.jsonrpc.CheckpointingService._
66
import io.iohk.ethereum.jsonrpc.JsonRpcController.Codec
7-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
7+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
88
import io.iohk.ethereum.jsonrpc.JsonSerializers.QuantitiesSerializer
99
import org.json4s.JsonAST._
1010
import org.json4s.{Extraction, JsonAST}

src/main/scala/io/iohk/ethereum/jsonrpc/EthJsonMethodsImplicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import akka.util.ByteString
44
import io.iohk.ethereum.jsonrpc.EthService._
55
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonDecoder.NoParamsDecoder
66
import io.iohk.ethereum.jsonrpc.JsonRpcController.{Codec, JsonDecoder, JsonEncoder}
7-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
7+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
88
import io.iohk.ethereum.jsonrpc.PersonalService.{SendTransactionRequest, SendTransactionResponse, SignRequest}
99
import org.json4s.{Extraction, JsonAST}
1010
import org.json4s.JsonAST.{JArray, JBool, JString, JValue, _}

src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.iohk.ethereum.jsonrpc.FilterManager.{FilterChanges, FilterLogs, LogFil
1717
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonRpcConfig
1818
import io.iohk.ethereum.keystore.KeyStore
1919
import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, Ledger, StxLedger}
20+
import io.iohk.ethereum.mpt.MerklePatriciaTrie.MissingNodeException
2021
import io.iohk.ethereum.ommers.OmmersPool
2122
import io.iohk.ethereum.rlp
2223
import io.iohk.ethereum.rlp.RLPImplicitConversions._
@@ -226,7 +227,7 @@ class EthService(
226227

227228
private[this] def ifEthash[Req, Res](req: Req)(f: Req => Res): ServiceResponse[Res] = {
228229
@inline def F[A](x: A): Future[A] = Future.successful(x)
229-
consensus.ifEthash[ServiceResponse[Res]](_ => F(Right(f(req))))(F(Left(JsonRpcErrors.ConsensusIsNotEthash)))
230+
consensus.ifEthash[ServiceResponse[Res]](_ => F(Right(f(req))))(F(Left(JsonRpcError.ConsensusIsNotEthash)))
230231
}
231232

232233
def protocolVersion(req: ProtocolVersionRequest): ServiceResponse[ProtocolVersionResponse] =
@@ -557,7 +558,7 @@ class EthService(
557558
)
558559
}
559560
response
560-
})(Future.successful(Left(JsonRpcErrors.ConsensusIsNotEthash)))
561+
})(Future.successful(Left(JsonRpcError.ConsensusIsNotEthash)))
561562

562563
private def getOmmersFromPool(parentBlockHash: ByteString): Future[OmmersPool.Ommers] =
563564
consensus.ifEthash(ethash => {
@@ -602,7 +603,7 @@ class EthService(
602603
Right(SubmitWorkResponse(false))
603604
}
604605
}
605-
})(Future.successful(Left(JsonRpcErrors.ConsensusIsNotEthash)))
606+
})(Future.successful(Left(JsonRpcError.ConsensusIsNotEthash)))
606607

607608
/**
608609
* Implements the eth_syncing method that returns syncing information if the node is syncing.
@@ -637,10 +638,10 @@ class EthService(
637638
pendingTransactionsManager ! PendingTransactionsManager.AddOrOverrideTransaction(signedTransaction)
638639
Future.successful(Right(SendRawTransactionResponse(signedTransaction.hash)))
639640
} else {
640-
Future.successful(Left(JsonRpcErrors.InvalidRequest))
641+
Future.successful(Left(JsonRpcError.InvalidRequest))
641642
}
642643
case Failure(_) =>
643-
Future.successful(Left(JsonRpcErrors.InvalidRequest))
644+
Future.successful(Left(JsonRpcError.InvalidRequest))
644645
}
645646
}
646647

@@ -657,7 +658,7 @@ class EthService(
657658
val dataEither = (tx.function, tx.contractCode) match {
658659
case (Some(function), None) => Right(rlp.encode(RLPList(function, args)))
659660
case (None, Some(contractCode)) => Right(rlp.encode(RLPList(contractCode, args)))
660-
case _ => Left(JsonRpcErrors.InvalidParams("Iele transaction should contain either functionName or contractCode"))
661+
case _ => Left(JsonRpcError.InvalidParams("Iele transaction should contain either functionName or contractCode"))
661662
}
662663

663664
dataEither match {
@@ -710,7 +711,7 @@ class EthService(
710711
Right(GetUncleCountByBlockHashResponse(blockBody.uncleNodesList.size))
711712
case None =>
712713
Left(
713-
JsonRpcErrors.InvalidParams(s"Block with hash ${Hex.toHexString(req.blockHash.toArray[Byte])} not found")
714+
JsonRpcError.InvalidParams(s"Block with hash ${Hex.toHexString(req.blockHash.toArray[Byte])} not found")
714715
)
715716
}
716717
}
@@ -774,31 +775,22 @@ class EthService(
774775
.flatMap(_ => Right(None))
775776
}
776777

777-
def getBalance(req: GetBalanceRequest): ServiceResponse[GetBalanceResponse] = {
778-
Future {
779-
withAccount(req.address, req.block) { account =>
780-
GetBalanceResponse(account.balance)
781-
}
778+
def getBalance(req: GetBalanceRequest): ServiceResponse[GetBalanceResponse] =
779+
withAccount(req.address, req.block) { account =>
780+
GetBalanceResponse(account.balance)
782781
}
783-
}
784782

785-
def getStorageAt(req: GetStorageAtRequest): ServiceResponse[GetStorageAtResponse] = {
786-
Future {
787-
withAccount(req.address, req.block) { account =>
788-
GetStorageAtResponse(
789-
blockchain.getAccountStorageAt(account.storageRoot, req.position, blockchainConfig.ethCompatibleStorage)
790-
)
791-
}
783+
def getStorageAt(req: GetStorageAtRequest): ServiceResponse[GetStorageAtResponse] =
784+
withAccount(req.address, req.block) { account =>
785+
GetStorageAtResponse(
786+
blockchain.getAccountStorageAt(account.storageRoot, req.position, blockchainConfig.ethCompatibleStorage)
787+
)
792788
}
793-
}
794789

795-
def getTransactionCount(req: GetTransactionCountRequest): ServiceResponse[GetTransactionCountResponse] = {
796-
Future {
797-
withAccount(req.address, req.block) { account =>
798-
GetTransactionCountResponse(account.nonce)
799-
}
790+
def getTransactionCount(req: GetTransactionCountRequest): ServiceResponse[GetTransactionCountResponse] =
791+
withAccount(req.address, req.block) { account =>
792+
GetTransactionCountResponse(account.nonce)
800793
}
801-
}
802794

803795
def newFilter(req: NewFilterRequest): ServiceResponse[NewFilterResponse] = {
804796
implicit val timeout: Timeout = Timeout(filterConfig.filterManagerQueryTimeout)
@@ -863,20 +855,25 @@ class EthService(
863855
}
864856
}
865857

866-
private def withAccount[T](address: Address, blockParam: BlockParam)(f: Account => T): Either[JsonRpcError, T] = {
867-
resolveBlock(blockParam).map { case ResolvedBlock(block, _) =>
868-
f(
869-
blockchain.getAccount(address, block.header.number).getOrElse(Account.empty(blockchainConfig.accountStartNonce))
870-
)
858+
private def withAccount[T](address: Address, blockParam: BlockParam)(makeResponse: Account => T): ServiceResponse[T] =
859+
Future {
860+
resolveBlock(blockParam)
861+
.map { case ResolvedBlock(block, _) =>
862+
blockchain
863+
.getAccount(address, block.header.number)
864+
.getOrElse(Account.empty(blockchainConfig.accountStartNonce))
865+
}
866+
.map(makeResponse)
867+
}.recover { case _: MissingNodeException =>
868+
Left(JsonRpcError.NodeNotFound)
871869
}
872-
}
873870

874871
private def resolveBlock(blockParam: BlockParam): Either[JsonRpcError, ResolvedBlock] = {
875872
def getBlock(number: BigInt): Either[JsonRpcError, Block] = {
876873
blockchain
877874
.getBlockByNumber(number)
878875
.map(Right.apply)
879-
.getOrElse(Left(JsonRpcErrors.InvalidParams(s"Block $number not found")))
876+
.getOrElse(Left(JsonRpcError.InvalidParams(s"Block $number not found")))
880877
}
881878

882879
blockParam match {
@@ -929,7 +926,7 @@ class EthService(
929926
if (numBlocksToSearch > jsonRpcConfig.accountTransactionsMaxBlocks) {
930927
Future.successful(
931928
Left(
932-
JsonRpcErrors.InvalidParams(
929+
JsonRpcError.InvalidParams(
933930
s"""Maximum number of blocks to search is ${jsonRpcConfig.accountTransactionsMaxBlocks}, requested: $numBlocksToSearch.
934931
|See: 'network.rpc.account-transactions-max-blocks' config.""".stripMargin
935932
)
@@ -963,13 +960,10 @@ class EthService(
963960
}
964961
}
965962

966-
def getStorageRoot(req: GetStorageRootRequest): ServiceResponse[GetStorageRootResponse] = {
967-
Future {
968-
withAccount(req.address, req.block) { account =>
969-
GetStorageRootResponse(account.storageRoot)
970-
}
963+
def getStorageRoot(req: GetStorageRootRequest): ServiceResponse[GetStorageRootResponse] =
964+
withAccount(req.address, req.block) { account =>
965+
GetStorageRootResponse(account.storageRoot)
971966
}
972-
}
973967

974968
/**
975969
* Returns the transactions that are pending in the transaction pool and have a from address that is one of the accounts this node manages.

src/main/scala/io/iohk/ethereum/jsonrpc/IeleJsonMethodsImplicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.iohk.ethereum.jsonrpc
33
import akka.util.ByteString
44
import io.iohk.ethereum.jsonrpc.EthService._
55
import io.iohk.ethereum.jsonrpc.JsonRpcController.{JsonDecoder, JsonEncoder}
6-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
6+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
77
import io.iohk.ethereum.jsonrpc.PersonalService.{InvalidAddress, SendIeleTransactionRequest}
88
import org.json4s.JsonAST.{JArray, JObject, JString, JValue}
99

src/main/scala/io/iohk/ethereum/jsonrpc/JsonMethodsImplicits.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import io.iohk.ethereum.domain.Address
88
import io.iohk.ethereum.jsonrpc.EthService.BlockParam
99
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonDecoder.NoParamsDecoder
1010
import io.iohk.ethereum.jsonrpc.JsonRpcController.{Codec, JsonDecoder, JsonEncoder}
11-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
11+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
1212
import io.iohk.ethereum.jsonrpc.JsonSerializers.{
1313
AddressJsonSerializer,
1414
OptionNoneToJNullSerializer,
@@ -141,7 +141,7 @@ trait JsonMethodsImplicits {
141141
extractQuantity(other)
142142
.map(BlockParam.WithNumber)
143143
.left
144-
.map(_ => JsonRpcErrors.InvalidParams(s"Invalid default block param: $other"))
144+
.map(_ => JsonRpcError.InvalidParams(s"Invalid default block param: $other"))
145145
}
146146
}
147147

@@ -152,7 +152,7 @@ trait JsonMethodsImplicits {
152152

153153
object JsonMethodsImplicits extends JsonMethodsImplicits {
154154

155-
import JsonRpcErrors._
155+
import JsonRpcError._
156156

157157
implicit val web3_sha3 = new JsonDecoder[Sha3Request] with JsonEncoder[Sha3Response] {
158158
override def decodeJson(params: Option[JArray]): Either[JsonRpcError, Sha3Request] =

src/main/scala/io/iohk/ethereum/jsonrpc/JsonRpcController.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.json4s.JsonAST.{JArray, JValue}
1010
import org.json4s.JsonDSL._
1111
import com.typesafe.config.{Config => TypesafeConfig}
1212
import io.iohk.ethereum.jsonrpc.DebugService.{ListPeersInfoRequest, ListPeersInfoResponse}
13-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
13+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
1414
import io.iohk.ethereum.jsonrpc.QAService.{
1515
GenerateCheckpointRequest,
1616
GenerateCheckpointResponse,
@@ -47,6 +47,12 @@ object JsonRpcController {
4747
trait JsonEncoder[T] {
4848
def encodeJson(t: T): JValue
4949
}
50+
object JsonEncoder {
51+
def apply[T](implicit encoder: JsonEncoder[T]): JsonEncoder[T] = encoder
52+
53+
implicit def listEncoder[T](implicit itemEncoder: JsonEncoder[T]): JsonEncoder[List[T]] = list =>
54+
JArray(list.map(itemEncoder.encodeJson))
55+
}
5056

5157
trait Codec[Req, Res] extends JsonDecoder[Req] with JsonEncoder[Res]
5258
object Codec {
@@ -126,7 +132,7 @@ class JsonRpcController(
126132
import TestJsonMethodsImplicits._
127133
import IeleJsonMethodsImplicits._
128134
import JsonMethodsImplicits._
129-
import JsonRpcErrors._
135+
import JsonRpcError._
130136
import DebugJsonMethodsImplicits._
131137
import QAJsonMethodsImplicits._
132138
import CheckpointingJsonMethodsImplicits._
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package io.iohk.ethereum.jsonrpc
22

33
import io.iohk.ethereum.consensus.Protocol
4-
import org.json4s.JsonAST.JValue
4+
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonEncoder
5+
import org.json4s.{JInt, JString, JObject, JValue}
56

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

89
// scalastyle:off magic.number
910
// scalastyle:off public.methods.have.type
10-
object JsonRpcErrors {
11+
object JsonRpcError {
12+
def apply[T: JsonEncoder](code: Int, message: String, data: T): JsonRpcError =
13+
JsonRpcError(code, message, Some(JsonEncoder[T].encodeJson(data)))
14+
1115
val ParseError = JsonRpcError(-32700, "An error occurred on the server while parsing the JSON text", None)
1216
val InvalidRequest = JsonRpcError(-32600, "The JSON sent is not a valid Request object", None)
1317
val MethodNotFound = JsonRpcError(-32601, "The method does not exist / is not available", None)
@@ -16,9 +20,26 @@ object JsonRpcErrors {
1620
def LogicError(msg: String) = JsonRpcError(-32000, msg, None)
1721
val AccountLocked = LogicError("account is locked or unknown")
1822

19-
// We use the recommendation from https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
23+
// We use the recommendation from https://eth.wiki/json-rpc/json-rpc-error-codes-improvement-proposal
2024
//
2125
// Note Error Code "2", "Action not allowed" could be a candidate here, but the description they provide
2226
// probably does not match this use-case.
2327
final val ConsensusIsNotEthash = JsonRpcError(200, s"The consensus algorithm is not ${Protocol.Names.Ethash}", None)
28+
29+
def executionError(reasons: List[EthCustomError]): JsonRpcError = JsonRpcError(3, "Execution error", reasons)
30+
val NodeNotFound = executionError(List(EthCustomError.DoesntExist("State node")))
31+
32+
// Custom errors based on proposal https://eth.wiki/json-rpc/json-rpc-error-codes-improvement-proposal
33+
sealed abstract class EthCustomError private (val code: Int, val message: String)
34+
object EthCustomError {
35+
implicit val ethCustomErrorEncoder: JsonEncoder[EthCustomError] = err =>
36+
JObject("code" -> JInt(err.code), "message" -> JString(err.message))
37+
38+
case class DoesntExist(what: String) extends EthCustomError(100, s"${what} doesn't exist")
39+
case object RequiresEther extends EthCustomError(101, "Requires ether")
40+
case object GasTooLow extends EthCustomError(102, "Gas too low")
41+
case object GasLimitExceeded extends EthCustomError(103, "Gas limit exceeded")
42+
case object Rejected extends EthCustomError(104, "Rejected")
43+
case object EtherTooLow extends EthCustomError(105, "Ether too low")
44+
}
2445
}

src/main/scala/io/iohk/ethereum/jsonrpc/PersonalService.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import io.iohk.ethereum.db.storage.AppStateStorage
1111
import io.iohk.ethereum.domain.{Account, Address, Blockchain}
1212
import io.iohk.ethereum.jsonrpc.PersonalService._
1313
import io.iohk.ethereum.keystore.{KeyStore, Wallet}
14-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors._
14+
import io.iohk.ethereum.jsonrpc.JsonRpcError._
1515
import io.iohk.ethereum.rlp.RLPList
1616
import io.iohk.ethereum.transactions.PendingTransactionsManager
1717
import io.iohk.ethereum.transactions.PendingTransactionsManager.{AddOrOverrideTransaction, PendingTransactionsResponse}
@@ -178,7 +178,7 @@ class PersonalService(
178178
val dataEither = (tx.function, tx.contractCode) match {
179179
case (Some(function), None) => Right(rlp.encode(RLPList(function, args)))
180180
case (None, Some(contractCode)) => Right(rlp.encode(RLPList(contractCode, args)))
181-
case _ => Left(JsonRpcErrors.InvalidParams("Iele transaction should contain either functionName or contractCode"))
181+
case _ => Left(JsonRpcError.InvalidParams("Iele transaction should contain either functionName or contractCode"))
182182
}
183183

184184
dataEither match {

src/main/scala/io/iohk/ethereum/jsonrpc/QAJsonMethodsImplicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.iohk.ethereum.jsonrpc
33
import akka.util.ByteString
44
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonDecoder.NoParamsDecoder
55
import io.iohk.ethereum.jsonrpc.JsonRpcController.{Codec, JsonEncoder}
6-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
6+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
77
import io.iohk.ethereum.jsonrpc.QAService._
88
import org.json4s.Extraction
99
import org.json4s.JsonAST._

src/main/scala/io/iohk/ethereum/jsonrpc/QAService.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class QAService(
3939
.map(_ |> (MineBlocksResponse(_)) |> (_.asRight))
4040
.recover { case t: Throwable =>
4141
log.info("Unable to mine requested blocks", t)
42-
Left(JsonRpcErrors.InternalError)
42+
Left(JsonRpcError.InternalError)
4343
}
4444
}
4545

src/main/scala/io/iohk/ethereum/jsonrpc/TestJsonMethodsImplicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package io.iohk.ethereum.jsonrpc
22

33
import akka.util.ByteString
44
import io.iohk.ethereum.jsonrpc.JsonRpcController.{JsonDecoder, JsonEncoder}
5-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
5+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
66
import io.iohk.ethereum.jsonrpc.TestService._
77
import org.json4s.JsonAST._
88
import org.json4s.JsonDSL._

src/main/scala/io/iohk/ethereum/jsonrpc/server/http/JsonRpcHttpServer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ trait JsonRpcHttpServer extends Json4sSupport {
3737
.newBuilder()
3838
.handle {
3939
case _: MalformedRequestContentRejection =>
40-
complete((StatusCodes.BadRequest, JsonRpcResponse("2.0", None, Some(JsonRpcErrors.ParseError), JInt(0))))
40+
complete((StatusCodes.BadRequest, JsonRpcResponse("2.0", None, Some(JsonRpcError.ParseError), JInt(0))))
4141
case _: CorsRejection =>
4242
complete(StatusCodes.Forbidden)
4343
}

src/test/scala/io/iohk/ethereum/jsonrpc/CheckpointingJRCSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import io.iohk.ethereum.checkpointing.CheckpointingTestHelpers
44
import io.iohk.ethereum.crypto.ECDSASignature
55
import io.iohk.ethereum.jsonrpc.CheckpointingService._
66
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonRpcConfig
7-
import io.iohk.ethereum.jsonrpc.JsonRpcErrors.InvalidParams
7+
import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams
88
import io.iohk.ethereum.nodebuilder.SecureRandomBuilder
99
import io.iohk.ethereum.utils.{ByteStringUtils, Config}
1010
import io.iohk.ethereum.{Fixtures, NormalPatience, crypto}

src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,19 @@ class EthServiceSpec
884884
response.futureValue shouldEqual Right(GetBalanceResponse(123))
885885
}
886886

887+
it should "handle MissingNodeException when getting balance" in new TestSetup {
888+
val address = Address(ByteString(Hex.decode("abbb6bebfa05aa13e908eaa492bd7a8343760477")))
889+
890+
val newBlockHeader = blockToRequest.header
891+
val newblock = blockToRequest.copy(header = newBlockHeader)
892+
blockchain.storeBlock(newblock).commit()
893+
blockchain.saveBestKnownBlocks(newblock.header.number)
894+
895+
val response = ethService.getBalance(GetBalanceRequest(address, BlockParam.Latest))
896+
897+
response.futureValue shouldEqual Left(JsonRpcError.NodeNotFound)
898+
}
899+
887900
it should "handle getStorageAt request" in new TestSetup {
888901
import io.iohk.ethereum.rlp.UInt256RLPImplicits._
889902

0 commit comments

Comments
 (0)