BruceFan's Blog

Stay hungry, stay foolish

0%

用Python实现简单的区块链(一)

这篇文章用python实现一下区块链的基本概念,主要目的是凑热闹学习区块链相关知识,没有太大实际用途。

创建区块链

区块链就是许多区块的链表,区块链里的每个链表都有自己的签名,包含前一个区块的数字签名和一些数据(例如交易)。


每个区块有前一个区块的hash,也有自己的hash,自己的hash包含了自己的数据和前一个区块的hash。如果前一个区块的数据改变了,那么前一个区块的hash就会改变,后面的区块都会受到影响。计算和比较hash可以检查一个区块链是否合法。
改变链表里的任何数据,都会改变签名,破坏链。
首先创建组成区块链的Block类:
代码清单 block.py

1
2
3
4
5
6
7
8
9
import time

class Block:
def __init__(self, data, previousHash):
self.data = data
self.previousHash = previousHash
self.timeStamp = time.time()
self.nonce = 0
self.hash = self.calculateHash()

基本的Block包含了一个hash来保存数字签名,previousHash变量保存了前一个Block的hash,data保存该区块的数据。
下面需要一个方法来产生数字签名,这里使用sha256加密算法,调用Python的hashlib库,非常简单:
代码清单 strutils.py

1
2
3
4
5
6
7
8
import hashlib

class StringUtils:
@staticmethod
def sha256(msg):
sh = hashlib.sha256()
sh.update(msg)
return sh.hexdigest()

下面为Block类添加一个计算hash的方法:
代码清单 block.py

1
2
3
4
from strutils import StringUtils
...
def calculateHash(self):
return StringUtils.sha256(self.previousHash+self.data+str(self.nonce)+str(self.timeStamp))

创建几个区块打印hash测试一下是否能正确运行:
代码清单 noobchain.py

1
2
3
4
5
6
7
8
from block import Block

genesisBlock = Block("Hi im the first block", "0")
print "Hash for block 1: " + genesisBlock.hash
secondBlock = Block("Hi im the second block", genesisBlock.hash)
print "Hash for block 2: " + secondBlock.hash
thirdBlock = Block("Hi im the third block", secondBlock.hash)
print "Hash for block 3: " + thirdBlock.hash
1
2
3
4
$ python noobchain.py
Hash for block 1: 9dbe25df31f62ecdda90aac0ab7b25603fcdada1f890c910a7e8007560ef9689
Hash for block 2: bb2983a4837cdd83acdf398929babb592c8f9d4165b3eec07279d1bfcc49d199
Hash for block 3: 1e2b74ee552ae2a59b5215eaab87e896e11c33e94541917323d1f2e58f47fa87

现在每个区块都有自己的数字签名,基于自己的数据和前一个区块的签名。现在不太像一个区块链,所以我们用一个列表来存放这些区块。
代码清单 noobchain.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import json
blockchain = []

blockchain.append(Block('Hi im the first block', '0'))
blockchain.append(Block('Yo im the second block', blockchain[len(blockchain)-1].hash))
blockchain.append(Block('Hey im the third block', blockchain[len(blockchain)-1].hash))

blockdict = []
for b in blockchain:
blockdict.append(b.__dict__)

blockjson = json.dumps(blockdict, indent=4)
print blockjson

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ python noobchain.py
[
{
"nonce": 0,
"timeStamp": 1526824346.983302,
"data": "Hi im the first block",
"hash": "6120bc136ad7d77d462ad7142864739035e66548bdb26104a4716e3c29690e89",
"previousHash": "0"
},
{
"nonce": 0,
"timeStamp": 1526824346.983333,
"data": "Yo im the second block",
"hash": "b08a3eb3838c8897c55c107defcd758f094b3d4c0865c15820fcf93bd0fced96",
"previousHash": "6120bc136ad7d77d462ad7142864739035e66548bdb26104a4716e3c29690e89"
},
{
"nonce": 0,
"timeStamp": 1526824346.98334,
"data": "Hey im the third block",
"hash": "31e787cb6ad0cb3a92922e61a525f47e1023ed426b30b17dc2d6efc58e020733",
"previousHash": "b08a3eb3838c8897c55c107defcd758f094b3d4c0865c15820fcf93bd0fced96"
}
]

下面我们需要检查我们区块链的完整性,创建一个isChainValid()方法,这个方法循环整个链中的区块,比较hash,这个方法需要检查hash变量确实与计算的hash相等,前一个区块的hash与previousHash变量相等。
代码清单 noobchain.py

1
2
3
4
5
6
7
8
9
10
11
def isChainValid():
for i in range(1, len(blockchain)):
curblock = blockchain[i]
preblock = blockchain[i-1]
if curblock.hash != curblock.calculateHash():
print 'Current Hashes not equal'
return False
if preblock.hash != curblock.previousHash:
print 'Previous Hashes not equal'
return False
return True

任何对区块链中区块的改动都会让这个方法返回错误。
当人们将自己的区块链分享到比特币网络节点时,网络会接收其最长的有效链。如何阻止有人篡改一个旧区块中的数据,然后创建一整个新的更长的区块链到网络中。工作量证明——哈希现金工作量证明系统意味着它花费了可观的时间和计算量来创建新的区块。因此,攻击者会用到比其他人加起来还要多的计算量。

开始采矿

我们让矿工通过尝试区块中不同的变量,直到它的hash由一定数量的0开头来做工作量证明。
calculateHash()方法中加入nonce变量,还需要mineBlock()方法:
代码清单 block.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Block:
def __init__(self, data, previousHash):
self.data = data
self.previousHash = previousHash
self.timeStamp = time.time()
self.nonce = 0
self.hash = self.calculateHash()

def calculateHash(self):
return sha256(self.previousHash+self.data+str(self.nonce)+str(self.timeStamp))

def mineBlock(self, difficulty):
target = '0'*difficulty
while self.hash[0:difficulty] != target:
self.nonce += 1
self.hash = self.calculateHash()
print "Block Mined!!! : " + self.hash

mineBlock()方法需要一个difficulty参数,来指定他们需要产生0的个数。
代码清单 noobchain.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
difficulty = 5

blockchain.append(Block('Hi im the first block', '0'))
print 'Trying to mine block 1...'
blockchain[0].mineBlock(difficulty)

blockchain.append(Block('Yo im the second block', blockchain[len(blockchain)-1].hash))
print 'Trying to mine block 2...'
blockchain[1].mineBlock(difficulty)

blockchain.append(Block('Hey im the third block', blockchain[len(blockchain)-1].hash))
print 'Trying to mine block 3...'
blockchain[2].mineBlock(difficulty)

print 'Blockchain is Valid:' + str(isChainValid())

blockdict = []
for b in blockchain:
blockdict.append(b.__dict__)

blockjson = json.dumps(blockdict, indent=4)
print blockjson

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ python noobchain.py
Trying to mine block 1...
Block Mined!!! : 00000ecb9f53da95a26ee8316131bda984bc3afb037a8bdf29074004261b839b
Trying to mine block 2...
Block Mined!!! : 00000e576c0879015c408e6ec870a25745d7049588aef35fcc28c4f071524f8f
Trying to mine block 3...
Block Mined!!! : 00000137d52d6a04821f5aa3bc77dd0c51b768e415e8b44115b36b80f62b9b2c
Blockchain is Valid:True
[
{
"nonce": 392303,
"timeStamp": 1526829543.310173,
"data": "Hi im the first block",
"hash": "00000ecb9f53da95a26ee8316131bda984bc3afb037a8bdf29074004261b839b",
"previousHash": "0"
},
{
"nonce": 1228895,
"timeStamp": 1526829544.882065,
"data": "Yo im the second block",
"hash": "00000e576c0879015c408e6ec870a25745d7049588aef35fcc28c4f071524f8f",
"previousHash": "00000ecb9f53da95a26ee8316131bda984bc3afb037a8bdf29074004261b839b"
},
{
"nonce": 3053359,
"timeStamp": 1526829550.066907,
"data": "Hey im the third block",
"hash": "00000137d52d6a04821f5aa3bc77dd0c51b768e415e8b44115b36b80f62b9b2c",
"previousHash": "00000e576c0879015c408e6ec870a25745d7049588aef35fcc28c4f071524f8f"
}
]

挖矿每个区块大约要花费3秒。

reference
https://medium.com/programmers-blockchain/create-simple-blockchain-java-tutorial-from-scratch-6eeed3cb03fa