diff --git a/src/main/scala/com/github/fluency03/blockchain/api/Server.scala b/src/main/scala/com/github/fluency03/blockchain/api/Server.scala index fbcf61d..677af7b 100644 --- a/src/main/scala/com/github/fluency03/blockchain/api/Server.scala +++ b/src/main/scala/com/github/fluency03/blockchain/api/Server.scala @@ -14,8 +14,14 @@ import com.typesafe.config.ConfigFactory import scala.concurrent.{Await, ExecutionContextExecutor, Future} import scala.concurrent.duration.Duration -object Server extends App - with BlockchainRoutes with BlockPoolRoutes with TxPoolRoutes with NetworkRoutes with GenericRoutes { +object Server + extends App + with BlockchainRoutes + with BlockPoolRoutes + with TxPoolRoutes + with NetworkRoutes + with GenericRoutes { + // we leave these abstract, since they will be provided by the App implicit val system: ActorSystem = ActorSystem("blockchain-http-service") implicit val materializer: ActorMaterializer = ActorMaterializer() @@ -32,10 +38,10 @@ object Server extends App val txPoolActor: ActorRef = system.actorOf(TxPoolActor.props, TX_POOL_ACTOR_NAME) val networkActor: ActorRef = system.actorOf(NetworkActor.props, NETWORK_ACTOR_NAME) - lazy val routes: Route = blockchainRoutes ~ blockPoolRoutes ~ txPoolRoutes ~ networkRoutes ~ genericRoutes + lazy val routes: Route = + blockchainRoutes ~ blockPoolRoutes ~ txPoolRoutes ~ networkRoutes ~ genericRoutes - val bindingFuture: Future[ServerBinding] = - Http().bindAndHandle(routes, host, port) + val bindingFuture: Future[ServerBinding] = Http().bindAndHandle(routes, host, port) bindingFuture.failed.foreach { ex => log.error(ex, "Failed to bind to {}:{}!", host, port) diff --git a/src/main/scala/com/github/fluency03/blockchain/core/Block.scala b/src/main/scala/com/github/fluency03/blockchain/core/Block.scala index 8169a29..db201ca 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/Block.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/Block.scala @@ -3,7 +3,8 @@ package core import com.github.fluency03.blockchain.core.Block.allTransValidOf import com.github.fluency03.blockchain.core.BlockHeader.hashOfHeaderFields -import com.github.fluency03.blockchain.core.Transaction.{createCoinbaseTx, validateCoinbaseTx, noDuplicateTxInOf} +import com.github.fluency03.blockchain.core.Transaction.{ + createCoinbaseTx, noDuplicateTxInOf, validateCoinbaseTx} import org.json4s.native.JsonMethods.{compact, render} import org.json4s.{Extraction, JValue} @@ -34,13 +35,15 @@ case class Block(header: BlockHeader, transactions: Seq[Transaction], hash: Stri def removeTransaction(tx: Transaction): Block = Block(index, previousHash, data, timestamp, difficulty, nonce, transactions.filter(_ != tx)) - def hasValidHash: Boolean = hasValidHeaderHash && isWithValidDifficulty(hash, difficulty) && hasValidMerkleHash + def hasValidHash: Boolean = + hasValidHeaderHash && isWithValidDifficulty(hash, difficulty) && hasValidMerkleHash def hasValidMerkleHash: Boolean = merkleHash == Merkle.computeRoot(transactions) def hasValidHeaderHash: Boolean = hash == header.hash - def allTransAreValid(uTxOs: mutable.Map[Outpoint, TxOut]): Boolean = allTransValidOf(transactions, index, uTxOs) + def allTransAreValid(uTxOs: mutable.Map[Outpoint, TxOut]): Boolean = + allTransValidOf(transactions, index, uTxOs) def noDuplicateTxIn(): Boolean = noDuplicateTxInOf(transactions) @@ -71,9 +74,12 @@ object Block { timestamp: Long, difficulty: Int, nonce: Int, - transactions: Seq[Transaction]): Block = - Block(BlockHeader(index, previousHash, data, Merkle.computeRoot(transactions), timestamp, difficulty, nonce), - transactions) + transactions: Seq[Transaction]): Block = { + val merkleRoot = Merkle.computeRoot(transactions) + val header = BlockHeader(index, previousHash, data, merkleRoot, timestamp, difficulty, nonce) + Block(header, transactions) + } + def apply( index: Int, @@ -83,13 +89,19 @@ object Block { timestamp: Long, difficulty: Int, nonce: Int, - transactions: Seq[Transaction]): Block = - Block(BlockHeader(index, previousHash, data, merkleHash, timestamp, difficulty, nonce), transactions) + transactions: Seq[Transaction]): Block = { + val header = BlockHeader(index, previousHash, data, merkleHash, timestamp, difficulty, nonce) + Block(header, transactions) + } lazy val genesisBlock: Block = genesis() def genesis(difficulty: Int = 4): Block = mineNextBlock( - 0, ZERO64, "Welcome to Blockchain in Scala!", genesisTimestamp, difficulty, + 0, + ZERO64, + "Welcome to Blockchain in Scala!", + genesisTimestamp, + difficulty, Seq(createCoinbaseTx(0, genesisMiner, genesisTimestamp))) // TODO (Chang): implement parallel version @@ -104,9 +116,12 @@ object Block { var nextHash = "" val merkleHash = Merkle.computeRoot(transactions) + val hashOnNonce: Int => String = + hashOfHeaderFields(nextIndex, prevHash, newBlockData, merkleHash, timestamp, difficulty, _) + while (!isWithValidDifficulty(nextHash, difficulty)) { nonce += 1 - nextHash = hashOfHeaderFields(nextIndex, prevHash, newBlockData, merkleHash, timestamp, difficulty, nonce) + nextHash = hashOnNonce(nonce) } Block(nextIndex, prevHash, newBlockData, merkleHash, timestamp, difficulty, nonce, transactions) @@ -118,15 +133,16 @@ object Block { timestamp: Long, difficulty: Int, transactions: Seq[Transaction]): Block = - mineNextBlock(currentBlock.index + 1, currentBlock.hash, newBlockData, timestamp, difficulty, transactions) + mineNextBlock(currentBlock.index + 1, currentBlock.hash, newBlockData, timestamp, difficulty, + transactions) def mineNextBlock( - currentBlock: Block, - newBlockData: String, - timestamp: Long, - difficulty: Int, - transactions: Seq[Transaction], - miner: String): Block = + currentBlock: Block, + newBlockData: String, + timestamp: Long, + difficulty: Int, + transactions: Seq[Transaction], + miner: String): Block = mineNextBlock(currentBlock.index + 1, currentBlock.hash, newBlockData, timestamp, difficulty, transactions :+ createCoinbaseTx(currentBlock.index + 1, miner, timestamp)) @@ -134,19 +150,21 @@ object Block { * Check whether transactions of a Block are valid: * 1. Coinbase transaction is valid * 2. Rest of the transactions are valid - * 3. if the Seq is empty, then it is not valid, because it has to at least contain one coinbase transaction + * 3. if the Seq is empty, then it is not valid, because it has to at least contain one + * coinbase transaction */ def allTransValidOf( transactions: Seq[Transaction], blockIndex: Int, - uTxOs: mutable.Map[Outpoint, TxOut]) - : Boolean = transactions match { + uTxOs: mutable.Map[Outpoint, TxOut]): Boolean = transactions match { case Nil => false - case init :+ last => validateCoinbaseTx(last, blockIndex) && init.forall(tx => tx.isValid(uTxOs)) + case init :+ last => + validateCoinbaseTx(last, blockIndex) && init.forall(tx => tx.isValid(uTxOs)) } /** - * Check whether a new Block can be chained to the last Block of a Blockchain (previousBlock of newBlock): + * Check whether a new Block can be chained to the last Block of a Blockchain + * (previousBlock of newBlock): * 1. New Block's index should be the previous index plus one * 2. New Block's previousHash should be the hash of previous Block * 3. New Block should have valid hash (that means, valid header hash and merkle hash) diff --git a/src/main/scala/com/github/fluency03/blockchain/core/BlockHeader.scala b/src/main/scala/com/github/fluency03/blockchain/core/BlockHeader.scala index 1a9dcdc..d66946e 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/BlockHeader.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/BlockHeader.scala @@ -28,7 +28,8 @@ case class BlockHeader( lazy val hash: String = hashOfBlockHeader(this) - def nextTrial(): BlockHeader = BlockHeader(index, previousHash, data, merkleHash, timestamp, difficulty, nonce + 1) + def nextTrial(): BlockHeader = + BlockHeader(index, previousHash, data, merkleHash, timestamp, difficulty, nonce + 1) def toJson: JValue = Extraction.decompose(this) @@ -56,6 +57,13 @@ object BlockHeader { timestamp: Long, difficulty: Int, nonce: Int): String = - SHA256.hashAll(index.toString, previousHash, data, merkleHash, timestamp.toString, difficulty.toString, nonce.toString) + SHA256.hashAll( + index.toString, + previousHash, + data, + merkleHash, + timestamp.toString, + difficulty.toString, + nonce.toString) } diff --git a/src/main/scala/com/github/fluency03/blockchain/core/Blockchain.scala b/src/main/scala/com/github/fluency03/blockchain/core/Blockchain.scala index 76472da..fe5e7fc 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/Blockchain.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/Blockchain.scala @@ -2,7 +2,7 @@ package com.github.fluency03.blockchain package core import com.github.fluency03.blockchain.core.Block.validLinkBetween -import com.github.fluency03.blockchain.core.Blockchain._ +import com.github.fluency03.blockchain.core.Blockchain.isValidChain import org.json4s.native.JsonMethods.{compact, render} import org.json4s.{Extraction, JValue} @@ -25,13 +25,13 @@ case class Blockchain(difficulty: Int = 4, chain: Seq[Block] = Seq(Block.genesis def lastBlock(): Option[Block] = chain.headOption - def mineNextBlock(newBlockData: String, transactions: Seq[Transaction]): Block = { - val lastBlockOpt: Option[Block] = this.lastBlock() - if (lastBlockOpt.isEmpty) throw new NoSuchElementException("Last Block does not exist!") - val lastHeader = lastBlockOpt.get.header - Block.mineNextBlock(lastHeader.index + 1, lastHeader.hash, newBlockData, getCurrentTimestamp, difficulty, - transactions) - } + def mineNextBlock(newBlockData: String, transactions: Seq[Transaction]): Block = + this.lastBlock() match { + case Some(block) => + Block.mineNextBlock( + block.index + 1, block.hash, newBlockData, getCurrentTimestamp, difficulty, transactions) + case None => throw new NoSuchElementException("Last Block does not exist!") + } def isValid: Boolean = chain match { case Nil => throw new NoSuchElementException("Blockchain is Empty!") @@ -48,7 +48,8 @@ case class Blockchain(difficulty: Int = 4, chain: Seq[Block] = Seq(Block.genesis object Blockchain { - def apply(difficulty: Int): Blockchain = new Blockchain(difficulty, Seq(Block.genesis(difficulty))) + def apply(difficulty: Int): Blockchain = + new Blockchain(difficulty, Seq(Block.genesis(difficulty))) // TODO (Chang): return both the validity and highest valid Block index @tailrec diff --git a/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala b/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala index 1aad887..ab7a3c9 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala @@ -25,11 +25,14 @@ object Merkle { def hashViaMerklePath(init: String, path: Seq[String], index: Int): String = if (path.isEmpty) init else { - val newHash = if (index % 2 == 0) SHA256.hashAll(init, path.head) else SHA256.hashAll(path.head, init) + val newHash = if (index % 2 == 0) + SHA256.hashAll(init, path.head) + else + SHA256.hashAll(path.head, init) hashViaMerklePath(newHash, path.tail, index/2) } - def simplifiedPaymentVerification(init: String, root: String, path: Seq[String], index: Int): Boolean = + def verifySimplified(init: String, root: String, path: Seq[String], index: Int): Boolean = hashViaMerklePath(init, path, index) == root diff --git a/src/main/scala/com/github/fluency03/blockchain/core/Transaction.scala b/src/main/scala/com/github/fluency03/blockchain/core/Transaction.scala index 1dfddd7..f01d9a3 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/Transaction.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/Transaction.scala @@ -28,7 +28,8 @@ case class Transaction(txIns: Seq[TxIn], txOuts: Seq[TxOut], timestamp: Long, id def removeTxIn(txIn: TxIn): Transaction = Transaction(txIns.filter(_ != txIn), txOuts, timestamp) - def removeTxOut(txOut: TxOut): Transaction = Transaction(txIns, txOuts.filter(_ != txOut), timestamp) + def removeTxOut(txOut: TxOut): Transaction = + Transaction(txIns, txOuts.filter(_ != txOut), timestamp) def hasValidId: Boolean = id == hashOfTransaction(this) @@ -69,20 +70,28 @@ object Transaction { tx.txOuts.map(tx => tx.address + tx.amount).mkString, tx.timestamp.toString) - def hashOfTransaction(txIns: Seq[TxIn], txOuts: Seq[TxOut], timestamp: Long): String = SHA256.hashAll( - txIns.map(tx => tx.previousOut.id + tx.previousOut.index).mkString, - txOuts.map(tx => tx.address + tx.amount).mkString, - timestamp.toString) + def hashOfTransaction(txIns: Seq[TxIn], txOuts: Seq[TxOut], timestamp: Long): String = + SHA256.hashAll( + txIns.map(tx => tx.previousOut.id + tx.previousOut.index).mkString, + txOuts.map(tx => tx.address + tx.amount).mkString, + timestamp.toString) // sign TxIn - def signTxIn(txId: String, txIn: TxIn, keyPair: KeyPair, uTxOs: mutable.Map[Outpoint, TxOut]): Option[TxIn] = + def signTxIn( + txId: String, + txIn: TxIn, + keyPair: KeyPair, + uTxOs: mutable.Map[Outpoint, TxOut]): Option[TxIn] = signTxIn(txId.hex2Bytes, txIn, keyPair, uTxOs) - def signTxIn(txId: Bytes, txIn: TxIn, keyPair: KeyPair, uTxOs: mutable.Map[Outpoint, TxOut]): Option[TxIn] = - uTxOs.get(txIn.previousOut) match { - case Some(uTxO) => signTxIn(txId, txIn, keyPair, uTxO) - case None => None - } + def signTxIn( + txId: Bytes, + txIn: TxIn, + keyPair: KeyPair, + uTxOs: mutable.Map[Outpoint, TxOut]): Option[TxIn] = uTxOs.get(txIn.previousOut) match { + case Some(uTxO) => signTxIn(txId, txIn, keyPair, uTxO) + case None => None + } def signTxIn(txId: Bytes, txIn: TxIn, keyPair: KeyPair, uTxO: TxOut): Option[TxIn] = if (keyPair.getPublic.toHex != uTxO.address) None @@ -105,7 +114,10 @@ object Transaction { def validateTxOutValues(transaction: Transaction, uTxOs: mutable.Map[Outpoint, TxOut]): Boolean = validateTxOutValues(transaction.txIns, transaction.txOuts, uTxOs) - def validateTxOutValues(txIns: Seq[TxIn], txOuts: Seq[TxOut], uTxOs: mutable.Map[Outpoint, TxOut]): Boolean = + def validateTxOutValues( + txIns: Seq[TxIn], + txOuts: Seq[TxOut], + uTxOs: mutable.Map[Outpoint, TxOut]): Boolean = txIns.map(txIn => uTxOs.get(txIn.previousOut) match { case Some(txOut) => txOut.amount case None => 0 @@ -125,7 +137,9 @@ object Transaction { * 1. Remove all consumed unspent transaction outputs * 2. Append all new unspent transaction outputs */ - def updateUTxOs(transactions: Seq[Transaction], uTxOs: Map[Outpoint, TxOut]): Map[Outpoint, TxOut] = { + def updateUTxOs( + transactions: Seq[Transaction], + uTxOs: Map[Outpoint, TxOut]): Map[Outpoint, TxOut] = { val consumedTxOuts = getConsumedUTxOs(transactions) uTxOs.filterNot { case (i, _) => consumedTxOuts.contains(i) @@ -134,15 +148,15 @@ object Transaction { def getNewUTxOs(transactions: Seq[Transaction]): Map[Outpoint, TxOut] = transactions - .map(t => t.txOuts.zipWithIndex.map { + .map { t => t.txOuts.zipWithIndex.map { case (txOut, index) => Outpoint(t.id, index) -> txOut - }.toMap) + }.toMap } .foldLeft(Map.empty[Outpoint, TxOut])(_ ++ _) def getConsumedUTxOs(transactions: Seq[Transaction]): Map[Outpoint, TxOut] = transactions.map(_.txIns) .foldLeft(Seq.empty[TxIn])(_ ++ _) - .map(txIn => Outpoint(txIn.previousOut.id, txIn.previousOut.index) -> TxOut("", 0)) + .map { txIn => Outpoint(txIn.previousOut.id, txIn.previousOut.index) -> TxOut("", 0) } .toMap def noDuplicateTxInOf(transactions: Seq[Transaction]): Boolean = { diff --git a/src/main/scala/com/github/fluency03/blockchain/crypto/Base58.scala b/src/main/scala/com/github/fluency03/blockchain/crypto/Base58.scala index 345b93f..afee4ab 100644 --- a/src/main/scala/com/github/fluency03/blockchain/crypto/Base58.scala +++ b/src/main/scala/com/github/fluency03/blockchain/crypto/Base58.scala @@ -46,7 +46,8 @@ object Base58 { def decodeToHex(str: String): String = new String(decode(str)) - def checkEncode(bytes: Bytes): String = encode(bytes ++ bytes.toSha256Digest.toSha256Digest.slice(0, 4)) + def checkEncode(bytes: Bytes): String = + encode(bytes ++ bytes.toSha256Digest.toSha256Digest.slice(0, 4)) } \ No newline at end of file diff --git a/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala b/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala index 3fdc6f0..9910b07 100644 --- a/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala +++ b/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala @@ -49,16 +49,16 @@ object Secp256k1 { } def generatePublicKey(privateKey: PrivateKey): PublicKey = - KeyFactory.getInstance(KEY_ALGORITHM) - .generatePublic(new ECPublicKeySpec(ecSpec.getG.multiply(privateKey.asInstanceOf[ECPrivateKey].getD), ecSpec)) + KeyFactory.getInstance(KEY_ALGORITHM).generatePublic( + new ECPublicKeySpec(ecSpec.getG.multiply(privateKey.asInstanceOf[ECPrivateKey].getD), ecSpec)) def recoverPublicKey(hex: String): PublicKey = - KeyFactory.getInstance(KEY_ALGORITHM) - .generatePublic(new ECPublicKeySpec(ecSpec.getCurve.decodePoint(hex.hex2Bytes), ecSpec)) + KeyFactory.getInstance(KEY_ALGORITHM).generatePublic( + new ECPublicKeySpec(ecSpec.getCurve.decodePoint(hex.hex2Bytes), ecSpec)) def recoverPrivateKey(hex: String): PrivateKey = - KeyFactory.getInstance(KEY_ALGORITHM) - .generatePrivate(new ECPrivateKeySpec(new BigInteger(hex, 16), ecSpec)) + KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate( + new ECPrivateKeySpec(new BigInteger(hex, 16), ecSpec)) def publicKeyToHex(publicKey: PublicKey): String = publicKeyToBytes(publicKey).toHex diff --git a/src/main/scala/com/github/fluency03/blockchain/package.scala b/src/main/scala/com/github/fluency03/blockchain/package.scala index 7c03938..5e40551 100644 --- a/src/main/scala/com/github/fluency03/blockchain/package.scala +++ b/src/main/scala/com/github/fluency03/blockchain/package.scala @@ -98,7 +98,8 @@ package object blockchain { /** * Check whether the given hash is with valid difficulty. */ - def isWithValidDifficulty(hash: String, difficulty: Int): Boolean = hash startsWith ("0" * difficulty) + def isWithValidDifficulty(hash: String, difficulty: Int): Boolean = + hash.startsWith("0" * difficulty) /** * Encode a String to Base64.