Skip to content

Commit

Permalink
rewrite Transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
fluency03 committed Apr 21, 2018
1 parent 0cb4720 commit ce1188d
Show file tree
Hide file tree
Showing 36 changed files with 611 additions and 391 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

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.
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)

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).

4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ val testDependencies = Seq(
libraryDependencies ++= {
Seq(
"org.scalaz" %% "scalaz-core" % scalazVersion,
"org.json4s" %% "json4s-native" % json4sVersion
"org.json4s" %% "json4s-native" % json4sVersion,
"org.json4s" %% "json4s-jackson" % json4sVersion,
"org.bouncycastle" % "bcprov-jdk15on" % "1.59"
)
} ++ httpDependencies ++ testDependencies
19 changes: 1 addition & 18 deletions src/main/resources/genesis-block.json
Original file line number Diff line number Diff line change
@@ -1,18 +1 @@
{
"header": {
"index": 0,
"previousHash": "0000000000000000000000000000000000000000000000000000000000000000",
"data": "Welcome to Blockchain in Scala!",
"merkleHash": "7814a9c43e9015462e5ffec1a3a9a69be024c1aacfa3ec4c879b5cd544761e7e",
"timestamp": 1523472721,
"nonce": 13860,
"hash": "00003607219f7a455e216f19ac3a34e3b158cf7282f7fdc624c93d593c2fc61f"
},
"transactions": [
{
"sender": "0000000000000000000000000000000000000000000000000000000000000000",
"receiver": "0000000000000000000000000000000000000000000000000000000000000000",
"amount": 50.0
}
]
}
{"header":{"index":0,"previousHash":"0000000000000000000000000000000000000000000000000000000000000000","data":"Welcome to Blockchain in Scala!","merkleHash":"e580ae290899b8acc333fdb4e5ce52b6161c0df8f433a96cfcacbc4c211f1dc6","timestamp":1523472721,"difficulty":4,"nonce":289612},"transactions":[{"txIns":[{"previousOut":{"id":"","index":0},"signature":""}],"txOuts":[{"address":"3056301006072a8648ce3d020106052b8104000a034200049671ad288b396bdadf9d2d85640c6c61e14fa4a837b7b335bba21f226ba1525974c1a3f70fa1bc5a55c48ceced51468fe29bbbf67b22afa40383f99b98b841f9","amount":50}],"timestamp":1523472721,"id":"e580ae290899b8acc333fdb4e5ce52b6161c0df8f433a96cfcacbc4c211f1dc6"}],"hash":"0000d691591d3f999dce60c54719bc38b5bc2fb3ac39f76d9343b5ad4c01548b"}
1 change: 1 addition & 0 deletions src/main/resources/private-key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
30818d020100301006072a8648ce3d020106052b8104000a04763074020101042005ab8a244673c5f9d86c64c28a0440368896a35daac9ec08a909de920c3dbc3aa00706052b8104000aa144034200049671ad288b396bdadf9d2d85640c6c61e14fa4a837b7b335bba21f226ba1525974c1a3f70fa1bc5a55c48ceced51468fe29bbbf67b22afa40383f99b98b841f9
1 change: 1 addition & 0 deletions src/main/resources/public-key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3056301006072a8648ce3d020106052b8104000a034200049671ad288b396bdadf9d2d85640c6c61e14fa4a837b7b335bba21f226ba1525974c1a3f70fa1bc5a55c48ceced51468fe29bbbf67b22afa40383f99b98b841f9
1 change: 1 addition & 0 deletions src/main/resources/signature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3045022100a15dff564d1e0fd4e475956d6fe1c8fc00fb86cb3c38729b47386011a8e9b38d02200c575841124500d4a1b250dec4fa6dfa48388cec54649b4f168767aae3062d65
48 changes: 48 additions & 0 deletions src/main/scala/com/fluency03/blockchain/Crypto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.fluency03.blockchain

import java.security._
import java.security.spec.{PKCS8EncodedKeySpec, X509EncodedKeySpec}

import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECParameterSpec

object Crypto {

Security.addProvider(new BouncyCastleProvider)

val SPECP256K1 = "secp256k1"
val KEY_ALGORITHM = "ECDSA"
val KEY_PROVIDER = "BC"

val ecSpec: ECParameterSpec = ECNamedCurveTable.getParameterSpec(SPECP256K1)

def sign(data: Array[Byte], privateKey: Array[Byte]): Array[Byte] = {
val keySpec: PKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey)
val keyFactory: KeyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
val key: PrivateKey = keyFactory.generatePrivate(keySpec)

val sig: Signature = Signature.getInstance(KEY_ALGORITHM, KEY_PROVIDER)
sig.initSign(key, new SecureRandom)
sig.update(data)
sig.sign()
}

def verify(data: Array[Byte], publicKey: Array[Byte], signature: Array[Byte]): Boolean = {
val keySpec: X509EncodedKeySpec = new X509EncodedKeySpec(publicKey)
val keyFactory: KeyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
val key: PublicKey = keyFactory.generatePublic(keySpec)

val sig: Signature = Signature.getInstance(KEY_ALGORITHM, KEY_PROVIDER)
sig.initVerify(key)
sig.update(data)
sig.verify(signature)
}

def generateKeyPair(): KeyPair = {
val gen: KeyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM, KEY_PROVIDER)
gen.initialize(ecSpec, new SecureRandom)
gen.generateKeyPair()
}

}
22 changes: 17 additions & 5 deletions src/main/scala/com/fluency03/blockchain/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import java.util.Base64

object Util {



/**
* Generate SHA256 Hash from a input String.
* https://gist.github.com/navicore/6234040bbfce3aa58f866db314c07c15
Expand All @@ -28,7 +26,12 @@ object Util {
def getCurrentTimestamp: Long = Instant.now.getEpochSecond

/**
* Calculate the hash of concatenation a List of Strings.
*
*/
def epochTimeOf(t: String): Long = Instant.parse(t).getEpochSecond

/**
* Calculate the hash of concatenation a Seq of Strings.
*/
def hashOf(strings: String*): String = hashOf(strings mkString "")

Expand All @@ -37,6 +40,11 @@ object Util {
*/
def hashOf(str: String): String = sha256HashOf(str)

/**
* Get binary representation of a hash.
*/
def binaryOfHash(hash: String): String = BigInt(hash, 16).toString(2)

/**
* Check whether the given hash is with valid difficulty.
*/
Expand All @@ -45,12 +53,16 @@ object Util {
/**
* Encode a String to Base64.
*/
def toBase64(text: String): String = Base64.getEncoder.encodeToString(text.getBytes())
def base64Of(text: String): String = Base64.getEncoder.encodeToString(text.getBytes("UTF-8"))

/**
* Decode a Base64 to String.
*/
def fromBase64(base64: String): String = new String(Base64.getDecoder.decode(base64), "ASCII")
def fromBase64(base64: String): String = new String(Base64.getDecoder.decode(base64), "UTF-8")






}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class BlockActor extends Actor with ActorLogging {
case CreateBlock(block) => onCreateBlock(block)
case GetBlock(hash) => onGetBlock(hash)
case DeleteBlock(hash) => onDeleteBlock(hash)
case _ => unhandled _
}

private[this] def onGetBlocks(): Unit = sender() ! blocks.values.toList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,57 +22,29 @@ class BlockchainActor extends Actor with ActorLogging {
val txActor: ActorSelection = context.actorSelection(PARENT_UP + TX_ACTOR_NAME)
val blockActor: ActorSelection = context.actorSelection(PARENT_UP + BLOCK_ACTOR_NAME)

// TODO (Chang): not persistent
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)
case _ => unhandled _
}

/* ---------- Blockchain actions ---------- */
private[this] def onGetBlockchain(): Unit = sender() ! blockchainOpt
private def onGetBlockchain(): Unit = sender() ! blockchainOpt

private[this] def onCreateBlockchain(): Unit =
private 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 =
private 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.")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.fluency03.blockchain.api.actors

import akka.actor.{Actor, ActorLogging, ActorRef, ActorSelection, Props}
import com.fluency03.blockchain.api.actors.NetworkActor._
import com.fluency03.blockchain.api.utils.GenericMessage.Response
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, BLOCK_ACTOR_NAME, PARENT_UP}

import scala.collection.mutable

object NetworkActor {
final case object GetPeers
final case class CreatePeer(id: String)
final case class GetPeer(id: String)
final case class DeletePeer(id: String)

def props: Props = Props[NetworkActor]
}

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

def receive: Receive = {
case GetPeers => context.children.map(_.path.name).toList
case CreatePeer(id) =>
if (context.child(id).isDefined) sender() ! Response(s"Peer $id has been created.")
else {
val _ = context.actorOf(Props[PeerActor], name = id)
sender() ! Response(s"Peer $id created.")
}
case GetPeer(id) =>
sender() ! context.child(id).isDefined
case DeletePeer(id) =>
if (context.child(id).isDefined) {
context stop context.child(id).get
sender() ! Response(s"Peer $id deleted.")
} else sender() ! Response(s"Peer $id does not exist.")
case _ => unhandled _
}

}
30 changes: 30 additions & 0 deletions src/main/scala/com/fluency03/blockchain/api/actors/PeerActor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.fluency03.blockchain.api.actors

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

import akka.actor.{Actor, ActorLogging, Props}

import scala.collection.mutable

object PeerActor {


def props: Props = Props[PeerActor]
}

class PeerActor 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 wallets = mutable.Map.empty[String, KeyPair]
val publicKeys = mutable.Map.empty[String, PublicKey]

def receive: Receive = {
case _ => ???
// case _ => unhandled _
}



}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
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.Response
import com.fluency03.blockchain.api.{BLOCKCHAIN_ACTOR_NAME, BLOCK_ACTOR_NAME, PARENT_UP}
import com.fluency03.blockchain.core.Transaction
import com.fluency03.blockchain.core.{Outpoint, Transaction, TxOut}

import scala.collection.mutable

object TransactionActor {
final case object GetTransactions
Expand All @@ -17,20 +21,35 @@ 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 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)

def receive: Receive = {
case _ => blockchainActor forward _
case GetTransactions => onGetTransactions()
case CreateTransaction(tx) => onCreateTransaction(tx)
case GetTransaction(hash) => onGetTransaction(hash)
case DeleteTransaction(hash) => onDeleteTransaction(hash)
case _ => unhandled _
}

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

private def onCreateTransaction(tx: Transaction): Unit ={
currentTransactions += (tx.id -> tx)
sender() ! Response(s"Transaction ${tx.id} created.")
}

//
// {
// case msg @ GetTransactions => blockchainActor forward msg
// case msg: CreateTransaction => blockchainActor forward msg
// case msg: GetTransaction => blockchainActor forward msg
// case msg: DeleteTransaction => blockchainActor forward msg
// }
private def onGetTransaction(hash: String): Unit = sender() ! currentTransactions.get(hash)

private def onDeleteTransaction(hash: String): Unit =
if (currentTransactions contains hash) {
currentTransactions -= hash
sender() ! Response(s"Transaction $hash deleted.")
} else sender() ! Response(s"Blockchain does not exist.")


}
1 change: 1 addition & 0 deletions src/main/scala/com/fluency03/blockchain/api/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package object api {
lazy val BLOCK_ACTOR_NAME = "blockActor"
lazy val BLOCKCHAIN_ACTOR_NAME = "blockchainActor"
lazy val TX_ACTOR_NAME = "txActor"
lazy val PEER_ACTOR_NAME = "peerActor"

lazy val PARENT_UP = "../"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ trait BlockRoutes extends Routes {
def blockActor: ActorRef

lazy val blockRoutes: Route =
pathPrefix("blocks") {
path("blocks") {
get {
val blocks: Future[Blocks] = (blockActor ? GetBlocks).mapTo[Blocks]
complete(blocks)
}
} ~
pathPrefix("block") {
pathEnd {
get {
val blocks: Future[Blocks] = (blockActor ? GetBlocks).mapTo[Blocks]
complete(blocks)
} ~
post {
entity(as[Block]) { block =>
val blockCreated: Future[Response] = (blockActor ? CreateBlock(block)).mapTo[Response]
Expand Down
Loading

0 comments on commit ce1188d

Please sign in to comment.