From b52f6e164b3db0254cf59f6e75ac5869081d2d73 Mon Sep 17 00:00:00 2001 From: fluency03 Date: Tue, 17 Apr 2018 00:35:16 +0200 Subject: [PATCH] add Difficulty --- README.md | 2 +- .../scala/com/fluency03/blockchain/Util.scala | 2 + .../blockchain/core/Difficulty.scala | 65 +++++++++++++++++++ .../fluency03/blockchain/core/package.scala | 9 ++- src/test/resources/application.conf | 4 ++ .../blockchain/core/BlockchainTest.scala | 6 ++ .../blockchain/core/DifficultyTest.scala | 42 ++++++++++++ 7 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/com/fluency03/blockchain/core/Difficulty.scala create mode 100644 src/test/resources/application.conf create mode 100644 src/test/scala/com/fluency03/blockchain/core/DifficultyTest.scala diff --git a/README.md b/README.md index ffe9854..55134f3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# blockchain-scala +# Blockchain in Scala [![Build Status](https://travis-ci.org/fluency03/blockchain-in-scala.svg?branch=master)](https://travis-ci.org/fluency03/blockchain-in-scala) [![Coverage Status](https://coveralls.io/repos/github/fluency03/blockchain-in-scala/badge.svg?branch=master)](https://coveralls.io/github/fluency03/blockchain-in-scala?branch=master) diff --git a/src/main/scala/com/fluency03/blockchain/Util.scala b/src/main/scala/com/fluency03/blockchain/Util.scala index 56e79d8..d230992 100644 --- a/src/main/scala/com/fluency03/blockchain/Util.scala +++ b/src/main/scala/com/fluency03/blockchain/Util.scala @@ -6,6 +6,8 @@ import java.util.Base64 object Util { + + /** * Generate SHA256 Hash from a input String. * https://gist.github.com/navicore/6234040bbfce3aa58f866db314c07c15 diff --git a/src/main/scala/com/fluency03/blockchain/core/Difficulty.scala b/src/main/scala/com/fluency03/blockchain/core/Difficulty.scala new file mode 100644 index 0000000..1abe0ed --- /dev/null +++ b/src/main/scala/com/fluency03/blockchain/core/Difficulty.scala @@ -0,0 +1,65 @@ +package com.fluency03.blockchain.core + +object Difficulty { + + lazy val difficultyOneTarget: BigInt = targetOfBits("1d00ffff".hex) + + /** + * https://github.com/bitcoin/bitcoin/blob/master/src/arith_uint256.cpp#L206 + */ + def decodeCompact(nCompact: Long): (BigInt, Boolean, Boolean) = { + val nSize = (nCompact >> 24).toInt + val (nWord, result) = if (nSize <= 3) { + val nWord1 = (nCompact & 0x007fffff) >> 8 * (3 - nSize) + (nWord1, BigInt(nWord1)) + } else { + val nWord1 = nCompact & 0x007fffff + (nWord1, BigInt(nWord1) << (8 * (nSize - 3))) + } + val fNegative = nWord != 0 && (nCompact & 0x00800000) != 0 + val fOverflow = nWord != 0 && ((nSize > 34) || (nWord > 0xff && nSize > 33) || (nWord > 0xffff && nSize > 32)) + (result, fNegative, fOverflow) + } + +// def encodeCompact(target: BigInt, fNegative: Boolean): Long = { +// val bitLength = target.bitLength +// var nSize = ((if (bitLength == 0) 0 else bitLength + 1) + 7) / 8 +// var nCompact: Long = if (nSize <= 3) { +// getLowBits(target, 64) << 8 * (3 - nSize) +// } else { +// val bn = target >> 8 * (nSize - 3) +// getLowBits(bn, 64) +// } +// // The 0x00800000 bit denotes the sign. +// // Thus, if it is already set, divide the mantissa by 256 and increase the exponent. +// if ((nCompact & 0x00800000) != 0) (nCompact >>= 8, nSize += 1) +// +// assert((nCompact & ~0x007fffff) == 0) +// assert(nSize < 256) +// nCompact |= nSize << 24 +// nCompact |= (if (fNegative && ((nCompact & 0x007fffff) != 0)) 0x00800000 else 0) +// nCompact +// } + + def getLowBits(x: BigInt, N: Int): Long = (x & ((1 << N) - 1)).toLong + + def targetOfBits(bitsInt: Long): BigInt = { + val (result, fNegative, _) = decodeCompact(bitsInt) + if (fNegative) -result else result + } + +// def bitsOfTarget(target: BigInt, fNegative: Boolean): Long = encodeCompact(target, fNegative) + + def padHexTarget(hex: String): String = hex.length match { + case len => "0" * (64 - len) + hex + } + + def hashLessThanTarget(hash: String, target: String): Boolean = ??? + + def difficultyOf(target: BigInt, negative: Boolean, overflow: Boolean): Double = { + if (target == 0 || negative || overflow) 0.0 + else (BigDecimal(difficultyOneTarget) / BigDecimal(target)).toDouble + } + + +} diff --git a/src/main/scala/com/fluency03/blockchain/core/package.scala b/src/main/scala/com/fluency03/blockchain/core/package.scala index 8d3c443..e8ace5e 100644 --- a/src/main/scala/com/fluency03/blockchain/core/package.scala +++ b/src/main/scala/com/fluency03/blockchain/core/package.scala @@ -6,9 +6,16 @@ import org.json4s.NoTypeHints import org.json4s.native.Serialization package object core { - implicit val formats = Serialization.formats(NoTypeHints) + lazy val ZERO64: String = "0000000000000000000000000000000000000000000000000000000000000000" + lazy val genesisTimestamp: Long = Instant.parse("2018-04-11T18:52:01Z").getEpochSecond + class HexString(val s: String) { + def hex: Long = java.lang.Long.parseLong(s, 16) + } + + implicit def str2Hex(str: String): HexString = new HexString(str) + } diff --git a/src/test/resources/application.conf b/src/test/resources/application.conf new file mode 100644 index 0000000..10255fe --- /dev/null +++ b/src/test/resources/application.conf @@ -0,0 +1,4 @@ +http { + host = "0.0.0.0" + port = 9090 +} \ No newline at end of file diff --git a/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala b/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala index 6716f69..6e23221 100644 --- a/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala +++ b/src/test/scala/com/fluency03/blockchain/core/BlockchainTest.scala @@ -24,6 +24,7 @@ class BlockchainTest extends FlatSpec with Matchers { blockchain.lastBlock().isEmpty shouldEqual false blockchain.lastBlock().get shouldEqual genesis blockchain.currentTransactions shouldEqual mutable.Map.empty[String, Transaction] + blockchain.isValid shouldEqual true } "A new Blockchain with different difficulty" should "have all default values but the difficulty." in { @@ -32,6 +33,7 @@ class BlockchainTest extends FlatSpec with Matchers { blockchainOf5.lastBlock().isEmpty shouldEqual false blockchainOf5.lastBlock().get shouldEqual genesisOf5 blockchainOf5.currentTransactions shouldEqual mutable.Map.empty[String, Transaction] + blockchainOf5.isValid shouldEqual true } "Add a Transaction to a Blockchain" should "add these Transactions to its currentTransactions collection." in { @@ -45,6 +47,7 @@ class BlockchainTest extends FlatSpec with Matchers { trans += (t2.hash -> t2) trans += (t3.hash -> t3) blockchain.currentTransactions shouldEqual trans + blockchain.isValid shouldEqual true } "Add a List of Transaction to a Blockchain" should "add these Transactions to its currentTransactions collection." in { @@ -54,6 +57,7 @@ class BlockchainTest extends FlatSpec with Matchers { trans += (t3.hash -> t3) trans += (t4.hash -> t4) blockchainOf5.currentTransactions shouldEqual trans + blockchainOf5.isValid shouldEqual true } "Blockchain" should "be able to mine the next Block." in { @@ -65,8 +69,10 @@ class BlockchainTest extends FlatSpec with Matchers { actual shouldEqual expected blockchainToAdd.lastBlock().get shouldEqual genesis + blockchainToAdd.isValid shouldEqual true val blockchainAdded = blockchainToAdd.addBlock(actual) blockchainAdded.lastBlock().get shouldEqual expected + blockchainAdded.isValid shouldEqual true } diff --git a/src/test/scala/com/fluency03/blockchain/core/DifficultyTest.scala b/src/test/scala/com/fluency03/blockchain/core/DifficultyTest.scala new file mode 100644 index 0000000..5b92f0c --- /dev/null +++ b/src/test/scala/com/fluency03/blockchain/core/DifficultyTest.scala @@ -0,0 +1,42 @@ +package com.fluency03.blockchain.core + +import com.fluency03.blockchain.core.Difficulty._ +import org.scalatest.{FlatSpec, Matchers} + +class DifficultyTest extends FlatSpec with Matchers { + + val (t1, n1, o1) = decodeCompact("1d00ffff".hex) + val (t2, n2, o2) = decodeCompact("453062093".toLong) + val (t3, n3, o3) = decodeCompact("-453062093".toLong) + + "Bits in different formats" should "have its corresponding targets." in { + difficultyOneTarget shouldEqual t1 + t1.toString(16) shouldEqual "ffff0000000000000000000000000000000000000000000000000000" + t2.toString(16) shouldEqual "12dcd000000000000000000000000000000000000000000000000" + targetOfBits("01003456".hex) shouldEqual "00".hex + targetOfBits("01123456".hex) shouldEqual "12".hex + targetOfBits("02008000".hex) shouldEqual "80".hex + targetOfBits("05009234".hex) shouldEqual "92340000".hex + targetOfBits("04923456".hex) shouldEqual - "12345600".hex + targetOfBits("04123456".hex) shouldEqual "12345600".hex +// println("1d00ffff".hex) +// println(bitsOfTarget(t1, n1)) +// println(n1, o1) +// println(bitsOfTarget(t2, n2)) +// println(n2, o2) +// println(bitsOfTarget(t3, n3)) +// println(n3, o3) + } + + "padHexTarget" should "add enough zeros (0) in front of a hex fot making it 64 bytes." in { + padHexTarget(t1.toString(16)) shouldEqual "00000000ffff0000000000000000000000000000000000000000000000000000" + padHexTarget(t2.toString(16)) shouldEqual "0000000000012dcd000000000000000000000000000000000000000000000000" + difficultyOf(t1, n1, o1) shouldEqual 1.0 + difficultyOf(t2, n2, o2) shouldEqual 55589.518126868665 +// targetOfBits(bitsOfTarget(t1, n1)) shouldEqual t1 +// targetOfBits(bitsOfTarget(t2, n2)) shouldEqual t2 + } + + + +}