diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala index 4a6c11f..10c8478 100644 --- a/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala +++ b/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala @@ -1,22 +1,28 @@ package com.fluency03.blockchain.api.actors import akka.actor.{ActorSelection, Props} +import akka.http.scaladsl.server.Directives.onSuccess +import akka.pattern.ask import com.fluency03.blockchain.api.actors.BlockchainActor._ import com.fluency03.blockchain.api._ import com.fluency03.blockchain.core.{Block, Blockchain, Transaction} import scala.collection.mutable +import scala.concurrent.Future +import scala.util.{Failure, Success} object BlockchainActor { final case object GetBlockchain final case object CreateBlockchain final case object DeleteBlockchain - final case class GetBlock(hash: String) + final case class GetBlockFromChain(hash: String) final case class GetTxOfBlock(id: String, hash: String) final case class AddBlock(block: Block) + final case class AddBlockFromPool(hash: String) final case object RemoveBlock - final case class MineNextBlock(data: String, trans: Seq[Transaction]) + final case class MineNextBlock(data: String, ids: Seq[String]) final case object CheckBlockchainValidity + final case class GetBlockFromPool(hash: String) def props: Props = Props[BlockchainActor] } @@ -25,7 +31,9 @@ class BlockchainActor extends ActorSupport { override def preStart(): Unit = log.info("{} started!", this.getClass.getSimpleName) override def postStop(): Unit = log.info("{} stopped!", this.getClass.getSimpleName) - val blockActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKS_ACTOR_NAME) + import context.dispatcher + + val blocksActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKS_ACTOR_NAME) val transActor: ActorSelection = context.actorSelection(PARENT_UP + TRANS_ACTOR_NAME) val networkActor: ActorSelection = context.actorSelection(PARENT_UP + NETWORK_ACTOR_NAME) @@ -37,19 +45,20 @@ class BlockchainActor extends ActorSupport { case GetBlockchain => onGetBlockchain() case CreateBlockchain => onCreateBlockchain() case DeleteBlockchain => onDeleteBlockchain() - case GetBlock(hash) => onGetBlock(hash) + case GetBlockFromChain(hash) => onGetBlockFromChain(hash) case GetTxOfBlock(id, hash) => onGetTxOfBlock(id, hash) case AddBlock(block) => onAddBlock(block) + case AddBlockFromPool(hash) => onAddBlockFromPool(hash) case RemoveBlock => onRemoveBlock() - case MineNextBlock(data, trans) => onMineNextBlock(data, trans) + case MineNextBlock(data, ids) => onMineNextBlock(data, ids) case CheckBlockchainValidity => onCheckBlockchainValidity() + case GetBlockFromPool(hash) => onGetBlockFromPool(hash) case _ => unhandled _ } /** - * TODO (Chang): new APIS: - * - AddBlock with Block obtained from pool based on hash - * - MineNextBlock with Transactions + * TODO (Chang): + * */ private def onGetBlockchain(): Unit = sender() ! blockchainOpt @@ -73,10 +82,10 @@ class BlockchainActor extends ActorSupport { sender() ! SuccessMsg("Blockchain deleted.") } else sender() ! FailureMsg("Blockchain does not exist.") - private def onGetBlock(hash: String): Unit = sender() ! getBlock(hash) + private def onGetBlockFromChain(hash: String): Unit = sender() ! getBlockFromChain(hash) private def onGetTxOfBlock(id: String, hash: String): Unit = sender() ! { - getBlock(hash) match { + getBlockFromChain(hash) match { case Some(block) => block.transactions.find(_.id == id) case None => None } @@ -93,6 +102,25 @@ class BlockchainActor extends ActorSupport { sender() ! FailureMsg("Blockchain does not exist.") } + private def onAddBlockFromPool(hash: String): Unit = blockchainOpt match { + case Some(blockchain) => + val maybeBlock: Future[Option[Block]] = (blocksActor ? BlocksActor.GetBlock(hash)).mapTo[Option[Block]] + maybeBlock onComplete { + case Success(blockOpt) => blockOpt match { + case Some(block) => + blockchainOpt = Some(blockchain.addBlock(block)) + hashIndexMapping += (block.hash -> blockchain.length) + sender() ! SuccessMsg(s"New Block ${block.hash} added on the chain.") + case None => sender() ! FailureMsg(s"Does not find Block $hash in the poll.") + } + case Failure(t) => sender() ! FailureMsg(s"Cannot get Block $hash from the poll.") + } + case None => + log.error("Blockchain does not exist! Clear the hash-to-index mapping!") + hashIndexMapping.clear() + sender() ! FailureMsg("Blockchain does not exist.") + } + private def onRemoveBlock(): Unit = blockchainOpt match { case Some(blockchain) => val toBeRemoved = blockchain.chain.head @@ -105,8 +133,17 @@ class BlockchainActor extends ActorSupport { sender() ! FailureMsg("Blockchain does not exist.") } - private def onMineNextBlock(data: String, trans: Seq[Transaction]): Unit = blockchainOpt match { - case Some(blockchain) => sender() ! Some(blockchain.mineNextBlock(data, trans)) + private def onMineNextBlock(data: String, ids: Seq[String]): Unit = blockchainOpt match { + case Some(blockchain) => + if (ids.isEmpty) sender() ! Some(blockchain.mineNextBlock(data, Seq.empty[Transaction])) + else { + val maybeTrans: Future[Seq[Transaction]] = + (transActor ? TransactionsActor.GetTransactions(ids.toSet)).mapTo[Seq[Transaction]] + maybeTrans onComplete { + case Success(trans) => sender() ! Some(blockchain.mineNextBlock(data, trans)) + case Failure(_) => sender() ! None + } + } case None => log.error("Blockchain does not exist! Clear the hash-to-index mapping!") hashIndexMapping.clear() @@ -123,8 +160,10 @@ class BlockchainActor extends ActorSupport { sender() ! FailureMsg("Blockchain does not exist.") } + private def onGetBlockFromPool(hash: String): Unit = blocksActor forward BlocksActor.GetBlock(hash) + - private def getBlock(hash: String): Option[Block] = hashIndexMapping.get(hash) match { + private def getBlockFromChain(hash: String): Option[Block] = hashIndexMapping.get(hash) match { case Some(index) => blockchainOpt match { case Some(blockchain) => Some(blockchain.chain(index)) case None => 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 c9cd84c..3ac0bd0 100644 --- a/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala +++ b/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala @@ -32,10 +32,11 @@ trait BlockRoutes extends RoutesSupport { lazy val blockRoutes: Route = path(BLOCKS) { - parameters( 'hashes.as(CsvSeq[String]) ? ) { hashes => - val blocks: Future[Blocks] = - if (hashes.isDefined) (blocksActor ? GetBlocks(hashes.get.toSet)).mapTo[Blocks] - else (blocksActor ? GetBlocks).mapTo[Blocks] + parameters( 'hashes.as(CsvSeq[String]).? ) { hashesOpt => + val blocks: Future[Blocks] = hashesOpt match { + case Some(hashes) => (blocksActor ? GetBlocks(hashes.toSet)).mapTo[Blocks] + case None => (blocksActor ? GetBlocks).mapTo[Blocks] + } complete(blocks) } } ~ 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 02d2ab1..80947c0 100644 --- a/src/main/scala/com/fluency03/blockchain/api/routes/BlockchainRoutes.scala +++ b/src/main/scala/com/fluency03/blockchain/api/routes/BlockchainRoutes.scala @@ -7,6 +7,7 @@ import akka.http.scaladsl.server.Directives._ 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.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers.CsvSeq import akka.pattern.ask import com.fluency03.blockchain.api.{FailureMsg, Input, Message} import com.fluency03.blockchain.api.actors.BlockchainActor._ @@ -20,9 +21,8 @@ trait BlockchainRoutes extends RoutesSupport { def blockchainActor: ActorRef /** - * TODO (Chang): new APIS: - * - AddBlock with Block obtained from pool based on hash - * - MineNextBlock with Transactions + * TODO (Chang): + * */ lazy val blockchainRoutes: Route = @@ -43,22 +43,27 @@ trait BlockchainRoutes extends RoutesSupport { onSuccess(blockchainCreated) { respondOnCreation } } ~ put { - parameters('action) { - case ADD_BLOCK_ACTION => - entity(as[Block]) { block => - val blockchainUpdated: Future[Message] = (blockchainActor ? AddBlock(block)).mapTo[Message] - onSuccess(blockchainUpdated) { respondOnUpdate } + parameters('action, 'hash.?, 'id.as(CsvSeq[String]).?) { + (action, hashOpt: Option[String], idsOpt: Option[Seq[String]]) => action match { + case ADD_BLOCK_ACTION => hashOpt match { + case Some(hash) => + val blockchainUpdated: Future[Message] = (blockchainActor ? AddBlockFromPool(hash)).mapTo[Message] + onSuccess(blockchainUpdated) { respondOnUpdate } + case None => entity(as[Block]) { block => + val blockchainUpdated: Future[Message] = (blockchainActor ? AddBlock(block)).mapTo[Message] + onSuccess(blockchainUpdated) { respondOnUpdate } + } } - case REMOVE_BLOCK_ACTION => - val blockchainUpdated: Future[Message] = (blockchainActor ? RemoveBlock).mapTo[Message] - onSuccess(blockchainUpdated) { respondOnUpdate } - case MINE_NEXT_BLOCK_ACTION => - entity(as[Input]) { in => + case REMOVE_BLOCK_ACTION => + val blockchainUpdated: Future[Message] = (blockchainActor ? RemoveBlock).mapTo[Message] + onSuccess(blockchainUpdated) { respondOnUpdate } + case MINE_NEXT_BLOCK_ACTION => entity(as[Input]) { in => val maybeNextBlock: Future[Option[Block]] = - (blockchainActor ? MineNextBlock(in.content, Seq.empty[Transaction])).mapTo[Option[Block]] + (blockchainActor ? MineNextBlock(in.content, idsOpt.getOrElse(Seq.empty[String]))).mapTo[Option[Block]] rejectEmptyResponse { complete(maybeNextBlock) } } - case act => complete((StatusCodes.BadRequest, FailureMsg(s"Action Not Supported: $act"))) + case act => complete((StatusCodes.BadRequest, FailureMsg(s"Action Not Supported: $act"))) + } } } ~ delete { @@ -69,7 +74,7 @@ trait BlockchainRoutes extends RoutesSupport { pathPrefix(BLOCK / Segment) { hash => pathEnd { get { - val maybeBlock: Future[Option[Block]] = (blockchainActor ? GetBlock(hash)).mapTo[Option[Block]] + val maybeBlock: Future[Option[Block]] = (blockchainActor ? GetBlockFromChain(hash)).mapTo[Option[Block]] rejectEmptyResponse { complete(maybeBlock) } } } ~ diff --git a/src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala b/src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala index d4f301a..ecc023e 100644 --- a/src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala +++ b/src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala @@ -35,10 +35,11 @@ trait NetworkRoutes extends RoutesSupport { } ~ path(PEERS) { get { - parameters( 'names.as(CsvSeq[String]) ? ) { names => - val peers: Future[Map[String, Set[String]]] = - if (names.isDefined) (networkActor ? GetPeers(names.get.toSet)).mapTo[Map[String, Set[String]]] - else (networkActor ? GetPeers).mapTo[Map[String, Set[String]]] + parameters( 'names.as(CsvSeq[String]).? ) { namesOpt => + val peers: Future[Map[String, Set[String]]] = namesOpt match { + case Some(names) => (networkActor ? GetPeers(names.toSet)).mapTo[Map[String, Set[String]]] + case None => (networkActor ? GetPeers).mapTo[Map[String, Set[String]]] + } complete(peers) } } 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 7549899..c29fc56 100644 --- a/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala +++ b/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala @@ -30,10 +30,11 @@ trait TransactionRoutes extends RoutesSupport { lazy val transRoutes: Route = path(TRANSACTIONS) { - parameters( 'ids.as(CsvSeq[String]) ? ) { ids => - val transactions: Future[Transactions] = - if (ids.isDefined) (transActor ? GetTransactions(ids.get.toSet)).mapTo[Transactions] - else (transActor ? GetTransactions).mapTo[Transactions] + parameters( 'ids.as(CsvSeq[String]).? ) { idsOpt => + val transactions: Future[Transactions] = idsOpt match { + case Some(ids) => (transActor ? GetTransactions(ids.toSet)).mapTo[Transactions] + case None => (transActor ? GetTransactions).mapTo[Transactions] + } complete(transactions) } } ~ diff --git a/src/test/scala/com/fluency03/blockchain/api/actors/BlockchainActorTest.scala b/src/test/scala/com/fluency03/blockchain/api/actors/BlockchainActorTest.scala index a29bb38..efeacb9 100644 --- a/src/test/scala/com/fluency03/blockchain/api/actors/BlockchainActorTest.scala +++ b/src/test/scala/com/fluency03/blockchain/api/actors/BlockchainActorTest.scala @@ -36,11 +36,11 @@ class BlockchainActorTest extends TestKit(ActorSystem("BlockchainActorTest")) wi blockchainActor ! GetBlockchain val blockchain = expectMsgType[Some[Blockchain]].get - blockchainActor ! GetBlock("somehash") + blockchainActor ! GetBlockFromChain("somehash") expectMsg(None) val genesis = Block.genesisBlock - blockchainActor ! GetBlock(genesis.hash) + blockchainActor ! GetBlockFromChain(genesis.hash) expectMsg(Some(genesis)) blockchainActor ! GetTxOfBlock(genesis.transactions.head.id, genesis.hash) @@ -67,7 +67,7 @@ class BlockchainActorTest extends TestKit(ActorSystem("BlockchainActorTest")) wi blockchainActor ! CheckBlockchainValidity expectMsg(SuccessMsg("true")) - blockchainActor ! MineNextBlock("next", Seq.empty[Transaction]) + blockchainActor ! MineNextBlock("next", Seq.empty[String]) val actualBlock = expectMsgType[Some[Block]].get actualBlock.data shouldEqual "next" actualBlock.transactions shouldEqual Seq.empty[Transaction] @@ -91,7 +91,7 @@ class BlockchainActorTest extends TestKit(ActorSystem("BlockchainActorTest")) wi blockchainActor ! CheckBlockchainValidity expectMsg(FailureMsg("Blockchain does not exist.")) - blockchainActor ! MineNextBlock("next", Seq.empty[Transaction]) + blockchainActor ! MineNextBlock("next", Seq.empty[String]) expectMsg(None) blockchainActor ! "other"