From aa7851145af7592d27bb95c8fda9a4c507a22cb5 Mon Sep 17 00:00:00 2001 From: fluency03 Date: Tue, 24 Apr 2018 21:34:08 +0100 Subject: [PATCH] add network actor --- README.md | 2 ++ .../scala/com/fluency03/blockchain/Util.scala | 4 +-- .../com/fluency03/blockchain/api/Server.scala | 10 +++--- .../api/actors/BlockchainActor.scala | 12 ++++--- .../{BlockActor.scala => BlocksActor.scala} | 17 ++++++---- .../blockchain/api/actors/NetworkActor.scala | 19 ++++++++--- .../blockchain/api/actors/PeerActor.scala | 20 +++++++----- ...ionActor.scala => TransactionsActor.scala} | 18 +++++------ .../fluency03/blockchain/api/package.scala | 15 +++++---- .../blockchain/api/routes/BlockRoutes.scala | 12 +++---- .../blockchain/api/routes/GenericRoutes.scala | 10 +++--- .../blockchain/api/routes/NetworkRoutes.scala | 26 +++++++++++++++ .../api/routes/TransactionRoutes.scala | 14 ++++---- .../blockchain/core/Blockchain.scala | 23 ++++++------- .../blockchain/core/Transaction.scala | 32 +++++++------------ .../com/fluency03/blockchain/CryptoTest.scala | 1 - .../blockchain/core/BlockchainTest.scala | 2 +- .../blockchain/core/TransactionTest.scala | 14 ++++---- 18 files changed, 143 insertions(+), 108 deletions(-) rename src/main/scala/com/fluency03/blockchain/api/actors/{BlockActor.scala => BlocksActor.scala} (77%) rename src/main/scala/com/fluency03/blockchain/api/actors/{TransactionActor.scala => TransactionsActor.scala} (81%) create mode 100644 src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala diff --git a/README.md b/README.md index 13e8064..744813e 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/src/main/scala/com/fluency03/blockchain/Util.scala b/src/main/scala/com/fluency03/blockchain/Util.scala index 67e57d2..8c02304 100644 --- a/src/main/scala/com/fluency03/blockchain/Util.scala +++ b/src/main/scala/com/fluency03/blockchain/Util.scala @@ -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 @@ -60,6 +60,4 @@ object Util { */ def fromBase64(base64: String): String = new String(Base64.decode(base64), "UTF-8") - - } diff --git a/src/main/scala/com/fluency03/blockchain/api/Server.scala b/src/main/scala/com/fluency03/blockchain/api/Server.scala index b1cc688..0201cc3 100644 --- a/src/main/scala/com/fluency03/blockchain/api/Server.scala +++ b/src/main/scala/com/fluency03/blockchain/api/Server.scala @@ -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 @@ -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) 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 51b83cb..759ec93 100644 --- a/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala +++ b/src/main/scala/com/fluency03/blockchain/api/actors/BlockchainActor.scala @@ -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 @@ -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() diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/BlockActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/BlocksActor.scala similarity index 77% rename from src/main/scala/com/fluency03/blockchain/api/actors/BlockActor.scala rename to src/main/scala/com/fluency03/blockchain/api/actors/BlocksActor.scala index 9444502..024a4bd 100644 --- a/src/main/scala/com/fluency03/blockchain/api/actors/BlockActor.scala +++ b/src/main/scala/com/fluency03/blockchain/api/actors/BlocksActor.scala @@ -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 = { @@ -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) diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/NetworkActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/NetworkActor.scala index 9c2c9ad..536287d 100644 --- a/src/main/scala/com/fluency03/blockchain/api/actors/NetworkActor.scala +++ b/src/main/scala/com/fluency03/blockchain/api/actors/NetworkActor.scala @@ -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 @@ -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) => @@ -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 diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/PeerActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/PeerActor.scala index 6edad51..bb6be47 100644 --- a/src/main/scala/com/fluency03/blockchain/api/actors/PeerActor.scala +++ b/src/main/scala/com/fluency03/blockchain/api/actors/PeerActor.scala @@ -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] } @@ -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 _ } - } diff --git a/src/main/scala/com/fluency03/blockchain/api/actors/TransactionActor.scala b/src/main/scala/com/fluency03/blockchain/api/actors/TransactionsActor.scala similarity index 81% rename from src/main/scala/com/fluency03/blockchain/api/actors/TransactionActor.scala rename to src/main/scala/com/fluency03/blockchain/api/actors/TransactionsActor.scala index 8c1b8a0..4c6297a 100644 --- a/src/main/scala/com/fluency03/blockchain/api/actors/TransactionActor.scala +++ b/src/main/scala/com/fluency03/blockchain/api/actors/TransactionsActor.scala @@ -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() @@ -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) @@ -51,5 +52,4 @@ class TransactionActor extends Actor with ActorLogging { sender() ! Response(s"Transaction $hash deleted.") } else sender() ! Response(s"Blockchain does not exist.") - } diff --git a/src/main/scala/com/fluency03/blockchain/api/package.scala b/src/main/scala/com/fluency03/blockchain/api/package.scala index ee6ad63..df8a026 100644 --- a/src/main/scala/com/fluency03/blockchain/api/package.scala +++ b/src/main/scala/com/fluency03/blockchain/api/package.scala @@ -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 = "../" } 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 4ca8406..a96b85c 100644 --- a/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala +++ b/src/main/scala/com/fluency03/blockchain/api/routes/BlockRoutes.scala @@ -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 @@ -19,12 +19,12 @@ 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) } } ~ @@ -32,7 +32,7 @@ trait BlockRoutes extends Routes { 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)) @@ -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)) diff --git a/src/main/scala/com/fluency03/blockchain/api/routes/GenericRoutes.scala b/src/main/scala/com/fluency03/blockchain/api/routes/GenericRoutes.scala index 12836e9..ffe24c1 100644 --- a/src/main/scala/com/fluency03/blockchain/api/routes/GenericRoutes.scala +++ b/src/main/scala/com/fluency03/blockchain/api/routes/GenericRoutes.scala @@ -15,6 +15,8 @@ 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 { @@ -22,17 +24,17 @@ trait GenericRoutes extends Routes { } } ~ 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))) } } @@ -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))) } } diff --git a/src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala b/src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala new file mode 100644 index 0000000..daeccbe --- /dev/null +++ b/src/main/scala/com/fluency03/blockchain/api/routes/NetworkRoutes.scala @@ -0,0 +1,26 @@ +package com.fluency03.blockchain.api.routes + +import akka.actor.ActorRef +import akka.event.Logging +import akka.http.scaladsl.model.StatusCodes +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.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.BlocksActor._ +import com.fluency03.blockchain.api.utils.GenericMessage.Response +import com.fluency03.blockchain.core.Block + +import scala.concurrent.Future + +trait NetworkRoutes extends Routes { + lazy val log = Logging(system, classOf[NetworkRoutes]) + + def networkActor: ActorRef + + lazy val networkRoutes: Route = ??? + +} 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 c95baf5..8d5227f 100644 --- a/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala +++ b/src/main/scala/com/fluency03/blockchain/api/routes/TransactionRoutes.scala @@ -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.Transactions -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.core.Transaction @@ -19,12 +19,12 @@ import scala.concurrent.Future trait TransactionRoutes extends Routes { lazy val log = Logging(system, classOf[TransactionRoutes]) - def txActor: ActorRef + def transActor: ActorRef - lazy val txRoutes: Route = + lazy val transRoutes: Route = path("transactions") { get { - val transactions: Future[Transactions] = (txActor ? GetTransactions).mapTo[Transactions] + val transactions: Future[Transactions] = (transActor ? GetTransactions).mapTo[Transactions] complete(transactions) } } ~ @@ -32,7 +32,7 @@ trait TransactionRoutes extends Routes { pathEnd { post { entity(as[Transaction]) { tx => - val txCreated: Future[Response] = (txActor ? CreateTransaction(tx)).mapTo[Response] + val txCreated: Future[Response] = (transActor ? CreateTransaction(tx)).mapTo[Response] onSuccess(txCreated) { resp => log.info("Created transaction [{}]: {}", tx.id, resp.message) complete((StatusCodes.Created, resp)) @@ -42,11 +42,11 @@ trait TransactionRoutes extends Routes { } ~ path(Segment) { id => get { - val maybeTx: Future[Option[Transaction]] = (txActor ? GetTransaction(id)).mapTo[Option[Transaction]] + val maybeTx: Future[Option[Transaction]] = (transActor ? GetTransaction(id)).mapTo[Option[Transaction]] rejectEmptyResponse { complete(maybeTx) } } ~ delete { - val txDeleted: Future[Response] = (txActor ? DeleteTransaction(id)).mapTo[Response] + val txDeleted: Future[Response] = (transActor ? DeleteTransaction(id)).mapTo[Response] onSuccess(txDeleted) { resp => log.info("Deleted transaction [{}]: {}", id, resp.message) complete((StatusCodes.OK, resp)) diff --git a/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala b/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala index dd0c9b5..1aad3bf 100644 --- a/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala +++ b/src/main/scala/com/fluency03/blockchain/core/Blockchain.scala @@ -14,13 +14,11 @@ import scala.collection.mutable */ case class Blockchain(difficulty: Int = 4, chain: Seq[Block] = Seq(Block.genesisBlock)) { - def addBlock(newBlockData: String, transactions: Seq[Transaction]): Blockchain = { + def addBlock(newBlockData: String, transactions: Seq[Transaction]): Blockchain = Blockchain(difficulty, mineNextBlock(newBlockData, transactions) +: chain) - } - def addBlock(newBlock: Block): Blockchain = { + def addBlock(newBlock: Block): Blockchain = Blockchain(difficulty, newBlock +: chain) - } def lastBlock(): Option[Block] = chain.headOption @@ -50,16 +48,13 @@ object Blockchain { case a +: b +: tail => canBeChained(a, b) && a.isValid && isValidChain(b +: tail) } - def updateUTxOs( - transactions: Seq[Transaction], - unspentTxOuts: Map[Outpoint, TxOut] - ): Map[Outpoint, TxOut] = { - val newUnspentTxOuts = getNewUTxOs(transactions) - val consumedTxOuts = getConsumedUTxOs(transactions) - unspentTxOuts.filterNot { - case (i, _) => consumedTxOuts.contains(i) - } ++ newUnspentTxOuts - } + def updateUTxOs(transactions: Seq[Transaction], unspentTxOuts: Map[Outpoint, TxOut]): Map[Outpoint, TxOut] = { + val newUnspentTxOuts = getNewUTxOs(transactions) + val consumedTxOuts = getConsumedUTxOs(transactions) + unspentTxOuts.filterNot { + case (i, _) => consumedTxOuts.contains(i) + } ++ newUnspentTxOuts + } def getNewUTxOs(transactions: Seq[Transaction]): Map[Outpoint, TxOut] = transactions diff --git a/src/main/scala/com/fluency03/blockchain/core/Transaction.scala b/src/main/scala/com/fluency03/blockchain/core/Transaction.scala index 2014cbc..97f63f3 100644 --- a/src/main/scala/com/fluency03/blockchain/core/Transaction.scala +++ b/src/main/scala/com/fluency03/blockchain/core/Transaction.scala @@ -54,26 +54,16 @@ object Transaction { sha256Of(tx.txIns.map(tx => tx.previousOut.id + tx.previousOut.index).mkString, tx.txOuts.map(tx => tx.address + tx.amount).mkString, tx.timestamp.toString) - def signTxIn( - txId: String, - txIn: TxIn, - keyPair: KeyPair, - unspentTxOuts: mutable.Map[Outpoint, TxOut] - ): Option[TxIn] = signTxIn( - txId.hex2Bytes, txIn, keyPair, unspentTxOuts - ) - - def signTxIn( - txId: Bytes, - txIn: TxIn, - keyPair: KeyPair, - unspentTxOuts: mutable.Map[Outpoint, TxOut] - ): Option[TxIn] = unspentTxOuts.get(txIn.previousOut) match { - case Some(uTxO) => - if (keyPair.getPublic.getEncoded.toHex != uTxO.address) None - else Some(TxIn(txIn.previousOut, Crypto.sign(txId, keyPair.getPrivate.getEncoded).toHex)) - case None => None - } + def signTxIn(txId: String, txIn: TxIn, keyPair: KeyPair, unspentTxOuts: mutable.Map[Outpoint, TxOut]): Option[TxIn] = + signTxIn(txId.hex2Bytes, txIn, keyPair, unspentTxOuts) + + def signTxIn(txId: Bytes, txIn: TxIn, keyPair: KeyPair, unspentTxOuts: mutable.Map[Outpoint, TxOut]): Option[TxIn] = + unspentTxOuts.get(txIn.previousOut) match { + case Some(uTxO) => + if (keyPair.getPublic.getEncoded.toHex != uTxO.address) None + else Some(TxIn(txIn.previousOut, Crypto.sign(txId, keyPair.getPrivate.getEncoded).toHex)) + case None => None + } def validateTxIn(txIn: TxIn, txId: String, unspentTxOuts: mutable.Map[Outpoint, TxOut]): Boolean = validateTxIn(txIn, txId.hex2Bytes, unspentTxOuts) @@ -100,7 +90,7 @@ object Transaction { } def validateTransaction(transaction: Transaction, unspentTxOuts: mutable.Map[Outpoint, TxOut]): Boolean = - transaction.txIns.forall(txIn => validateTxIn(txIn, transaction.id.hex2Bytes, unspentTxOuts)) && + transaction.txIns.forall(txIn => validateTxIn(txIn, transaction.id, unspentTxOuts)) && validateTxOutValues(transaction, unspentTxOuts) diff --git a/src/test/scala/com/fluency03/blockchain/CryptoTest.scala b/src/test/scala/com/fluency03/blockchain/CryptoTest.scala index 9b95713..678e0db 100644 --- a/src/test/scala/com/fluency03/blockchain/CryptoTest.scala +++ b/src/test/scala/com/fluency03/blockchain/CryptoTest.scala @@ -2,7 +2,6 @@ package com.fluency03.blockchain import java.security.KeyPair -import org.bouncycastle.util.encoders.Hex import org.scalatest.{FlatSpec, Matchers} class CryptoTest extends FlatSpec with Matchers { diff --git a/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala b/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala index 3eff4e5..6006769 100644 --- a/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala +++ b/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala @@ -1,5 +1,6 @@ package com.fluency03.blockchain package core + import com.fluency03.blockchain.core.Blockchain.updateUTxOs import com.fluency03.blockchain.core.Transaction.createCoinbaseTx import org.json4s.JValue @@ -7,7 +8,6 @@ import org.json4s.native.JsonMethods.parse import org.scalatest.{FlatSpec, Matchers} import scala.collection.mutable -import scala.collection.mutable.ListBuffer import scala.io.Source class BlockchainTest extends FlatSpec with Matchers { diff --git a/src/test/scala/com/fluency03/blockchain/core/TransactionTest.scala b/src/test/scala/com/fluency03/blockchain/core/TransactionTest.scala index 97b98e0..0068d98 100644 --- a/src/test/scala/com/fluency03/blockchain/core/TransactionTest.scala +++ b/src/test/scala/com/fluency03/blockchain/core/TransactionTest.scala @@ -95,10 +95,10 @@ class TransactionTest extends FlatSpec with Matchers { "Transaction" should "be able to be signed by key pair." in { val txIn = TxIn(Outpoint("def0", 0), "abc") val pair: KeyPair = Crypto.generateKeyPair() - val hash = "ace0".hex2Bytes + val hash = "ace0" - val signature = Crypto.sign(hash, pair.getPrivate.getEncoded) - Crypto.verify(hash, pair.getPublic.getEncoded, signature) shouldEqual true + val signature = Crypto.sign(hash.hex2Bytes, pair.getPrivate.getEncoded) + Crypto.verify(hash.hex2Bytes, pair.getPublic.getEncoded, signature) shouldEqual true val unspentTxOuts: mutable.Map[Outpoint, TxOut] = mutable.Map.empty[Outpoint, TxOut] val signedTxIn0 = signTxIn(hash, txIn, pair, unspentTxOuts) @@ -111,15 +111,15 @@ class TransactionTest extends FlatSpec with Matchers { signedTxIn shouldEqual Some(TxIn(Outpoint("def0", 0), signedTxIn.get.signature)) signedTxIn.get.previousOut shouldEqual Outpoint("def0", 0) - Crypto.verify(hash, pair.getPublic.getEncoded, signedTxIn.get.signature.hex2Bytes) shouldEqual true + Crypto.verify(hash.hex2Bytes, pair.getPublic.getEncoded, signedTxIn.get.signature.hex2Bytes) shouldEqual true } "Transaction" should "have valid TxIns." in { val pair: KeyPair = Crypto.generateKeyPair() - val hash = "ace0".hex2Bytes + val hash = "ace0" - val signature = Crypto.sign(hash, pair.getPrivate.getEncoded) - Crypto.verify(hash, pair.getPublic.getEncoded, signature) shouldEqual true + val signature = Crypto.sign(hash.hex2Bytes, pair.getPrivate.getEncoded) + Crypto.verify(hash.hex2Bytes, pair.getPublic.getEncoded, signature) shouldEqual true val txIn = TxIn(Outpoint("def0", 0), "abc1") val unspentTxOuts: mutable.Map[Outpoint, TxOut] = mutable.Map.empty[Outpoint, TxOut]