diff --git a/README.md b/README.md
index 6042f89..74a4ae2 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
# blockchain-scala
Simple Blockchain Implementation in Scala.
+
+Inspired by [Daniel van Flymen](http://www.dvf.nyc/)'s blog [Learn Blockchains by Building One](https://hackernoon.com/learn-blockchains-by-building-one-117428612f46), I started to implement this Scala version of a simple Blockchain.
+
+REST API service is built on the open source projects [akka](https://github.com/akka/akka) and [akka-http](https://github.com/akka/akka-http).
+
diff --git a/scalastyle-config.xml b/scalastyle-config.xml
index 7a820fd..b316416 100644
--- a/scalastyle-config.xml
+++ b/scalastyle-config.xml
@@ -90,7 +90,7 @@
-
+
diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
new file mode 100644
index 0000000..10255fe
--- /dev/null
+++ b/src/main/resources/application.conf
@@ -0,0 +1,4 @@
+http {
+ host = "0.0.0.0"
+ port = 9090
+}
\ No newline at end of file
diff --git a/src/main/scala/com/fluency03/blockchain/api/Server.scala b/src/main/scala/com/fluency03/blockchain/api/Server.scala
index dd060cc..a8520e2 100644
--- a/src/main/scala/com/fluency03/blockchain/api/Server.scala
+++ b/src/main/scala/com/fluency03/blockchain/api/Server.scala
@@ -7,34 +7,38 @@ import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
-import com.fluency03.blockchain.api.actors.{BlockRegistryActor, BlockchainRegistryActor, TransactionRegistryActor}
-import com.fluency03.blockchain.api.routes.{BlockRoutes, BlockchainRoutes}
+import com.fluency03.blockchain.api.actors.{BlockActor, BlockchainActor, TransactionActor}
+import com.fluency03.blockchain.api.routes.{BlockRoutes, BlockchainRoutes, TransactionRoutes}
+import com.typesafe.config.ConfigFactory
import scala.concurrent.{Await, ExecutionContextExecutor, Future}
import scala.concurrent.duration.Duration
-object Server extends App with BlockchainRoutes with BlockRoutes {
+object Server extends App with BlockchainRoutes with BlockRoutes with TransactionRoutes {
implicit val system: ActorSystem = ActorSystem("blockchain-http-service")
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val executionContext: ExecutionContextExecutor = system.dispatcher
override lazy val log = Logging(system, classOf[App])
- val (interface, port) = (args(0), args(1).toInt)
+ val config = ConfigFactory.load()
+ val httpConfig = config.getConfig("http")
+ val (host, port) = (httpConfig.getString("host"), httpConfig.getInt("port"))
- val blockchainRegistryActor: ActorRef = system.actorOf(BlockchainRegistryActor.props, "blockchainRegistryActor")
- val blockRegistryActor: ActorRef = system.actorOf(BlockRegistryActor.props, "blockRegistryActor")
+ val blockchainActor: ActorRef = system.actorOf(BlockchainActor.props, BLOCKCHAIN_ACTOR_NAME)
+ val blockActor: ActorRef = system.actorOf(BlockActor.props, BLOCK_ACTOR_NAME)
+ val txActor: ActorRef = system.actorOf(TransactionActor.props, TX_ACTOR_NAME)
- lazy val routes: Route = blockchainRoutes ~ blockRoutes
+ lazy val routes: Route = blockchainRoutes ~ blockRoutes ~ txRoutes
val bindingFuture: Future[ServerBinding] =
- Http().bindAndHandle(routes, interface, port)
+ Http().bindAndHandle(routes, host, port)
bindingFuture.failed.foreach { ex =>
- log.error(ex, "Failed to bind to {}:{}!", interface, port)
+ log.error(ex, "Failed to bind to {}:{}!", host, port)
}
- log.info("Server online at http://{}:{}/", interface, port)
+ log.info("Server online at http://{}:{}/", host, port)
Await.result(system.whenTerminated, Duration.Inf)
}
diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/BlockActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/BlockActor.scala
new file mode 100644
index 0000000..b6e0ac1
--- /dev/null
+++ b/src/main/scala/com/fluency03/blockchain/api/actors/BlockActor.scala
@@ -0,0 +1,49 @@
+package com.fluency03.blockchain.api.actors
+
+import akka.actor.{Actor, ActorLogging, ActorSelection, Props}
+import com.fluency03.blockchain.api.actors.BlockActor._
+import com.fluency03.blockchain.api.utils.GenericMessage._
+import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, PARENT_UP, TX_ACTOR_NAME}
+import com.fluency03.blockchain.core.Block
+import scala.collection.mutable
+
+object BlockActor {
+ final case object GetBlocks
+ final case class CreateBlock(block: Block)
+ final case class GetBlock(hash: String)
+ final case class DeleteBlock(hash: String)
+
+ def props: Props = Props[BlockActor]
+}
+
+class BlockActor extends Actor with ActorLogging {
+ override def preStart(): Unit = log.info("{} started!", this.getClass.getSimpleName)
+ override def postStop(): Unit = log.info("{} stopped!", this.getClass.getSimpleName)
+
+ val blockchainActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKCHAIN_ACTOR_NAME)
+ val txActor: ActorSelection = context.actorSelection(PARENT_UP + TX_ACTOR_NAME)
+
+ var blocks = mutable.Map.empty[String, Block]
+
+ def receive: Receive = {
+ case GetBlocks => onGetBlocks()
+ case CreateBlock(block) => onCreateBlock(block)
+ case GetBlock(hash) => onGetBlock(hash)
+ case DeleteBlock(hash) => onDeleteBlock(hash)
+ }
+
+ private[this] def onGetBlocks(): Unit = sender() ! blocks.values.toList
+
+ private[this] def onCreateBlock(block: Block): Unit = {
+ blocks += (block.hash -> block)
+ sender() ! Response(s"Block ${block.hash} created.")
+ }
+
+ private[this] def onGetBlock(hash: String): Unit = sender() ! blocks.get(hash)
+
+ private[this] def onDeleteBlock(hash: String): Unit = {
+ blocks -= hash
+ sender() ! Response(s"Block $hash deleted.")
+ }
+
+}
diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/BlockRegistryActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/BlockRegistryActor.scala
deleted file mode 100644
index a78b25c..0000000
--- a/src/main/scala/com/fluency03/blockchain/api/actors/BlockRegistryActor.scala
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.fluency03.blockchain.api.actors
-
-import akka.actor.{Actor, ActorLogging, ActorRef, Props}
-import com.fluency03.blockchain.core.Block
-
-object BlockRegistryActor {
- final case class ActionPerformed(description: String)
- final case object GetBlocks
- final case class CreateBlock(block: Block)
- final case class GetBlock(hash: String)
- final case class DeleteBlock(hash: String)
-
- def props: Props = Props[TransactionRegistryActor]
-}
-
-class BlockRegistryActor extends Actor with ActorLogging {
- import BlockRegistryActor._
-
- val blockchainActor: ActorRef = context.actorOf(BlockchainRegistryActor.props)
-
- var blocks = Set.empty[Block]
-
- def receive: Receive = {
- case GetBlocks => sender() ! blocks.toList
- case CreateBlock(block) =>
- blocks += block
- sender() ! ActionPerformed(s"Block ${block.hash} created.")
- case GetBlock(hash) => sender() ! blocks.find(_.hash == hash)
- case DeleteBlock(hash) =>
- blocks.find(_.hash == hash) foreach { block => blocks -= block }
- sender() ! ActionPerformed(s"Block $hash deleted.")
- }
-}
diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala
new file mode 100644
index 0000000..0ba8cd7
--- /dev/null
+++ b/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala
@@ -0,0 +1,78 @@
+package com.fluency03.blockchain.api.actors
+
+import akka.actor.{Actor, ActorLogging, ActorSelection, Props}
+import com.fluency03.blockchain.api.actors.BlockchainActor._
+import com.fluency03.blockchain.api.actors.TransactionActor._
+import com.fluency03.blockchain.api.utils.GenericMessage._
+import com.fluency03.blockchain.api.{BLOCK_ACTOR_NAME, PARENT_UP, TX_ACTOR_NAME}
+import com.fluency03.blockchain.core.{Blockchain, Transaction}
+
+object BlockchainActor {
+ final case object GetBlockchain
+ final case object CreateBlockchain
+ final case object DeleteBlockchain
+
+ def props: Props = Props[BlockchainActor]
+}
+
+class BlockchainActor extends Actor with ActorLogging {
+ override def preStart(): Unit = log.info("{} started!", this.getClass.getSimpleName)
+ override def postStop(): Unit = log.info("{} stopped!", this.getClass.getSimpleName)
+
+ val txActor: ActorSelection = context.actorSelection(PARENT_UP + TX_ACTOR_NAME)
+ val blockActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCK_ACTOR_NAME)
+
+ var blockchainOpt: Option[Blockchain] = None
+
+ def receive: Receive = {
+ /* ---------- Blockchain actions ---------- */
+ case GetBlockchain => onGetBlockchain()
+ case CreateBlockchain => onCreateBlockchain()
+ case DeleteBlockchain => onDeleteBlockchain()
+ /* ---------- Transaction actions ---------- */
+ case GetTransactions => onGetTransactions()
+ case CreateTransaction(tx) => onCreateTransaction(tx)
+ case GetTransaction(hash) => onGetTransaction(hash)
+ case DeleteTransaction(hash) => onDeleteTransaction(hash)
+ }
+
+ /* ---------- Blockchain actions ---------- */
+ private[this] def onGetBlockchain(): Unit = sender() ! blockchainOpt
+
+ private[this] def onCreateBlockchain(): Unit =
+ if (blockchainOpt.isDefined) sender() ! Response(s"Blockchain already exists.")
+ else {
+ blockchainOpt = Some(Blockchain())
+ sender() ! Response(s"Blockchain created, with difficulty ${blockchainOpt.get.difficulty}.")
+ }
+
+ private[this] def onDeleteBlockchain(): Unit =
+ if (blockchainOpt.isDefined) {
+ blockchainOpt = None
+ sender() ! Response(s"Blockchain deleted.")
+ } else sender() ! Response(s"Blockchain does not exist.")
+
+ /* ---------- Transaction actions ---------- */
+ private[this] def onGetTransactions(): Unit = sender() ! {
+ if (blockchainOpt.isDefined) blockchainOpt.get.currentTransactions.toList
+ else List()
+ }
+
+ private[this] def onCreateTransaction(tx: Transaction): Unit =
+ if (blockchainOpt.isDefined) {
+ blockchainOpt.get.currentTransactions += (tx.hash -> tx)
+ sender() ! Response(s"Transaction ${tx.hash} created.")
+ } else sender() ! Response(s"Blockchain does not exist.")
+
+ private[this] def onGetTransaction(hash: String): Unit = sender() ! {
+ if (blockchainOpt.isDefined) blockchainOpt.get.currentTransactions(hash)
+ else None
+ }
+
+ private[this] def onDeleteTransaction(hash: String): Unit =
+ if (blockchainOpt.isDefined) {
+ blockchainOpt.get.currentTransactions -= hash
+ sender() ! Response(s"Transaction $hash deleted.")
+ } else sender() ! Response(s"Blockchain does not exist.")
+
+}
diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainRegistryActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainRegistryActor.scala
deleted file mode 100644
index ef114c1..0000000
--- a/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainRegistryActor.scala
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.fluency03.blockchain.api.actors
-
-import akka.actor.{Actor, ActorLogging, ActorRef, Props}
-import com.fluency03.blockchain.core.Blockchain
-
-object BlockchainRegistryActor {
- final case class ActionPerformed(description: String)
- final case object GetBlockchain
- final case object CreateBlockchain
- final case object DeleteBlockchain
-
- def props: Props = Props[BlockchainRegistryActor]
-}
-
-class BlockchainRegistryActor extends Actor with ActorLogging {
- import BlockchainRegistryActor._
-
- val blockActor: ActorRef = context.actorOf(BlockRegistryActor.props)
-
- var blockchainOpt: Option[Blockchain] = None
-
- def receive: Receive = {
- case GetBlockchain => sender() ! blockchainOpt.get
- case CreateBlockchain =>
- blockchainOpt = Some(Blockchain())
- sender() ! ActionPerformed(s"Blockchain created, with difficulty ${blockchainOpt.get.difficulty}.")
- case DeleteBlockchain =>
- blockchainOpt = None
- sender() ! ActionPerformed(s"Blockchain deleted.")
- }
-}
diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/TransactionActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/TransactionActor.scala
new file mode 100644
index 0000000..193c8c8
--- /dev/null
+++ b/src/main/scala/com/fluency03/blockchain/api/actors/TransactionActor.scala
@@ -0,0 +1,38 @@
+package com.fluency03.blockchain.api.actors
+
+import akka.actor.{Actor, ActorLogging, ActorSelection, Props}
+import com.fluency03.blockchain.api.actors.TransactionActor._
+import com.fluency03.blockchain.api.utils.GenericMessage._
+import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, BLOCK_ACTOR_NAME, PARENT_UP}
+import com.fluency03.blockchain.core.Transaction
+
+object TransactionActor {
+ final case object GetTransactions
+ final case class CreateTransaction(tx: Transaction)
+ final case class GetTransaction(hash: String)
+ final case class DeleteTransaction(hash: String)
+
+ def props: Props = Props[TransactionActor]
+}
+
+class TransactionActor extends Actor with ActorLogging {
+ override def preStart(): Unit = log.info("{} started!", this.getClass.getSimpleName)
+ override def postStop(): Unit = log.info("{} stopped!", this.getClass.getSimpleName)
+
+ val blockchainActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKCHAIN_ACTOR_NAME)
+ val blockActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCK_ACTOR_NAME)
+
+ def receive: Receive = {
+ case _ => blockchainActor forward _
+ }
+
+//
+// {
+// case msg @ GetTransactions => blockchainActor forward msg
+// case msg: CreateTransaction => blockchainActor forward msg
+// case msg: GetTransaction => blockchainActor forward msg
+// case msg: DeleteTransaction => blockchainActor forward msg
+// }
+
+
+}
diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/TransactionRegistryActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/TransactionRegistryActor.scala
deleted file mode 100644
index b3643c3..0000000
--- a/src/main/scala/com/fluency03/blockchain/api/actors/TransactionRegistryActor.scala
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.fluency03.blockchain.api.actors
-
-import akka.actor.{Actor, ActorLogging, ActorRef, Props}
-import com.fluency03.blockchain.core.{Block, Transaction}
-
-object TransactionRegistryActor {
- final case class ActionPerformed(description: String)
- final case object GetTransactions
- final case class CreateTransaction(tx: Transaction)
- final case class GetTransaction(hash: String)
- final case class DeleteTransaction(hash: String)
-
- def props: Props = Props[TransactionRegistryActor]
-}
-
-class TransactionRegistryActor extends Actor with ActorLogging {
- import TransactionRegistryActor._
-
- var transactions = Set.empty[Transaction]
-
- def receive: Receive = {
- case GetTransactions => sender() ! transactions.toList
- case CreateTransaction(tx) =>
- transactions += tx
- sender() ! ActionPerformed(s"Transaction ${tx.hash} created.")
- case GetTransaction(hash) => sender() ! transactions.find(_.hash == hash)
- case DeleteTransaction(hash) =>
- transactions.find(_.hash == hash) foreach { tx => transactions -= tx }
- sender() ! ActionPerformed(s"Transaction $hash deleted.")
- }
-}
diff --git a/src/main/scala/com/fluency03/blockchain/api/package.scala b/src/main/scala/com/fluency03/blockchain/api/package.scala
index f924bd9..46eeb35 100644
--- a/src/main/scala/com/fluency03/blockchain/api/package.scala
+++ b/src/main/scala/com/fluency03/blockchain/api/package.scala
@@ -1,9 +1,16 @@
package com.fluency03.blockchain
-import com.fluency03.blockchain.core.Block
+import com.fluency03.blockchain.core.{Block, Transaction}
package object api {
type Blocks = List[Block]
+ type Transactions = List[Transaction]
+
+ lazy val BLOCK_ACTOR_NAME = "blockActor"
+ lazy val BLOCKCHAIN_ACTOR_NAME = "blockchainActor"
+ lazy val TX_ACTOR_NAME = "txActor"
+
+ lazy val PARENT_UP = "../"
}
diff --git a/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala b/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala
index 6c2c42c..7f7685a 100644
--- a/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala
+++ b/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala
@@ -9,7 +9,9 @@ import akka.http.scaladsl.server.directives.MethodDirectives.{delete, get, post}
import akka.http.scaladsl.server.directives.PathDirectives.path
import akka.http.scaladsl.server.directives.RouteDirectives.complete
import akka.pattern.ask
-import com.fluency03.blockchain.api.actors.BlockRegistryActor._
+import com.fluency03.blockchain.api.Blocks
+import com.fluency03.blockchain.api.actors.BlockActor._
+import com.fluency03.blockchain.api.utils.GenericMessage.Response
import com.fluency03.blockchain.core.Block
import scala.concurrent.Future
@@ -17,35 +19,35 @@ import scala.concurrent.Future
trait BlockRoutes extends Routes {
lazy val log = Logging(system, classOf[BlockRoutes])
- def blockRegistryActor: ActorRef
+ def blockActor: ActorRef
lazy val blockRoutes: Route =
pathPrefix("blocks") {
pathEnd {
get {
- val blocks: Future[List[Block]] = (blockRegistryActor ? GetBlocks).mapTo[List[Block]]
+ val blocks: Future[Blocks] = (blockActor ? GetBlocks).mapTo[Blocks]
complete(blocks)
} ~
post {
entity(as[Block]) { block =>
- val blockCreated: Future[ActionPerformed] = (blockRegistryActor ? CreateBlock(block)).mapTo[ActionPerformed]
- onSuccess(blockCreated) { performed =>
- log.info("Created user [{}]: {}", block.hash, performed.description)
- complete((StatusCodes.Created, performed))
+ val blockCreated: Future[Response] = (blockActor ? CreateBlock(block)).mapTo[Response]
+ onSuccess(blockCreated) { resp =>
+ log.info("Created block [{}]: {}", block.hash, resp.message)
+ complete((StatusCodes.Created, resp))
}
}
}
} ~
path(Segment) { hash =>
get {
- val maybeBlock: Future[Option[Block]] = (blockRegistryActor ? GetBlock(hash)).mapTo[Option[Block]]
+ val maybeBlock: Future[Option[Block]] = (blockActor ? GetBlock(hash)).mapTo[Option[Block]]
rejectEmptyResponse { complete(maybeBlock) }
} ~
delete {
- val blockDeleted: Future[ActionPerformed] = (blockRegistryActor ? DeleteBlock(hash)).mapTo[ActionPerformed]
- onSuccess(blockDeleted) { performed =>
- log.info("Deleted block [{}]: {}", hash, performed.description)
- complete((StatusCodes.OK, performed))
+ val blockDeleted: Future[Response] = (blockActor ? DeleteBlock(hash)).mapTo[Response]
+ onSuccess(blockDeleted) { resp =>
+ log.info("Deleted block [{}]: {}", hash, resp.message)
+ complete((StatusCodes.OK, resp))
}
}
}
diff --git a/src/main/scala/com/fluency03/blockchain/api/routes/BlockchainRoutes.scala b/src/main/scala/com/fluency03/blockchain/api/routes/BlockchainRoutes.scala
index cc120da..5927aaa 100644
--- a/src/main/scala/com/fluency03/blockchain/api/routes/BlockchainRoutes.scala
+++ b/src/main/scala/com/fluency03/blockchain/api/routes/BlockchainRoutes.scala
@@ -8,7 +8,8 @@ import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.directives.MethodDirectives.{delete, get, post}
import akka.http.scaladsl.server.directives.RouteDirectives.complete
import akka.pattern.ask
-import com.fluency03.blockchain.api.actors.BlockchainRegistryActor._
+import com.fluency03.blockchain.api.actors.BlockchainActor._
+import com.fluency03.blockchain.api.utils.GenericMessage.Response
import com.fluency03.blockchain.core.Blockchain
import org.json4s.JsonAST.JValue
@@ -17,31 +18,29 @@ import scala.concurrent.Future
trait BlockchainRoutes extends Routes {
lazy val log = Logging(system, classOf[BlockchainRoutes])
- def blockchainRegistryActor: ActorRef
+ def blockchainActor: ActorRef
lazy val blockchainRoutes: Route =
pathPrefix("blockchain") {
pathEnd {
get {
- val users: Future[Blockchain] = (blockchainRegistryActor ? GetBlockchain).mapTo[Blockchain]
- complete(users)
+ val blockchain: Future[Option[Blockchain]] = (blockchainActor ? GetBlockchain).mapTo[Option[Blockchain]]
+ rejectEmptyResponse { complete(blockchain) }
} ~
post {
entity(as[JValue]) { _ =>
- val blockchainCreated: Future[ActionPerformed] =
- (blockchainRegistryActor ? CreateBlockchain).mapTo[ActionPerformed]
- onSuccess(blockchainCreated) { performed =>
- log.info("Created Blockchain: {}", performed.description)
- complete((StatusCodes.Created, performed))
+ val blockchainCreated: Future[Response] = (blockchainActor ? CreateBlockchain).mapTo[Response]
+ onSuccess(blockchainCreated) { resp =>
+ log.info("Created Blockchain: {}", resp.message)
+ complete((StatusCodes.Created, resp))
}
}
} ~
delete {
- val blockchainDeleted: Future[ActionPerformed] =
- (blockchainRegistryActor ? DeleteBlockchain).mapTo[ActionPerformed]
- onSuccess(blockchainDeleted) { performed =>
- log.info("Deleted Blockchain: {}", performed.description)
- complete((StatusCodes.OK, performed))
+ val blockchainDeleted: Future[Response] = (blockchainActor ? DeleteBlockchain).mapTo[Response]
+ onSuccess(blockchainDeleted) { resp =>
+ log.info("Deleted Blockchain: {}", resp.message)
+ complete((StatusCodes.OK, resp))
}
}
}
diff --git a/src/main/scala/com/fluency03/blockchain/api/routes/Routes.scala b/src/main/scala/com/fluency03/blockchain/api/routes/Routes.scala
index ec29adc..ee4916c 100644
--- a/src/main/scala/com/fluency03/blockchain/api/routes/Routes.scala
+++ b/src/main/scala/com/fluency03/blockchain/api/routes/Routes.scala
@@ -1,9 +1,8 @@
package com.fluency03.blockchain.api.routes
import akka.actor.ActorSystem
-import akka.event.Logging
import akka.util.Timeout
-import com.fluency03.blockchain.api.JsonSupport
+import com.fluency03.blockchain.api.utils.JsonSupport
import scala.concurrent.duration._
@@ -11,7 +10,7 @@ trait Routes extends JsonSupport {
// we leave these abstract, since they will be provided by the App
implicit def system: ActorSystem
- // Required by the `ask` (?) method below
+ // Required by the `ask` (?) method
implicit lazy val timeout: Timeout = Timeout(5.seconds) // usually we'd obtain the timeout from the system's configuration
}
diff --git a/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala b/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala
index 727f180..92c5c9d 100644
--- a/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala
+++ b/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala
@@ -9,45 +9,47 @@ import akka.http.scaladsl.server.directives.MethodDirectives.{delete, get, post}
import akka.http.scaladsl.server.directives.PathDirectives.path
import akka.http.scaladsl.server.directives.RouteDirectives.complete
import akka.pattern.ask
-import com.fluency03.blockchain.api.actors.BlockRegistryActor._
-import com.fluency03.blockchain.core.Block
+import com.fluency03.blockchain.api.Transactions
+import com.fluency03.blockchain.api.actors.TransactionActor._
+import com.fluency03.blockchain.api.utils.GenericMessage.Response
+import com.fluency03.blockchain.core.Transaction
import scala.concurrent.Future
trait TransactionRoutes extends Routes {
lazy val log = Logging(system, classOf[TransactionRoutes])
- def txRoutesRegistryActor: ActorRef
+ def txActor: ActorRef
-// lazy val transactionRoutes: Route =
-// pathPrefix("transactions") {
-// pathEnd {
-// get {
-// val blocks: Future[List[Block]] = (txRoutesRegistryActor ? GetBlocks).mapTo[List[Block]]
-// complete(blocks)
-// } ~
-// post {
-// entity(as[Block]) { block =>
-// val blockCreated: Future[ActionPerformed] = (txRoutesRegistryActor ? CreateBlock(block)).mapTo[ActionPerformed]
-// onSuccess(blockCreated) { performed =>
-// log.info("Created user [{}]: {}", block.hash, performed.description)
-// complete((StatusCodes.Created, performed))
-// }
-// }
-// }
-// } ~
-// path(Segment) { hash =>
-// get {
-// val maybeBlock: Future[Option[Block]] = (txRoutesRegistryActor ? GetBlock(hash)).mapTo[Option[Block]]
-// rejectEmptyResponse { complete(maybeBlock) }
-// } ~
-// delete {
-// val blockDeleted: Future[ActionPerformed] = (txRoutesRegistryActor ? DeleteBlock(hash)).mapTo[ActionPerformed]
-// onSuccess(blockDeleted) { performed =>
-// log.info("Deleted block [{}]: {}", hash, performed.description)
-// complete((StatusCodes.OK, performed))
-// }
-// }
-// }
-// }
+ lazy val txRoutes: Route =
+ pathPrefix("transactions") {
+ pathEnd {
+ get {
+ val transactions: Future[Transactions] = (txActor ? GetTransactions).mapTo[Transactions]
+ complete(transactions)
+ } ~
+ post {
+ entity(as[Transaction]) { tx =>
+ val txCreated: Future[Response] = (txActor ? CreateTransaction(tx)).mapTo[Response]
+ onSuccess(txCreated) { resp =>
+ log.info("Created transaction [{}]: {}", tx.hash, resp.message)
+ complete((StatusCodes.Created, resp))
+ }
+ }
+ }
+ } ~
+ path(Segment) { hash =>
+ get {
+ val maybeTx: Future[Option[Transaction]] = (txActor ? GetTransaction(hash)).mapTo[Option[Transaction]]
+ rejectEmptyResponse { complete(maybeTx) }
+ } ~
+ delete {
+ val txDeleted: Future[Response] = (txActor ? DeleteTransaction(hash)).mapTo[Response]
+ onSuccess(txDeleted) { resp =>
+ log.info("Deleted transaction [{}]: {}", hash, resp.message)
+ complete((StatusCodes.OK, resp))
+ }
+ }
+ }
+ }
}
diff --git a/src/main/scala/com/fluency03/blockchain/api/utils/GenericMessage.scala b/src/main/scala/com/fluency03/blockchain/api/utils/GenericMessage.scala
new file mode 100644
index 0000000..e0cfcf8
--- /dev/null
+++ b/src/main/scala/com/fluency03/blockchain/api/utils/GenericMessage.scala
@@ -0,0 +1,7 @@
+package com.fluency03.blockchain.api.utils
+
+object GenericMessage {
+
+ final case class Response(message: String)
+
+}
diff --git a/src/main/scala/com/fluency03/blockchain/api/JsonSupport.scala b/src/main/scala/com/fluency03/blockchain/api/utils/JsonSupport.scala
similarity index 89%
rename from src/main/scala/com/fluency03/blockchain/api/JsonSupport.scala
rename to src/main/scala/com/fluency03/blockchain/api/utils/JsonSupport.scala
index 0886e28..55601d2 100644
--- a/src/main/scala/com/fluency03/blockchain/api/JsonSupport.scala
+++ b/src/main/scala/com/fluency03/blockchain/api/utils/JsonSupport.scala
@@ -1,4 +1,4 @@
-package com.fluency03.blockchain.api
+package com.fluency03.blockchain.api.utils
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.native.Serialization
diff --git a/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala b/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala
index b19ae9d..43deb2b 100644
--- a/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala
+++ b/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala
@@ -1,8 +1,9 @@
package com.fluency03.blockchain.core
import com.fluency03.blockchain.Util.getCurrentTimestamp
+import com.fluency03.blockchain.core.Blockchain._
-import scala.collection.mutable.ListBuffer
+import scala.collection.mutable
/**
* Blockchain with difficulty and the chain of Blocks.
@@ -10,18 +11,18 @@ import scala.collection.mutable.ListBuffer
* @param chain Chain of Blocks
*/
case class Blockchain(difficulty: Int = 4, chain: List[Block] = List(Block.genesisBlock)) {
- val currentTransactions: ListBuffer[Transaction] = new ListBuffer[Transaction]()
+ val currentTransactions: mutable.Map[String, Transaction] = mutable.Map.empty[String, Transaction]
def addBlock(newBlockData: String): Blockchain = {
- Blockchain(difficulty, mineNextBlock(newBlockData).addTransactions(currentTransactions.toList) :: chain)
+ Blockchain(difficulty, mineNextBlock(newBlockData).addTransactions(currentTransactions.values.toList) :: chain)
}
def addBlock(newBlock: Block): Blockchain = {
Blockchain(difficulty, newBlock :: chain)
}
- def addTransaction(t: Transaction): Blockchain = {
- currentTransactions += t
+ def addTransaction(tx: Transaction): Blockchain = {
+ currentTransactions += (tx.hash -> tx)
this
}
@@ -32,7 +33,7 @@ case class Blockchain(difficulty: Int = 4, chain: List[Block] = List(Block.genes
addTransaction(Transaction(sender, receiver, amount, timestamp))
def addTransactions(trans: List[Transaction]): Blockchain = {
- currentTransactions ++= trans
+ currentTransactions ++= trans.map(tx => (tx.hash, tx))
this
}
@@ -46,16 +47,27 @@ case class Blockchain(difficulty: Int = 4, chain: List[Block] = List(Block.genes
lastHeader.index + 1,
lastHeader.hash,
newBlockData,
- currentTransactions.toList,
+ currentTransactions.values.toList,
getCurrentTimestamp,
difficulty)
}
+ def isValid: Boolean = chain match {
+ case Nil => throw new NoSuchElementException("Blockchain is Empty!")
+ case _ => isValidChain(chain, difficulty)
+ }
+
}
object Blockchain {
def apply(difficulty: Int): Blockchain = new Blockchain(difficulty, List(Block.genesis(difficulty)))
+ def isValidChain(chain: List[Block], difficulty: Int): Boolean = chain match {
+ case Nil => true
+ case g :: Nil => g.previousHash == ZERO64 && g.isValid(difficulty)
+ case a :: b :: tail => a.previousHash == b.hash && a.isValid(difficulty) && isValidChain(b :: tail, difficulty)
+ }
+
}
diff --git a/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala b/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala
index cc892d9..6716f69 100644
--- a/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala
+++ b/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala
@@ -2,6 +2,7 @@ package com.fluency03.blockchain.core
import org.scalatest.{FlatSpec, Matchers}
+import scala.collection.mutable
import scala.collection.mutable.ListBuffer
class BlockchainTest extends FlatSpec with Matchers {
@@ -22,7 +23,7 @@ class BlockchainTest extends FlatSpec with Matchers {
blockchain.chain shouldEqual List(genesis)
blockchain.lastBlock().isEmpty shouldEqual false
blockchain.lastBlock().get shouldEqual genesis
- blockchain.currentTransactions shouldEqual new ListBuffer[Transaction]()
+ blockchain.currentTransactions shouldEqual mutable.Map.empty[String, Transaction]
}
"A new Blockchain with different difficulty" should "have all default values but the difficulty." in {
@@ -30,25 +31,28 @@ class BlockchainTest extends FlatSpec with Matchers {
blockchainOf5.chain shouldEqual List(genesisOf5)
blockchainOf5.lastBlock().isEmpty shouldEqual false
blockchainOf5.lastBlock().get shouldEqual genesisOf5
- blockchainOf5.currentTransactions shouldEqual new ListBuffer[Transaction]()
+ blockchainOf5.currentTransactions shouldEqual mutable.Map.empty[String, Transaction]
}
"Add a Transaction to a Blockchain" should "add these Transactions to its currentTransactions collection." in {
- val trans = new ListBuffer[Transaction]()
+ val trans = mutable.Map.empty[String, Transaction]
blockchain.addTransaction(t1)
- trans += t1
+ trans += (t1.hash -> t1)
blockchain.currentTransactions shouldEqual trans
blockchain.addTransaction(t2)
blockchain.addTransaction(t3)
- trans ++= t2 :: t3 :: Nil
+ trans += (t2.hash -> t2)
+ trans += (t3.hash -> t3)
blockchain.currentTransactions shouldEqual trans
}
"Add a List of Transaction to a Blockchain" should "add these Transactions to its currentTransactions collection." in {
- val trans = new ListBuffer[Transaction]()
+ val trans = mutable.Map.empty[String, Transaction]
blockchainOf5.addTransactions(t2 :: t3 :: t4 :: Nil)
- trans ++= t2 :: t3 :: t4 :: Nil
+ trans += (t2.hash -> t2)
+ trans += (t3.hash -> t3)
+ trans += (t4.hash -> t4)
blockchainOf5.currentTransactions shouldEqual trans
}