最近学了区块链的基本概念,不如亲自搭建一个区块链试一试。本文教你搭建一个简易的区块链。

区块链结构 Blockchain Structure

区块链是由一个个区块构成,每个区块中保存着一系列数据。鉴于我们要搭建一个简易的区块链,暂且在每个区块中存放下列数据:

  • index:记录当前区块高度/区块编号;
  • timestamp:记录当前区块的时间戳,即当前区块是何时被挖出的;
  • proof:记录当前区块的验证数据,类似于PoW中的随机数;
  • previous_hash:记录前一区块的哈希值。

当然,之后也可以在区块中加入交易信息等其他数据。

构建区块链 Build a Blockchain

为了更好的维护区块链,我们构造一个Blockchain类,将必要的函数都存放到这个类中。具体的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Blockchain:
def __init__(self):
# 初始化
pass

def create_block(self, proof, previous_hash):
# 使用proof和前一区块的哈希值previous_hash创造一个新的区块
pass

def get_previous_block(self):
# 返回上一个区块信息
pass

def proof_of_work(self, previous_proof):
# 定义一个PoW挖块方法
pass

def hash(self, block):
# 计算给定区块的哈希值
pass

def is_chain_valid(self, chain):
# 给定一个区块链,判断该区块链是否有效
pass

接下来我们分别完成每一个函数:

  • __init__(self): 在初始化函数中,设定一个数组存放所有的区块信息,并且自动生成第一个区块(genesis block):

    1
    2
    3
    def __init__(self):
    self.chain = []
    self.create_block(proof = 1, previous_hash = '0')
  • def create_block(self, proof, previous_hash):根据proof和previous_hash构造一个新的区块,并把新区块加入已有区块链:

    1
    import datetime # 需要使用datetime库获取当前时间戳
    1
    2
    3
    4
    5
    6
    7
    8
    def create_block(self, proof, previous_hash):
    block = {'index': len(self.chain) + 1,
    'timestamp': str(datetime.datetime.now()),
    'proof': proof,
    'previous_hash': previous_hash}
    # block中存放新建区块的数据
    self.chain.append(block)
    return block
  • def get_previous_block(self):返回上一区块中的数据:

    1
    2
    def get_previous_block(self):
    return self.chain[-1]
  • def proof_of_work(self, previous_proof):定义一个PoW算法。假设前一区块的proof为 $x$ ,寻找当前区块的proof $y$ 使得 $y2-x2$ 经过sha256算法后前4位都是0。注意,这里用的算法和比特币中的算法不同。你也可以定义自己的PoW算法。

    1
    import hashlib # 需要使用hashlib库计算sha256哈希值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def proof_of_work(self, previous_proof):
    new_proof = 1
    check_proof = False
    # 通过暴力穷举,proof从1开始,直到找到符合条件的proof返回
    while check_proof is False:
    hash_operation = hashlib.sha256(str(new_proof**2 - previous_proof**2).encode()).hexdigest()
    if hash_operation[:4] == '0000':
    check_proof = True
    else:
    new_proof += 1
    return new_proof
  • def hash(self, block):返回给定区块的哈希值:

    1
    import json # 需要使用json库将python数据转化为json字符串
    1
    2
    3
    def hash(self, block):
    encoded_block = json.dumps(block, sort_keys = True).encode()
    return hashlib.sha256(encoded_block).hexdigest()
  • def is_chain_valid(self, chain): 给定一个区块链,验证每个区块是否有效:1)验证每个区块中previous_hash是否和前一区块的哈希值相同;2)验证每个区块中的proof是否符合PoW规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def is_chain_valid(self, chain):
    previous_block = chain[0]
    block_index = 1 # 从第二个区块(index为1)开始验证
    while block_index < len(chain):
    block = chain[block_index]
    # 判断previous_hash是否符合条件
    if block['previous_hash'] != self.hash(previous_block):
    return False
    # 验证proof是否符合条件
    previous_proof = previous_block['proof']
    proof = block['proof']
    hash_operation = hashlib.sha256(str(proof**2 - previous_proof**2).encode()).hexdigest()
    if hash_operation[:4] != '0000':
    return False
    previous_block = block
    block_index += 1
    return True

至此,基本的区块链结构已经完成,接下来要想办法如何挖出一个区块。

挖一个区块 Mine a block

刚刚我们构建好了Blockchain类,是时候运行一个实例了:

1
blockchain = Blockchain()

为了能够在本地运行一个区块链以及检查区块链的运行状态,我们需要用到flask库和Postman软件。

1
from flask import Flask, jsonify
1
app = Flask(__name__) # 创建一个Web App

flask快速上手请看这里。别担心,我们只用到几个非常基本的功能。

为了能在现有区块链上挖一个块,我们定义一个mine_block函数。注意,在实际比特币网络中,挖矿肯定要输入交易信息等其他数据。这里为了简化便没有输入数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Mining a new block
@app.route('/mine_block', methods = ['GET'])
def mine_block():
previous_block = blockchain.get_previous_block()
previous_proof = previous_block['proof']
# 计算新区块的proof
proof = blockchain.proof_of_work(previous_proof)
previous_hash = blockchain.hash(previous_block)
# 创建一个新区块
block = blockchain.create_block(proof, previous_hash)
# response为返回信息,告知挖块成功,并返回当前区块中的数据
response = {'message': 'Congratulations, you just mined a block',
'index': block['index'],
'timestamp': block['timestamp'],
'proof': block['proof'],
'previous_hash': block['previous_hash']}
# jsonify把python数据变为json类型,200为http返回值,表示一切正常
return jsonify(response), 200

我们可以再构建两个函数:get_chain()用于返回当前区块链的信息,is_valid()用于验证现有区块链是否有效/是否被篡改过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Getting the full Blockchain
@app.route('/get_chain', methods = ['GET'])
def get_chain():
# 返回区块链中所有区块信息,以及当前区块的高度
response = {'chain': blockchain.chain,
'length': len(blockchain.chain)}
return jsonify(response), 200

# Checking if the blockchain is valid or not
@app.route('/is_valid', methods = ['GET'])
def is_valid():
is_valid = blockchain.is_chain_valid(blockchain.chain)
if is_valid:
response = {'message': 'All good. The Blockchain is valid.'}
else:
response = {'message': 'Houston, we have a problem. The Blockchain is not valid.'}
# 这里返回的信息可以自己写啦,比如“有内鬼,终止交易”hhh
return jsonify(response), 200

最后,是时候让一切都跑起来了:

1
2
# Running the app
app.run(host = '0.0.0.0', port = 5000)

和区块链进行交互

代码运行起来之后,可以在控制台看到如下信息:

1
* Running on http://0.0.0.0:5000/

打开Postman,点击Create a request,即可和区块链进行交互了。(没有Postman用浏览器也可以)

区块链刚创建的时候,应该只有一个创世区块。输入 http://127.0.0.1:5000/get_chain 按下回车可以发现,返回了创世区块的信息,并且当前区块高度为1。

我们可以试着多挖几个区块,输入http://127.0.0.1:5000/get_chain 按下回车,每按一次回车就会自动挖一个区块。(这里我按到index为8了,你甚至可以一直按回车等区块高度到100)

然后我们再看看区块状态,这时会返回区块链上所有的区块信息,并且告诉我们区块高度为8:

最后我们再试试该区块链是否有效,输入http://127.0.0.1:5000/is_valid

一切正常,区块链是有效的。

至此,我们已经在本地搭建了一个简易的区块链,希望你对区块链有更近一步的了解。当然,你也可以试着在区块中加入一些其他的数据,或者多写一些其他和区块链交互函数。如果你有任何疑问,欢迎在下方评论留言~


Q:也许你会发现我们搭建的区块链中,每个区块的哈希值并不是以若干个0开头的,这是为什么?

A:在proof_of_work()函数中,我们定义的PoW算法是计算两个proof之间运算结果的哈希值是否为4个0开头,而不是整个区块的哈希值是以若干个0开头。你可以试着改一改其中的PoW算法,让区块哈希值以4个0开头。

另:代码排版有些乱,正在调整ing