Skip to content

Commit

Permalink
add network actor
Browse files Browse the repository at this point in the history
  • Loading branch information
fluency03 committed Apr 24, 2018
1 parent 3ee0537 commit aa78511
Show file tree
Hide file tree
Showing 18 changed files with 143 additions and 108 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

Simple Blockchain Implementation in Scala.

*This project is still under development.*

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)
- [Naivecoin](https://github.com/lhartikk/naivecoin) of [@lhartikk](https://github.com/lhartikk)
Expand Down
4 changes: 1 addition & 3 deletions src/main/scala/com/fluency03/blockchain/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Util {
def getCurrentTimestamp: Long = Instant.now.getEpochSecond

/**
*
* Parse a time format string to its Epoch time in seconds.
*/
def epochTimeOf(t: String): Long = Instant.parse(t).getEpochSecond

Expand Down Expand Up @@ -60,6 +60,4 @@ object Util {
*/
def fromBase64(base64: String): String = new String(Base64.decode(base64), "UTF-8")



}
10 changes: 6 additions & 4 deletions src/main/scala/com/fluency03/blockchain/api/Server.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import com.typesafe.config.ConfigFactory
import scala.concurrent.{Await, ExecutionContextExecutor, Future}
import scala.concurrent.duration.Duration

object Server extends App with BlockchainRoutes with BlockRoutes with TransactionRoutes with GenericRoutes {
object Server extends App
with BlockchainRoutes with BlockRoutes with TransactionRoutes with NetworkRoutes with GenericRoutes {
implicit val system: ActorSystem = ActorSystem("blockchain-http-service")
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val executionContext: ExecutionContextExecutor = system.dispatcher
Expand All @@ -26,10 +27,11 @@ object Server extends App with BlockchainRoutes with BlockRoutes with Transactio
val (host, port) = (httpConfig.getString("host"), httpConfig.getInt("port"))

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)
val blocksActor: ActorRef = system.actorOf(BlocksActor.props, BLOCKS_ACTOR_NAME)
val transActor: ActorRef = system.actorOf(TransactionsActor.props, TRANS_ACTOR_NAME)
val networkActor: ActorRef = system.actorOf(NetworkActor.props, NETWORK_ACTOR_NAME)

lazy val routes: Route = blockchainRoutes ~ blockRoutes ~ txRoutes ~ genericRoutes
lazy val routes: Route = blockchainRoutes ~ blockRoutes ~ transRoutes ~ networkRoutes ~ genericRoutes

val bindingFuture: Future[ServerBinding] =
Http().bindAndHandle(routes, host, port)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ 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}
import com.fluency03.blockchain.api.{BLOCKS_ACTOR_NAME, NETWORK_ACTOR_NAME, PARENT_UP, TRANS_ACTOR_NAME}
import com.fluency03.blockchain.core.Blockchain

object BlockchainActor {
final case object GetBlockchain
Expand All @@ -19,12 +18,15 @@ 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)
val blockActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKS_ACTOR_NAME)
val txActor: ActorSelection = context.actorSelection(PARENT_UP + TRANS_ACTOR_NAME)
val networkActor: ActorSelection = context.actorSelection(PARENT_UP + NETWORK_ACTOR_NAME)

// TODO (Chang): not persistent
var blockchainOpt: Option[Blockchain] = None

// akka.actor.Status.Failure(e)

def receive: Receive = {
case GetBlockchain => onGetBlockchain()
case CreateBlockchain => onCreateBlockchain()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
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.actors.BlocksActor._
import com.fluency03.blockchain.api.utils.GenericMessage._
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, PARENT_UP, TX_ACTOR_NAME}
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, NETWORK_ACTOR_NAME, PARENT_UP, TRANS_ACTOR_NAME}
import com.fluency03.blockchain.core.Block

import scala.collection.mutable

object BlockActor {
object BlocksActor {
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]
def props: Props = Props[BlocksActor]
}

class BlockActor extends Actor with ActorLogging {
class BlocksActor 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)
val networkActor: ActorSelection = context.actorSelection(PARENT_UP + NETWORK_ACTOR_NAME)
val txActor: ActorSelection = context.actorSelection(PARENT_UP + TRANS_ACTOR_NAME)

// TODO (Chang): not persistent
var blocks = mutable.Map.empty[String, Block]

def receive: Receive = {
Expand All @@ -33,7 +36,7 @@ class BlockActor extends Actor with ActorLogging {
case _ => unhandled _
}

private[this] def onGetBlocks(): Unit = sender() ! blocks.values.toList
private[this] def onGetBlocks(): Unit = sender() ! blocks.values.toSeq

private[this] def onCreateBlock(block: Block): Unit = {
blocks += (block.hash -> block)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.fluency03.blockchain.api.actors

import akka.actor.{Actor, ActorLogging, ActorRef, ActorSelection, Props}
import akka.pattern.{ask, pipe}
import akka.actor.{Actor, ActorLogging, ActorSelection, Props}
import com.fluency03.blockchain.api.actors.NetworkActor._
import com.fluency03.blockchain.api.actors.PeerActor.GetPublicKeys
import com.fluency03.blockchain.api.utils.GenericMessage.Response
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, BLOCK_ACTOR_NAME, PARENT_UP}
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, BLOCKS_ACTOR_NAME, PARENT_UP, TRANS_ACTOR_NAME}

import scala.collection.mutable
import scala.concurrent.Future

object NetworkActor {
final case object GetPeers
Expand All @@ -17,9 +19,17 @@ object NetworkActor {
}

class NetworkActor extends Actor with ActorLogging {
import context.dispatcher

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)
val blockchainActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKCHAIN_ACTOR_NAME)
val txActor: ActorSelection = context.actorSelection(PARENT_UP + TRANS_ACTOR_NAME)

// TODO (Chang): children actors are not persistent

def receive: Receive = {
case GetPeers => context.children.map(_.path.name).toList
case CreatePeer(id) =>
Expand All @@ -29,7 +39,8 @@ class NetworkActor extends Actor with ActorLogging {
sender() ! Response(s"Peer $id created.")
}
case GetPeer(id) =>
sender() ! context.child(id).isDefined
if (context.child(id).isDefined) (context.child(id).get ? GetPublicKeys).mapTo[Seq[String]] pipeTo sender()
else sender() ! None
case DeletePeer(id) =>
if (context.child(id).isDefined) {
context stop context.child(id).get
Expand Down
20 changes: 12 additions & 8 deletions src/main/scala/com/fluency03/blockchain/api/actors/PeerActor.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.fluency03.blockchain.api.actors
package com.fluency03.blockchain
package api.actors

import java.security.{KeyPair, PrivateKey, PublicKey}
import java.security.{KeyPair, PublicKey}

import akka.actor.{Actor, ActorLogging, Props}
import com.fluency03.blockchain.api.actors.PeerActor._

import scala.collection.mutable

object PeerActor {


final case object GetPublicKeys
def props: Props = Props[PeerActor]
}

Expand All @@ -18,13 +19,16 @@ class PeerActor extends Actor with ActorLogging {

// TODO (Chang): not persistent
val wallets = mutable.Map.empty[String, KeyPair]
val publicKeys = mutable.Map.empty[String, PublicKey]
wallets += {
val pair: KeyPair = Crypto.generateKeyPair()
(pair.getPublic.getEncoded.toHex, pair)
}
val peerPublicKeys = mutable.Map.empty[String, PublicKey]

def receive: Receive = {
case _ => ???
// case _ => unhandled _
case GetPublicKeys => sender() ! wallets.values.map(_.getPublic.getEncoded.toHex).toSeq
case _ => unhandled _
}



}
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
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.actors.TransactionsActor._
import com.fluency03.blockchain.api.utils.GenericMessage.Response
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, BLOCK_ACTOR_NAME, PARENT_UP}
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, NETWORK_ACTOR_NAME, BLOCKS_ACTOR_NAME, PARENT_UP}
import com.fluency03.blockchain.core.{Outpoint, Transaction, TxOut}

import scala.collection.mutable

object TransactionActor {
object TransactionsActor {
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]
def props: Props = Props[TransactionsActor]
}

class TransactionActor extends Actor with ActorLogging {
class TransactionsActor extends Actor with ActorLogging {
override def preStart(): Unit = log.info("{} started!", this.getClass.getSimpleName)
override def postStop(): Unit = log.info("{} stopped!", this.getClass.getSimpleName)

// TODO (Chang): not persistent
val currentTransactions: mutable.Map[String, Transaction] = mutable.Map.empty[String, Transaction]
val unspentTxOuts: mutable.Map[Outpoint, TxOut] = mutable.Map.empty[Outpoint, TxOut]

// TODO (Chang): not persistent
val blockchainActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKCHAIN_ACTOR_NAME)
val blockActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCK_ACTOR_NAME)
val blockActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCKS_ACTOR_NAME)
val networkActor: ActorSelection = context.actorSelection(PARENT_UP + NETWORK_ACTOR_NAME)

def receive: Receive = {
case GetTransactions => onGetTransactions()
Expand All @@ -36,7 +37,7 @@ class TransactionActor extends Actor with ActorLogging {
case _ => unhandled _
}

private def onGetTransactions(): Unit = sender() ! currentTransactions.values.toList
private def onGetTransactions(): Unit = sender() ! currentTransactions.values.toSeq

private def onCreateTransaction(tx: Transaction): Unit ={
currentTransactions += (tx.id -> tx)
Expand All @@ -51,5 +52,4 @@ class TransactionActor extends Actor with ActorLogging {
sender() ! Response(s"Transaction $hash deleted.")
} else sender() ! Response(s"Blockchain does not exist.")


}
15 changes: 8 additions & 7 deletions src/main/scala/com/fluency03/blockchain/api/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import com.fluency03.blockchain.core.{Block, Transaction}

package object api {

type Blocks = List[Block]
type Transactions = List[Transaction]
type Blocks = Seq[Block]
type Transactions = Seq[Transaction]

lazy val BLOCK_ACTOR_NAME = "blockActor"
lazy val BLOCKCHAIN_ACTOR_NAME = "blockchainActor"
lazy val TX_ACTOR_NAME = "txActor"
lazy val PEER_ACTOR_NAME = "peerActor"
val BLOCKS_ACTOR_NAME = "blocksActor"
val BLOCKCHAIN_ACTOR_NAME = "blockchainActor"
val NETWORK_ACTOR_NAME = "networkActor"
val PEER_ACTOR_NAME = "peerActor"
val TRANS_ACTOR_NAME = "transActor"

lazy val PARENT_UP = "../"
val PARENT_UP = "../"

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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.Blocks
import com.fluency03.blockchain.api.actors.BlockActor._
import com.fluency03.blockchain.api.actors.BlocksActor._
import com.fluency03.blockchain.api.utils.GenericMessage.Response
import com.fluency03.blockchain.core.Block

Expand All @@ -19,20 +19,20 @@ import scala.concurrent.Future
trait BlockRoutes extends Routes {
lazy val log = Logging(system, classOf[BlockRoutes])

def blockActor: ActorRef
def blocksActor: ActorRef

lazy val blockRoutes: Route =
path("blocks") {
get {
val blocks: Future[Blocks] = (blockActor ? GetBlocks).mapTo[Blocks]
val blocks: Future[Blocks] = (blocksActor ? GetBlocks).mapTo[Blocks]
complete(blocks)
}
} ~
pathPrefix("block") {
pathEnd {
post {
entity(as[Block]) { block =>
val blockCreated: Future[Response] = (blockActor ? CreateBlock(block)).mapTo[Response]
val blockCreated: Future[Response] = (blocksActor ? CreateBlock(block)).mapTo[Response]
onSuccess(blockCreated) { resp =>
log.info("Created block [{}]: {}", block.hash, resp.message)
complete((StatusCodes.Created, resp))
Expand All @@ -42,11 +42,11 @@ trait BlockRoutes extends Routes {
} ~
path(Segment) { hash =>
get {
val maybeBlock: Future[Option[Block]] = (blockActor ? GetBlock(hash)).mapTo[Option[Block]]
val maybeBlock: Future[Option[Block]] = (blocksActor ? GetBlock(hash)).mapTo[Option[Block]]
rejectEmptyResponse { complete(maybeBlock) }
} ~
delete {
val blockDeleted: Future[Response] = (blockActor ? DeleteBlock(hash)).mapTo[Response]
val blockDeleted: Future[Response] = (blocksActor ? DeleteBlock(hash)).mapTo[Response]
onSuccess(blockDeleted) { resp =>
log.info("Deleted block [{}]: {}", hash, resp.message)
complete((StatusCodes.OK, resp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,26 @@ import com.fluency03.blockchain.api.utils.GenericMessage.Input
trait GenericRoutes extends Routes {
lazy val log = Logging(system, classOf[GenericRoutes])



lazy val genericRoutes: Route =
pathSingleSlash {
get {
complete("Welcome to Blockchain in Scala!")
}
} ~
pathPrefix("generic") {
path("hash-of-string") {
path("toSha256") {
post {
entity(as[Input]) { in => complete((StatusCodes.Created, in.data.toSha256)) }
}
} ~
path("base64-of-string") {
path("toBase64") {
post {
entity(as[Input]) { in => complete((StatusCodes.Created, in.data.toBase64)) }
}
} ~
path("string-of-base64") {
path("fromBase64") {
post {
entity(as[Input]) { in => complete((StatusCodes.Created, fromBase64(in.data))) }
}
Expand All @@ -42,7 +44,7 @@ trait GenericRoutes extends Routes {
entity(as[Input]) { in => complete((StatusCodes.Created, epochTimeOf(in.data))) }
}
} ~
path("time-from-epoch") {
path("time-of-epoch") {
post {
entity(as[Input]) { in => complete((StatusCodes.Created, Instant.ofEpochSecond(in.data.toLong))) }
}
Expand Down
Loading

0 comments on commit aa78511

Please sign in to comment.