Skip to content

Commit

Permalink
[ETCM-331] Add ip limit in the faucet for sendFunds requests
Browse files Browse the repository at this point in the history
  • Loading branch information
SoseMagarian committed Nov 20, 2020
1 parent 65c23ba commit 84929cb
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import akka.actor.ActorSystem
import io.iohk.ethereum.faucet.{FaucetConfigBuilder, FaucetSupervisor}
import io.iohk.ethereum.jsonrpc.server.controllers.ApisBase
import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig
import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpServer
import io.iohk.ethereum.jsonrpc.server.http.FaucetJsonRpcHttpServer
import io.iohk.ethereum.keystore.KeyStoreImpl
import io.iohk.ethereum.mallet.service.RpcClient
import io.iohk.ethereum.utils.{ConfigUtils, KeyStoreConfig, Logger}
Expand Down Expand Up @@ -79,7 +79,7 @@ trait FaucetJsonRpcHttpServerBuilder {
with FaucetJsonRpcHealthCheckBuilder
with FaucetJsonRpcControllerBuilder =>

val faucetJsonRpcHttpServer = JsonRpcHttpServer(
val faucetJsonRpcHttpServer = FaucetJsonRpcHttpServer(
faucetJsonRpcController,
faucetJsonRpcHealthCheck,
jsonRpcConfig.httpServerConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import io.iohk.ethereum.utils.Logger
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

class BasicJsonRpcHttpServer(
abstract class BasicJsonRpcHttpServer(
val jsonRpcController: JsonRpcBaseController,
val jsonRpcHealthChecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.iohk.ethereum.jsonrpc.server.http

import java.security.SecureRandom
import java.time.Clock

import akka.actor.ActorSystem
import akka.http.scaladsl.model.{RemoteAddress, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Route, StandardRoute}
import ch.megard.akka.http.cors.scaladsl.CorsDirectives.cors
import com.twitter.util.LruMap
import io.iohk.ethereum.faucet.jsonrpc.FaucetJsonRpcController
import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController
import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpServer.JsonRpcHttpServerConfig
import io.iohk.ethereum.jsonrpc.{JsonRpcHealthChecker, JsonRpcRequest}
import io.iohk.ethereum.utils.Logger
import monix.execution.Scheduler.Implicits.global

trait FaucetJsonRpcHttpServer extends JsonRpcHttpServer {

val minRequestInterval: Int

val latestTimestampCacheSize: Int

val latestRequestTimestamps: LruMap[RemoteAddress, Long]

val clock: Clock = Clock.systemUTC()

override def route: Route = cors(corsSettings) {
(pathEndOrSingleSlash & post) {
(extractClientIP & entity(as[JsonRpcRequest])) { (clientAddress: RemoteAddress, request: JsonRpcRequest) =>
handleRequest(clientAddress, request)
}
}
}

def handleRequest(clientAddr: RemoteAddress, request: JsonRpcRequest): StandardRoute = {
val timeMillis = clock.instant().toEpochMilli

if (request.method == FaucetJsonRpcController.SendFunds) {
val latestRequestTimestamp = latestRequestTimestamps.getOrElse(clientAddr, 0L)
if (latestRequestTimestamp + minRequestInterval < timeMillis) {
latestRequestTimestamps.put(clientAddr, timeMillis)
complete(jsonRpcController.handleRequest(request).runToFuture)
} else {
complete(StatusCodes.TooManyRequests)
}
} else complete(jsonRpcController.handleRequest(request).runToFuture)
}
}

object FaucetJsonRpcHttpServer extends Logger {
def apply(
jsonRpcController: JsonRpcBaseController,
jsonRpcHealthchecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig,
secureRandom: SecureRandom
)(implicit actorSystem: ActorSystem): Either[String, JsonRpcHttpServer] =
config.mode match {
case "http" =>
Right(new FaucetBasicJsonRpcHttpServer(jsonRpcController, jsonRpcHealthchecker, config)(actorSystem))
case "https" =>
Right(new FaucetJsonRpcHttpsServer(jsonRpcController, jsonRpcHealthchecker, config, secureRandom)(actorSystem))
case _ => Left(s"Cannot start JSON RPC server: Invalid mode ${config.mode} selected")
}
}

class FaucetBasicJsonRpcHttpServer(
jsonRpcController: JsonRpcBaseController,
jsonRpcHealthChecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig
)(implicit actorSystem: ActorSystem)
extends BasicJsonRpcHttpServer(jsonRpcController, jsonRpcHealthChecker, config)(actorSystem)
with FaucetJsonRpcHttpServer {

//FIXME: these 2 variables should be retrieved from a config file
override val minRequestInterval: Int = 10000
override val latestTimestampCacheSize: Int = 1024

override val latestRequestTimestamps = new LruMap[RemoteAddress, Long](latestTimestampCacheSize)
}

class FaucetJsonRpcHttpsServer(
jsonRpcController: JsonRpcBaseController,
jsonRpcHealthChecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig,
secureRandom: SecureRandom
)(implicit actorSystem: ActorSystem)
extends JsonRpcHttpsServer(jsonRpcController, jsonRpcHealthChecker, config, secureRandom)(actorSystem)
with FaucetJsonRpcHttpServer {

//FIXME: these 2 variables should be retrieved from a config file
val minRequestInterval: Int = 10000
val latestTimestampCacheSize: Int = 1024

override val latestRequestTimestamps = new LruMap[RemoteAddress, Long](latestTimestampCacheSize)
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package io.iohk.ethereum.jsonrpc.server.http

import java.security.SecureRandom

import akka.actor.ActorSystem
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Directives.complete
import akka.http.scaladsl.server._
import ch.megard.akka.http.cors.javadsl.CorsRejection
import ch.megard.akka.http.cors.scaladsl.CorsDirectives._
import ch.megard.akka.http.cors.scaladsl.model.HttpOriginMatcher
import ch.megard.akka.http.cors.scaladsl.settings.CorsSettings
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import io.iohk.ethereum.jsonrpc._
import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers
import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController
import io.iohk.ethereum.utils.{ConfigUtils, Logger}
import monix.eval.Task
import monix.execution.Scheduler.Implicits.global
import org.json4s.{DefaultFormats, JInt, native}

trait JsonRpcHttpServer extends Json4sSupport {
abstract class JsonRpcHttpServer extends Json4sSupport {
val jsonRpcController: JsonRpcBaseController
val jsonRpcHealthChecker: JsonRpcHealthChecker

Expand All @@ -44,69 +38,17 @@ trait JsonRpcHttpServer extends Json4sSupport {
}
.result()

val route: Route = cors(corsSettings) {
(path("healthcheck") & pathEndOrSingleSlash & get) {
handleHealthcheck()
} ~ (pathEndOrSingleSlash & post) {
entity(as[JsonRpcRequest]) { request =>
handleRequest(request)
} ~ entity(as[Seq[JsonRpcRequest]]) { request =>
handleBatchRequest(request)
}
}
}
def route: Route

/**
* Try to start JSON RPC server
*/
def run(): Unit

private def handleHealthcheck(): StandardRoute = {
val responseF = jsonRpcHealthChecker.healthCheck()
val httpResponseF =
responseF.map {
case response if response.isOK =>
HttpResponse(
status = StatusCodes.OK,
entity = HttpEntity(ContentTypes.`application/json`, serialization.writePretty(response))
)
case response =>
HttpResponse(
status = StatusCodes.InternalServerError,
entity = HttpEntity(ContentTypes.`application/json`, serialization.writePretty(response))
)
}
complete(httpResponseF)
}

private def handleRequest(request: JsonRpcRequest) = {
complete(jsonRpcController.handleRequest(request).runToFuture)
}

private def handleBatchRequest(requests: Seq[JsonRpcRequest]) = {
complete {
Task
.traverse(requests)(request => jsonRpcController.handleRequest(request))
.runToFuture
}
}
}

object JsonRpcHttpServer extends Logger {

def apply(
jsonRpcController: JsonRpcBaseController,
jsonRpcHealthchecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig,
secureRandom: SecureRandom
)(implicit actorSystem: ActorSystem): Either[String, JsonRpcHttpServer] =
config.mode match {
case "http" => Right(new BasicJsonRpcHttpServer(jsonRpcController, jsonRpcHealthchecker, config)(actorSystem))
case "https" =>
Right(new JsonRpcHttpsServer(jsonRpcController, jsonRpcHealthchecker, config, secureRandom)(actorSystem))
case _ => Left(s"Cannot start JSON RPC server: Invalid mode ${config.mode} selected")
}

trait JsonRpcHttpServerConfig {
val mode: String
val enabled: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpsServer.HttpsSetupResult
import io.iohk.ethereum.utils.Logger
import java.io.{File, FileInputStream}
import java.security.{KeyStore, SecureRandom}

import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController

import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.io.Source
import scala.util.{Failure, Success, Try}

class JsonRpcHttpsServer(
abstract class JsonRpcHttpsServer(
val jsonRpcController: JsonRpcBaseController,
val jsonRpcHealthChecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.iohk.ethereum.jsonrpc.server.http

import java.security.SecureRandom

import akka.actor.ActorSystem
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpResponse, StatusCodes}
import akka.http.scaladsl.server.{Route, StandardRoute}
import io.iohk.ethereum.jsonrpc.JsonRpcHealthChecker
import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpServer.JsonRpcHttpServerConfig
import ch.megard.akka.http.cors.scaladsl.CorsDirectives.cors
import akka.http.scaladsl.server.Directives._
import io.iohk.ethereum.jsonrpc._
import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController
import io.iohk.ethereum.utils.Logger
import monix.eval.Task
import monix.execution.Scheduler.Implicits.global

trait NodeJsonRpcHttpServer extends JsonRpcHttpServer {

private def handleHealthcheck(): StandardRoute = {
val responseF = jsonRpcHealthChecker.healthCheck()
val httpResponseF =
responseF.map {
case response if response.isOK =>
HttpResponse(
status = StatusCodes.OK,
entity = HttpEntity(ContentTypes.`application/json`, serialization.writePretty(response))
)
case response =>
HttpResponse(
status = StatusCodes.InternalServerError,
entity = HttpEntity(ContentTypes.`application/json`, serialization.writePretty(response))
)
}
complete(httpResponseF)
}

private def handleRequest(request: JsonRpcRequest) = {
complete(jsonRpcController.handleRequest(request).runToFuture)
}

private def handleBatchRequest(requests: Seq[JsonRpcRequest]) = {
complete {
Task
.traverse(requests)(request => jsonRpcController.handleRequest(request))
.runToFuture
}
}

override def route: Route = cors(corsSettings) {
(path("healthcheck") & pathEndOrSingleSlash & get) {
handleHealthcheck()
} ~ (pathEndOrSingleSlash & post) {
entity(as[JsonRpcRequest]) { request: JsonRpcRequest => handleRequest(request) } ~ entity(
as[Seq[JsonRpcRequest]]
) { request =>
handleBatchRequest(request)
}
}
}

}

object NodeJsonRpcHttpServer extends Logger {
def apply(
jsonRpcController: JsonRpcBaseController,
jsonRpcHealthchecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig,
secureRandom: SecureRandom
)(implicit actorSystem: ActorSystem): Either[String, JsonRpcHttpServer] =
config.mode match {
case "http" => Right(new NodeBasicJsonRpcHttpServer(jsonRpcController, jsonRpcHealthchecker, config)(actorSystem))
case "https" =>
Right(new NodeJsonRpcHttpsServer(jsonRpcController, jsonRpcHealthchecker, config, secureRandom)(actorSystem))
case _ => Left(s"Cannot start JSON RPC server: Invalid mode ${config.mode} selected")
}

}

class NodeBasicJsonRpcHttpServer(
jsonRpcController: JsonRpcBaseController,
jsonRpcHealthChecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig
)(implicit actorSystem: ActorSystem)
extends BasicJsonRpcHttpServer(jsonRpcController, jsonRpcHealthChecker, config)(actorSystem)
with NodeJsonRpcHttpServer {}

class NodeJsonRpcHttpsServer(
jsonRpcController: JsonRpcBaseController,
jsonRpcHealthChecker: JsonRpcHealthChecker,
config: JsonRpcHttpServerConfig,
secureRandom: SecureRandom
)(implicit actorSystem: ActorSystem)
extends JsonRpcHttpsServer(jsonRpcController, jsonRpcHealthChecker, config, secureRandom)(actorSystem)
with NodeJsonRpcHttpServer {}
8 changes: 2 additions & 6 deletions src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package io.iohk.ethereum.nodebuilder

import java.security.SecureRandom
import java.util.concurrent.atomic.AtomicReference

import akka.actor.{ActorRef, ActorSystem}
import io.iohk.ethereum.blockchain.data.GenesisDataLoader
import io.iohk.ethereum.blockchain.sync.{BlockchainHostActor, SyncController}
import io.iohk.ethereum.consensus._
import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator
import io.iohk.ethereum.db.components.Storages.PruningModeComponent
import io.iohk.ethereum.db.components._
import io.iohk.ethereum.db.storage.AppStateStorage
Expand All @@ -17,7 +13,7 @@ import io.iohk.ethereum.jsonrpc.NetService.NetServiceConfig
import io.iohk.ethereum.jsonrpc._
import io.iohk.ethereum.jsonrpc.server.controllers.ApisBase
import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig
import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpServer
import io.iohk.ethereum.jsonrpc.server.http.NodeJsonRpcHttpServer
import io.iohk.ethereum.jsonrpc.server.ipc.JsonRpcIpcServer
import io.iohk.ethereum.keystore.{KeyStore, KeyStoreImpl}
import io.iohk.ethereum.ledger.Ledger.VMImpl
Expand Down Expand Up @@ -466,7 +462,7 @@ trait JSONRpcHttpServerBuilder {
with JSONRpcConfigBuilder =>

lazy val maybeJsonRpcHttpServer =
JsonRpcHttpServer(jsonRpcController, jsonRpcHealthChecker, jsonRpcConfig.httpServerConfig, secureRandom)
NodeJsonRpcHttpServer(jsonRpcController, jsonRpcHealthChecker, jsonRpcConfig.httpServerConfig, secureRandom)
}

trait JSONRpcIpcServerBuilder {
Expand Down

0 comments on commit 84929cb

Please sign in to comment.