Skip to content

Commit 90c141e

Browse files
committedFeb 9, 2017
Day 4 挖矿机制
1 parent 08cbfc7 commit 90c141e

File tree

5 files changed

+261
-8
lines changed

5 files changed

+261
-8
lines changed
 

‎src/main/kotlin/mbc/core/Block.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ import org.joda.time.DateTime
99
class Block(val height: Long, val parentHash: ByteArray, val minerAddress: String, val transactions: List<Transaction>,
1010
val time: DateTime) {
1111

12+
val version: Int = 1
13+
14+
val merkleRoot: ByteArray
15+
get() = CryptoUtil.merkleRoot(transactions)
16+
17+
var difficulty: Int = 0
18+
19+
var nonce: Int = 0
20+
1221
/**
1322
* 区块(Block)的哈希值(KECCAK-256)
1423
*/
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package mbc.miner
2+
3+
import mbc.core.Block
4+
import mbc.util.CryptoUtil
5+
import org.spongycastle.util.encoders.Hex
6+
import java.math.BigInteger
7+
import java.nio.ByteBuffer
8+
9+
10+
/**
11+
* 挖矿的管理类,算法可以参照https://en.bitcoin.it/wiki/Block_hashing_algorithm。
12+
*/
13+
object BlockMiner {
14+
var currentDifficulty = 0x1f00ffff // 比特币的最小(初始)难度为0x1d00ffff,为测试方便我们降低难度为0x1f00ffff
15+
16+
/**
17+
* 挖矿,返回nonce值和target值。目前采用阻塞模型,后期修改为更合理的异步模型。
18+
*/
19+
fun mine(block: Block): MineResult {
20+
val ver = block.version
21+
val parentHash = block.parentHash
22+
val merkleRoot = block.merkleRoot
23+
val time = (block.time.millis/1000).toInt() // Current timestamp as seconds since 1970-01-01T00:00 UTC
24+
val difficulty = currentDifficulty // difficulty
25+
26+
// 挖矿难度的算法:https://en.bitcoin.it/wiki/Difficulty
27+
val exp = difficulty shr 24
28+
val mant = difficulty and 0xffffff
29+
val target = BigInteger.valueOf(mant.toLong()).multiply(BigInteger.valueOf(2).pow(8 * (exp - 3)))
30+
val targetStr = "%064x".format(target)
31+
32+
var nonce = 0
33+
while (nonce < 0x100000000) {
34+
35+
val headerBuffer = ByteBuffer.allocate(4 + 32 + 32 + 4 + 4 + 4)
36+
headerBuffer.put(ByteBuffer.allocate(4).putInt(ver).array()) // version
37+
headerBuffer.put(parentHash) // parentHash
38+
headerBuffer.put(merkleRoot) // merkleRoot
39+
headerBuffer.put(ByteBuffer.allocate(4).putInt(time).array()) // time
40+
headerBuffer.put(ByteBuffer.allocate(4).putInt(difficulty).array()) // difficulty(current difficulty)
41+
headerBuffer.put(ByteBuffer.allocate(4).putInt(nonce).array()) // nonce
42+
43+
val header = headerBuffer.array()
44+
val hit = Hex.toHexString(CryptoUtil.sha256(CryptoUtil.sha256(header)))
45+
46+
if (hit < targetStr) {
47+
break
48+
}
49+
nonce += 1
50+
}
51+
52+
val result = MineResult(currentDifficulty, nonce)
53+
return result
54+
}
55+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package mbc.miner
2+
3+
/**
4+
* 挖矿的结果数据。https://en.bitcoin.it/wiki/Block_hashing_algorithm
5+
*/
6+
data class MineResult(val target: Int, val nonce: Int)

‎src/main/kotlin/mbc/util/CryptoUtil.kt

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import java.nio.ByteBuffer
88
import java.security.*
99
import java.security.Security.insertProviderAt
1010
import java.security.spec.ECGenParameterSpec
11+
import java.util.*
1112

1213
/**
1314
* 密码学工具类。
@@ -77,21 +78,73 @@ class CryptoUtil {
7778
* 序列化交易(Transaction)。当前实现非常简单,后期会改成以太坊的RLP协议。
7879
*/
7980
private fun encodeTransaction(
80-
trx: Transaction) = (trx.senderAddress + trx.receiverAddress + trx.amount.toString() + trx.time.millis.toString()).toByteArray()
81+
trx: Transaction): ByteArray {
82+
val byteBuffer = ByteBuffer.allocate(1024)
83+
byteBuffer.put(Hex.decode(trx.senderAddress))
84+
byteBuffer.put(Hex.decode(trx.receiverAddress))
85+
byteBuffer.put(ByteBuffer.allocate(8).putLong(trx.amount).array())
86+
byteBuffer.put(ByteBuffer.allocate(4).putInt((trx.time.millis / 1000).toInt()).array())
87+
88+
val result = ByteArray(byteBuffer.remaining())
89+
byteBuffer.get(result, 0, result.size)
90+
return result
91+
}
8192

8293
/**
8394
* 序列化区块(Block)。当前实现非常简单,后期会改成以太坊的RLP协议。
8495
*/
8596
private fun encodeBlock(block: Block): ByteArray {
8697
val byteBuffer = ByteBuffer.allocate(1024)
87-
byteBuffer.put(block.minerAddress.toByteArray())
88-
byteBuffer.put(block.time.millis.toString().toByteArray())
89-
block.transactions.map { byteBuffer.put(encodeTransaction(it)) }
98+
byteBuffer.put(ByteBuffer.allocate(4).putInt(block.version).array()) // version
99+
byteBuffer.put(Hex.decode(block.parentHash)) // parentHash
100+
byteBuffer.put(Hex.decode(block.merkleRoot)) // merkleRoot
101+
byteBuffer.put(ByteBuffer.allocate(4).putInt((block.time.millis / 1000).toInt()).array()) // time
102+
byteBuffer.put(ByteBuffer.allocate(4).putInt(block.difficulty).array()) // bits(current difficulty)
103+
byteBuffer.put(ByteBuffer.allocate(4).putInt(block.nonce).array()) // nonce
104+
105+
byteBuffer.put(ByteBuffer.allocate(4).putInt(block.transactions.size).array()) // transaction count
106+
107+
block.transactions.map { byteBuffer.put(encodeTransaction(it)) } // transactions
90108

91109
byteBuffer.flip()
92110
return byteBuffer.array()
93111
}
94112

113+
/**
114+
* 计算Merkle Root Hash
115+
*/
116+
fun merkleRoot(transactions: List<Transaction>): ByteArray {
117+
val count = transactions.size
118+
if (count == 0) {
119+
return ByteArray(0)
120+
} else if (count == 1) {
121+
return sha256(sha256(
122+
encodeTransaction(transactions[0]) + encodeTransaction(transactions[0]))) // Double hash if we are leaf.
123+
} else if (count == 2) {
124+
return sha256(sha256(
125+
encodeTransaction(transactions[0]) + encodeTransaction(transactions[1]))) // Double hash if we are leaf.
126+
} else {
127+
if (count.mod(2) == 1) {
128+
val newList = transactions.toMutableList();
129+
newList.add(transactions.last())
130+
return merkleRoot(newList)
131+
} else {
132+
return merkleRoot(transactions)
133+
}
134+
}
135+
}
136+
137+
/**
138+
* SHA-256预算
139+
*/
140+
fun sha256(msg: ByteArray): ByteArray {
141+
val digest = MessageDigest.getInstance("SHA-256", "SC")
142+
digest.update(msg)
143+
val hash = digest.digest()
144+
145+
return hash
146+
}
147+
95148
}
96149

97150
}

‎src/test/kotlin/mbc/core/BlockChainTest.kt

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@ package mbc.core
22

33
import mbc.core.TransactionExecutor.addAmount
44
import mbc.core.TransactionExecutor.applyTrx
5+
import mbc.miner.BlockMiner
6+
import mbc.util.CryptoUtil
57
import mbc.util.CryptoUtil.Companion.generateKeyPair
6-
import mbc.util.CryptoUtil.Companion.signTransaction
8+
import mbc.util.CryptoUtil.Companion.sha256
79
import mbc.util.CryptoUtil.Companion.verifyTransactionSignature
810
import org.joda.time.DateTime
911
import org.junit.Before
1012
import org.junit.Test
1113
import org.spongycastle.jce.provider.BouncyCastleProvider
14+
import org.spongycastle.util.encoders.Hex
1215
import java.math.BigInteger
13-
import java.security.spec.ECGenParameterSpec
16+
import java.nio.ByteBuffer
1417
import java.security.KeyPairGenerator
1518
import java.security.Security
16-
import java.security.Signature;
19+
import java.security.Signature
20+
import java.security.spec.ECGenParameterSpec
21+
import kotlin.test.assertEquals
22+
import kotlin.test.assertNotEquals
1723

1824

1925
class BlockChainTest {
@@ -141,7 +147,8 @@ class BlockChainTest {
141147
val charlie = Account(kp3.public)
142148

143149
// 构造原始区块(高度为0)
144-
val genesisBlock = Block(0, ByteArray(0), "1234567890123456789012345678901234567890", emptyList(), DateTime(2017,2,1,0,0))
150+
val genesisBlock = Block(0, ByteArray(0), "1234567890123456789012345678901234567890", emptyList(),
151+
DateTime(2017, 2, 1, 0, 0))
145152

146153
// 构造新的区块
147154
val blockChain = BlockChain(charlie.address)
@@ -152,4 +159,127 @@ class BlockChainTest {
152159
assert(bob.balance == 300L)
153160
}
154161

162+
/**
163+
* 挖矿算法测试。
164+
*/
165+
@Test fun mineAlgorithmTest() {
166+
val ver: Int = 1
167+
val parentHash = "000000000000000117c80378b8da0e33559b5997f2ad55e2f7d18ec1975b9717"
168+
val merkleRoot = "871714dcbae6c8193a2bb9b2a69fe1c0440399f38d94b3a0f1b447275a29978a"
169+
val time = 0x53058b35 // 2014-02-20 04:57:25
170+
val difficulty = 0x1f00ffff // difficulty,比特币的最小(初始)难度为0x1d00ffff,为测试方便我们降低难度为0x1f00ffff
171+
172+
// 挖矿难度的算法:https://en.bitcoin.it/wiki/Difficulty
173+
val exp = difficulty shr 24
174+
val mant = difficulty and 0xffffff
175+
val target = BigInteger.valueOf(mant.toLong()).multiply(BigInteger.valueOf(2).pow(8 * (exp - 3)))
176+
val targetStr = "%064x".format(target)
177+
println("Target:$targetStr")
178+
179+
var nonce = 0
180+
while (nonce < 0x100000000) {
181+
182+
val headerBuffer = ByteBuffer.allocate(4+32+32+4+4+4)
183+
headerBuffer.put(ByteBuffer.allocate(4).putInt(ver).array()) // version
184+
headerBuffer.put(Hex.decode(parentHash)) // parentHash
185+
headerBuffer.put(Hex.decode(merkleRoot)) // merkleRoot
186+
headerBuffer.put(ByteBuffer.allocate(4).putInt(time).array()) // time
187+
headerBuffer.put(ByteBuffer.allocate(4).putInt(difficulty).array()) // difficulty(current difficulty)
188+
headerBuffer.put(ByteBuffer.allocate(4).putInt(nonce).array()) // nonce
189+
190+
val header = headerBuffer.array()
191+
val hit = Hex.toHexString(sha256(sha256(header)))
192+
println("$nonce : $hit")
193+
194+
if (hit < targetStr) {
195+
println("Got Nonce : $nonce")
196+
println("Got Hit : $hit")
197+
break
198+
}
199+
nonce += 1
200+
}
201+
}
202+
203+
/**
204+
* 挖矿难度(Difficulty)运算测试。
205+
*/
206+
@Test fun difficultyTest() {
207+
val difficulty = BigInteger.valueOf(0x0404cbL).multiply(BigInteger.valueOf(2).pow(8 * (0x1b - 3)))
208+
assertEquals(difficulty.toString(16), "404cb000000000000000000000000000000000000000000000000")
209+
}
210+
211+
/**
212+
* Merkle Root Hash测试。
213+
*/
214+
@Test fun merkleTest() {
215+
// 初始化Alice账户
216+
val kp1 = generateKeyPair() ?: return
217+
val alice = Account(kp1.public)
218+
219+
// 初始化Bob账户
220+
val kp2 = generateKeyPair() ?: return
221+
val bob = Account(kp2.public)
222+
223+
// Alice向Bob转账100
224+
val trx1 = Transaction(alice.address, bob.address, 100, DateTime(), kp1.public)
225+
226+
// Alice用私钥签名
227+
val signature = trx1.sign(kp1.private)
228+
229+
// Alice向Bob转账50
230+
val trx2 = Transaction(alice.address, bob.address, 50, DateTime(), kp1.public)
231+
232+
// Alice用私钥签名
233+
val signature2 = trx2.sign(kp1.private)
234+
235+
val merkleRoot = CryptoUtil.merkleRoot(listOf(trx1, trx2))
236+
println(Hex.toHexString(merkleRoot))
237+
}
238+
239+
/**
240+
* 挖矿
241+
*/
242+
@Test fun mineBlockTest() {
243+
// 初始化Alice账户
244+
val kp1 = generateKeyPair() ?: return
245+
val alice = Account(kp1.public)
246+
247+
// 初始化Bob账户
248+
val kp2 = generateKeyPair() ?: return
249+
val bob = Account(kp2.public)
250+
251+
// 初始金额为200
252+
addAmount(alice.address, 200)
253+
addAmount(bob.address, 200)
254+
255+
// Alice向Bob转账100
256+
val trx = Transaction(alice.address, bob.address, 100, DateTime(), kp1.public)
257+
// Alice用私钥签名
258+
trx.sign(kp1.private)
259+
260+
// 初始化矿工Charlie账户
261+
val kp3 = generateKeyPair() ?: return
262+
val charlie = Account(kp3.public)
263+
264+
// 构造原始区块(高度为0)
265+
val genesisBlock = Block(0, ByteArray(0), "1234567890123456789012345678901234567890", emptyList(),
266+
DateTime(2017, 2, 1, 0, 0))
267+
268+
// 构造新的区块
269+
val blockChain = BlockChain(charlie.address)
270+
val block = blockChain.createNewBlock(genesisBlock, listOf(trx))
271+
272+
// 查询余额是否正确
273+
assert(alice.balance == 100L)
274+
assert(bob.balance == 300L)
275+
276+
val mineResult = BlockMiner.mine(block)
277+
block.difficulty = mineResult.target
278+
block.nonce = mineResult.nonce
279+
280+
println("Block nonce: ${block.nonce}")
281+
assertNotEquals(block.difficulty, 0)
282+
assertNotEquals(block.nonce, 0)
283+
}
284+
155285
}

0 commit comments

Comments
 (0)
Please sign in to comment.