Skip to content

Commit

Permalink
add AddBlock with Block obtained from pool based on hash & MineNextBl…
Browse files Browse the repository at this point in the history
…ock based on tx from pool
  • Loading branch information
fluency03 committed May 1, 2018
1 parent 6818589 commit a47ad7c
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -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]
}
Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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 =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
} ~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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 =
Expand All @@ -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 {
Expand All @@ -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) }
}
} ~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
} ~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand All @@ -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"
Expand Down

0 comments on commit a47ad7c

Please sign in to comment.