基于 Python 语言理解区块链原理
Repo: https://github.com/WeLightProject/BlockchainSim
视频教学: https://ke.qq.com/course/3616174?tuin=d34bd514&taid=12533264329158062
参考文章:
实现创建新区块 | Python实现区块链
基于 Python 语言,动手实现一个区块链——第一步,实现创建新区块。
想要了解区块到底是什么,最简单快捷的办法就是分析它的数据结构,以 Bitcoin 中的区块 #514095 为例:
{
"hash":"00000000000000000018b0a6ae560fa33c469b6528bc9e0fb0c669319a186c33",
"confirmations":1009,
"strippedsize":956228,
"size":1112639,
"weight":3981323,
"height":514095,
"version":536870912,
"versionHex":"20000000",
"merkleroot":"5f8f8e053fd4c0c3175c10ac5189c15e6ba218909319850936fe54934dcbfeac",
"tx":[
// ...
],
"time":1521380124,
"mediantime":1521377506,
"nonce":3001236454,
"bits":"17514a49",
"difficulty":3462542391191.563,
"chainwork":"0000000000000000000000000000000000000000014d2b41a340e60b72292430",
"previousblockhash":"000000000000000000481ab128418847dc25db4dafec464baa5a33e66490990b",
"nextblockhash":"0000000000000000000c74966205813839ad1c6d55d75f95c9c5f821db9c3510"
}
在这个 Block 的结构体中,previousblockhash
和 merkleroot
是两个最重要的字段;前者是一个哈希指针,它其实是前一个 Block 的哈希,通过 previousblockhash
我们能递归地找到全部的 Block,也就是整条主链,后者是一个 Merkle 树的根,Merkle 树中包含整个 Block 中的全部交易,通过保存 merkleroot
,我们可以保证当前 Block 中任意交易都不会被修改。
Ethereum 的区块链模型虽然与 Bitcoin 有非常大的不同,但是它的 Block 结构中也有着类似的信息:
{
"jsonrpc":"2.0",
"result":{
"author":"0x00d8ae40d9a06d0e7a2877b62e32eb959afbe16d",
"difficulty":"0x785042b0",
"extraData":"0x414952412f7630",
"gasLimit":"0x47b784",
"gasUsed":"0x44218a",
"hash":"0x4de91e4af8d135e061d50ddd6d0d6f4119cd0f7062ebe8ff2d79c5af0e8344b9",
"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner":"0x00d8ae40d9a06d0e7a2877b62e32eb959afbe16d",
"mixHash":"0xb8155224974967443d8b83e484402fb6e1e18ff69a8fc5acdda32f2bcc6dd443",
"nonce":"0xad14fb6803147c7c",
"number":"0x2000f1",
"parentHash":"0x31919e2bf29306778f50bbc376bd490a7d056ddfd5b1f615752e79f32c7f1a38",
"receiptsRoot":"0xa2a7af5e3b9e1bbb6252ba82a09302321b8f0eea7ec8e3bb977401e4f473e672",
"sealFields":[
"0xa0b8155224974967443d8b83e484402fb6e1e18ff69a8fc5acdda32f2bcc6dd443",
"0x88ad14fb6803147c7c"
],
"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size":"0x276",
"stateRoot":"0x87e7e54cf229003014f453d64f0344e2ba4fc7ee3b95c7dd2642cca389fa1efe",
"timestamp":"0x5a10968a",
"totalDifficulty":"0x1804de0c47ffe1",
"transactions":[...],
"transactionsRoot":"0xc2091b032961ca23cf8323ea827e8956fe6dda9e68d75bcfaa8b910035397e35",
"uncles":[]
},
"id":1
}
parentHash
和 transactionsRoot
分别对应着 Bitcoin 中 previousblockhash
和 merkleroot
,这两者在整个区块链网络中是非常重要的。
Block 结构体中的哈希指针在区块链中有两个作用,它不仅能够连接不同的区块,还能够对 Block 进行验证,保证 Block 中的数据不会被其他恶意节点篡改。
除了第一个 Block,每一个 Block 中的 prev_hash
都是前一个 Block 的哈希,如果某一个节点想要修改主链上 Block 的交易,就会改变当前 Block 的哈希,后面的 Block 就没有办法通过 prev_hash
找到前面的链,所以当前节点篡改交易的行为就会被其他节点发现。
另一个字段 merkleroot
其实就是一个 Merkle 树 的根节点,它其实是一种使用哈希指针连接的数据结构;虽然 Merkle 树有叶节点和非叶节点,但是它只有叶节点会存储数据,所有的非叶结点都是用于验证数据完整性的哈希。
每一个 Block 中的全部交易都是存储在这个 Merkle 树中并将 merkleroot
保存在 Block 的结构体中,保证当前 Block 中任意交易的篡改都能被立刻发现。
prev_hash
和 merkleroot
分别通过『指针』的方式保证所有的 Block 和交易都是连接起来的,最终保证 Block 和交易不会被恶意节点或攻击者篡改,几乎全部的区块链项目都会使用类似方式连接不同的 Block 和交易,这可以说是区块链项目的基础设施和标配了。
注:以上内容来自 Draveness博客
可以通过如下命令进行下载:
wget https://leeduckgo.com/blockchain.py
代码如下:
import hashlib
import json
from time import time
class Blockchain(object):
def __init__(self):
self.chain = []
self.current_transactions = []
def new_block(self, proof, previous_hash=None):
"""
创建一个新的区块到区块链中
:param proof: <int> 由工作证明算法生成的证明
:param previous_hash: (Optional) <str> 前一个区块的 hash 值
:return: <dict> 新区块
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash,
}
# 重置当前交易记录
self.current_transactions = []
self.chain.append(block)
return block
def new_transaction(self):
# Adds a new transaction to the list of transactions
pass
@staticmethod
def hash(block):
"""
给一个区块生成 SHA-256 值
:param block: <dict> Block
:return: <str>
"""
# 我们必须确保这个字典(区块)是经过排序的,否则我们将会得到不一致的散列
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
@property
def last_block(self):
return self.chain[-1]
Flask 是一个 Python 实现的 Web 开发微(Micro)框架,微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展,非常适合用来作为进行各种计算机技术的入门学习——因为我们无需把精力放在理解Web框架的细节上。
Flask 文档:
Flask 安装方式:
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple flask request
注:本实操基于 Python 语言。
本实验可用两种方式实践。
方式1:
通过 vim 编写.py
脚本,用python3
命令执行。
执行vim
命令:
vim
按i
键进入输入模式。
粘贴代码。
输入::w [文件名]
保存,如:w hash.py
输入:q
退出。
执行脚本:
python3 [文件名] # 如 python3 hash.py
方式2:
打开互动式编辑器运行代码。
输入如下命令进入互动式编辑器:
python3
将代码一行一行复制执行。
代码如下:
import hashlib
import json
from time import time
class Blockchain(object):
def __init__(self):
self.chain = []
self.current_transactions = []
def new_block(self):
# Creates a new Block and adds it to the chain
pass
def new_transaction(self):
# Adds a new transaction to the list of transactions
pass
@staticmethod
def hash(block):
# Hashes a Block
pass
@property
def last_block(self):
# Returns the last Block in the chain
pass
我们的 Blockchain
类负责管理链式数据,它会存储存储交易,还有添加新的区块到已有的区块链上。让我们来扩充更多的方法。
区块是什么样的?
每个块都有一个 索引,一个 时间戳(Unix时间戳),一个事务列表, 一个 校验 (稍后详述) 和 前一个块的散列 。
下面是一个 Block 的样子 :
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
在这一点上,一个 区块链 的概念应该是明显的 - 每个新块都包含在其内的前一个块的 散列。这是至关重要的,因为这是区块链不可改变的原因:如果攻击者损坏 区块链 中较早的块,则所有后续块将包含不正确的哈希值。
这有道理吗? 如果你还没有想通,花点时间仔细思考一下 - 这是区块链背后的核心理念。
在类Blockchain
中,我们补全函数new_block()
、last_block()
与hash()
:
def new_block(self, proof, previous_hash=None):
"""
创建一个新的区块到区块链中
:param proof: <int> 由工作证明算法生成的证明
:param previous_hash: (Optional) <str> 前一个区块的 hash 值
:return: <dict> 新区块
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash,
}
# 重置当前交易记录
self.current_transactions = []
self.chain.append(block)
return block
@property
def last_block(self):
return self.chain[-1]
@staticmethod
def hash(block):
"""
给一个区块生成 SHA-256 值
:param block: <dict> Block
:return: <str>
"""
# 我们必须确保这个字典(区块)是经过排序的,否则我们将会得到不一致的散列
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
现在,我们已经有了一个可运行的代码版本,可以尝试去生成区块了!
我们可以用如下命令在命令行中下载代码:
wget https://leeduckgo.com/v1/block.py
在同一目录下执行python3
,打开python
交互式代码编译器。
复制粘贴如下代码:
from blockchain import Blockchain # 从文件引入Blockchain类
blockchain = Blockchain() # 创建新的 Blockchain 对象
print(blockchain.new_block("test"))
print(blockchain.chain)
print(blockchain.last_block)
屏幕上会打印出当前新的区块、当前链与最后一个区块。
创建第二个区块:
print(blockchain.new_block("test2", blockchain.hash(blockchain.last_block)))
print(blockchain.chain)
我们可以看到区块是首位相连的。
基于 Python 语言,动手实现一个区块链——第二步,实现交易相关函数。
区块链中的「交易」一词很具有迷惑性,似乎代表着一定涉及金钱的转换。
实际上,我们应该将交易抽象为如下的抽象模型:
{
from: from_addr,
to: to_addr,
amount: amount, # 仅公链有效
gas: gas, # 手续费
op: operation # 附带的操作
}
公链中,每笔交易均会包含amount
(amount可能是0)与gas
。有的交易是单纯的用户间转账,有的交易的关键点在于operation
。这个时候,to
通常是一个合约地址,operation
会告诉「区块链计算机」要做什么——例如在区块链数据库里存储一个值,例如在区块链上进行某些计算,这时我们就将区块链看成是一台「分布式计算机」。
在联盟链中,amount
这个概念被废弃,同时,也没有了原生代币转账的交易(注意,ERC20这种基于智能合约的代币是支持的,不过通常被称为积分)。但是,gas
依然存在,用来衡量对计算资源的消耗量。
实验代码可用命令下载:
wget https://leeduckgo.com/blockchain_with_tx.py
这一节完成后的 blockchain_with_tx.py
完整代码:
import hashlib
import json
from time import time
class Blockchain(object):
def __init__(self):
self.chain = []
self.current_transactions = []
def new_block(self, proof, previous_hash=None):
"""
创建一个新的区块到区块链中
:param proof: <int> 由工作证明算法生成的证明
:param previous_hash: (Optional) <str> 前一个区块的 hash 值
:return: <dict> 新区块
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash,
}
# 重置当前交易记录
self.current_transactions = []
self.chain.append(block)
return block
def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
:param sender: <str> Address of the Sender
:param recipient: <str> Address of the Recipient
:param amount: <int> Amount
:return: <int> The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
@staticmethod
def hash(block):
"""
给一个区块生成 SHA-256 值
:param block: <dict> Block
:return: <str>
"""
# 我们必须确保这个字典(区块)是经过排序的,否则我们将会得到不一致的散列
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
@property
def last_block(self):
return self.chain[-1]
注:本实操基于 Python 语言。
本实验可用两种方式实践。
方式1:
通过 vim 编写.py
脚本,用python3
命令执行。
执行vim
命令:
vim
按i
键进入输入模式。
粘贴代码。
输入::w [文件名]
保存,如:w hash.py
输入:q
退出。
执行脚本:
python3 [文件名] # 如 python3 hash.py
方式2:
打开互动式编辑器运行代码。
输入如下命令进入互动式编辑器:
python3
将代码一行一行复制执行。
基于上一节内容的代码「实现创建新区块 | Python实现区块链」,更新new_transaction
函数:
def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
:param sender: <str> Address of the Sender
:param recipient: <str> Address of the Recipient
:param amount: <int> Amount
:return: <int> The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
from block import Blockchain # 从文件引入Blockchain类
blockchain = Blockchain() # 创建新的 Blockchain 对象
print(blockchain.new_block("first bloooooooock")) # 创世区块
# 插入两笔交易到交易池
blockchain.new_transaction("alice", "bob", 1000)
blockchain.new_transaction("bob", "zhangsan", 400)
print(blockchain.new_block("second", blockchain.hash(blockchain.last_block)))
print(blockchain.chain)
交易相关功能正常:
实现工作量证明算法 | Python 实现区块链
基于 Python 语言,动手实现一个区块链——第三步,实现工作量证明算法。
To implement a distributed timestamp server on a peer-to-peer basis, we will need to use a proof-of-work system similar to Adam Back's Hashcash[6], rather than newspaper or Usenet posts. The proof-of-work involves scanning for a value that when hashed, such as with SHA-256, the hash begins with a number of zero bits. The average work required is exponential in the number of zero bits required and can be verified by executing a single hash.
为了实现一个基于点对点的分布式时间戳服务器,我们需要使用类似亚当·伯克的哈希现金[6]那样的一个工作证明系统,而不是报纸或者新闻组帖子那样的东西。所谓的工作证明,就是去寻找一个数值;这个数值要满足以下条件:为它提取散列数值之后 —— 例如使用 SHA-256 计算散列数值 —— 这个散列数值必须以一定数量的 0 开头。每增加一个 0 的要求,将使得工作量指数级增加,并且,这个工作量的验证却只需通过计算一个哈希。
For our timestamp network, we implement the proof-of-work by incrementing a nonce in the block until a value is found that gives the block's hash the required zero bits. Once the CPU effort has been expended to make it satisfy the proof-of-work, the block cannot be changed without redoing the work. As later blocks are chained after it, the work to change the block would include redoing all the blocks after it.
在我们的时间戳网络中,我们是这样实现工作证明的:不断在区块之中增加一个随机数(Nonce),直到一个满足条件的数值被找到;这个条件就是,这个区块的哈希以指定数量的 0 开头。一旦 CPU 的耗费算力所获的的结果满足工作证明,那么这个区块将不再能被更改,除非重新完成之前的所有工作量。随着新的区块不断被添加进来,改变当前区块即意味着说要重新完成所有其后区块的工作。
The proof-of-work also solves the problem of determining representation in majority decision making. If the majority were based on one-IP-address-one-vote, it could be subverted by anyone able to allocate many IPs. Proof-of-work is essentially one-CPU-one-vote. The majority decision is represented by the longest chain, which has the greatest proof-of-work effort invested in it. If a majority of CPU power is controlled by honest nodes, the honest chain will grow the fastest and outpace any competing chains. To modify a past block, an attacker would have to redo the proof-of-work of the block and all blocks after it and then catch up with and surpass the work of the honest nodes. We will show later that the probability of a slower attacker catching up diminishes exponentially as subsequent blocks are added.
工作证明同时解决了如何决定谁能代表大多数做决定的问题。如果所谓的“大多数”是基于“一个 IP 地址一票”的方式决定的话,那么任何一个可以搞定很多 IP 地址的人就可以被认为是“大多数”。工作证明本质上来看,是“一个 CPU(可以理解为一份算力) 一票”。所谓的“大多数决定”是由最长链所代表的,因为被投入最多工作的链就是它。如果大多数 CPU 算力被诚实的节点所控制,那么诚实链成长最为迅速,其速度会远超其他竞争链。为了更改一个已经产生的区块,攻击者将不得不重新完成那个区块以及所有其后区块的的工作证明,而后还要追上并超过诚实节点的工作。后文展示为什么一个被拖延了的攻击者能够追上的可能性将随着区块的不断增加而指数级降低。
To compensate for increasing hardware speed and varying interest in running nodes over time, the proof-of-work difficulty is determined by a moving average targeting an average number of blocks per hour. If they're generated too fast, the difficulty increases.
为了应对硬件算力综合的不断增加,以及随着时间推进可能产生的节点参与数量变化,工作证明难度由此决定:基于平均每小时产生的区块数量的一个移动平均值。如果区块生成得过快,那么难度将会增加。
比特币区块中的 nonce 是一个 32 位(4 字节)的字段。有了这个字段,区块的哈希值(一串 16 进制数字)小于或等于目前网络的目标,也即表示矿工实现了某个工作量。这个字段和其它字段是独立的,也即不会影响到其它的字段。
需要注意的是,Nonce 是一个变化的值。可以把它当成比特币系统的一个「操控杆」。正是有了这个操控杆,不管有多少算力投入了这个系统,也能保证平均十分钟出一个块。
实验代码可用命令下载:
wget https://leeduckgo.com/blockchain_with_pow.py
这一节完成后的 blockchain_with_pow.py
完整代码:
import hashlib
import json
from time import time
class Blockchain(object):
def __init__(self):
self.chain = []
self.current_transactions = []
self.last_nonce = 0
def new_block(self, nonce, previous_hash=None):
"""
创建一个新的区块到区块链中
:param proof: <int> 由工作证明算法生成的证明
:param previous_hash: (Optional) <str> 前一个区块的 hash 值
:return: <dict> 新区块
"""
if self.valid_proof(self.last_nonce, nonce) == True:
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'last_nonce': nonce,
'previous_hash': previous_hash,
}
# 重置当前交易记录
self.current_transactions = []
self.last_nonce = nonce # 更新last_nonce
self.chain.append(block)
return block
else:
return "nonce(proof) is invalid!"
def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
:param sender: <str> Address of the Sender
:param recipient: <str> Address of the Recipient
:param amount: <int> Amount
:return: <int> The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
@staticmethod
def hash(block):
"""
给一个区块生成 SHA-256 值
:param block: <dict> Block
:return: <str>
"""
# 我们必须确保这个字典(区块)是经过排序的,否则我们将会得到不一致的散列
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
@property
def last_block(self):
return self.chain[-1]
def proof_of_work(self, last_nonce):
"""
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous nonce, and p' is the new nonce
:param last_nonce: <int>
:return: <int>
"""
nonce = 0
while self.valid_proof(last_nonce, nonce) is False:
nonce += 1
return nonce
@staticmethod
def valid_proof(last_nonce, nonce):
"""
Validates the Proof: Does hash(last_nonce, nonce) contain 4 leading zeroes?
:param last_nonce: <int> Previous Proof
:param nonce: <int> Current Proof
:return: <bool> True if correct, False if not.
"""
guess = f'{last_nonce}{nonce}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
注:本实操基于 Python 语言。
本实验可用两种方式实践。
方式1:
通过 vim 编写.py
脚本,用python3
命令执行。
执行vim
命令:
vim
按i
键进入输入模式。
粘贴代码。
输入::w [文件名]
保存,如:w hash.py
输入:q
退出。
执行脚本:
python3 [文件名] # 如 python3 hash.py
方式2:
打开互动式编辑器运行代码。
输入如下命令进入互动式编辑器:
python3
将代码一行一行复制执行。
使用工作量证明(PoW)算法,来证明是如何在区块链上创建或挖掘新的区块。PoW 的目标是计算出一个符合特定条件的数字,这个数字对于所有人而言必须在计算上非常困难,但易于验证。这是工作证明背后的核心思想。
我们将看到一个简单的例子帮助你理解:
假设一个整数 x 乘以另一个整数 y 的积的 Hash 值必须以 0 结尾,即 hash(x * y) = ac23dc...0。设 x = 5,求 y 。
用 Python 实现:
from hashlib import sha256
x = 5
y = 0 # We don't know what y should be yet...
while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":
y += 1
print(f'The solution is y = {y}')
结果是:y = 21。因为,生成的 Hash 值结尾必须为 0。
hash(5 * 21) = 1253e9373e...5e3600155e860
在比特币中,工作量证明算法被称为 Hashcash ,它和上面的问题很相似,只不过计算难度非常大。这就是矿工们为了争夺创建区块的权利而争相计算的问题。 通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,就会获得一定数量的比特币奖励(通过交易)。
基于上一节内容的代码「实现交易函数 | Python实现区块链」,添加新函数proof_of_work
与valid_proof
def proof_of_work(self, last_proof):
"""
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof
:param last_proof: <int>
:return: <int>
"""
proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1
return proof
@staticmethod
def valid_proof(last_proof, proof):
"""
Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?
:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:return: <bool> True if correct, False if not.
"""
guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
确保目前与blockchain_with_pow
在同一目录下:
from blockchain import Blockchain
b = Blockchain()
print(b.last_nonce) # 查看当前 nonce
b.new_block(333) # 现在随便输入一个 nonce 不行了
nonce = b.proof_of_work(b.last_nonce) # 需要计算出合法的nonce才能生成新区块!
b.new_block(nonce)
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!