参考自:Learn Blockchains by Building One
数字货币的崛起在这些年无比惊艳,区块链这个本来陌生的概念这两年,尤其是今年以来甚至都要盖过了人工智能。但对于不懂密码学、不懂共识协议、也不怎么敢炒币的同学来说,怎么样了解这一新兴的概念以让自己不被时代淘汰,怎么样让心里那一小小的对风口的渴望成为现实,也就是,**怎么样了解区块链的本质,以将这种技术落地转换成真正的商业模式?**相信,这是很多同学都想要知道的问题。
而想要深刻的理解区块链到底是个东西,办法很简单,知行合一,做一个出来。
准备
预备知识
阅读本文,需要读者对Python
有基本的理解,能读写基本的Python
,并且需要了解HTTP
网络协议。
区块链狭义上是分布式账本,广义上是分布式基础架构与计算方式
如果您对区块链还不是太了解,可阅读 什么是区块链。
区块链在数据结构上是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,它们之间通过**哈希值(hashes)**进行链接。
环境准备
环境准备,确保已经安装Python3.6+, pip , Flask, requests
安装方法:
pip install Flask==0.12.2 requests==2.18.4
同时还需要一个HTTP
客户端,比如Postman,cURL或其它客户端。
开发
代码地址:https://github.com/zgljl2012/py_blockchain_learn
新建一个项目(建议使用VSCode。。。),然后新建一个 .py
文件 blockchain.py
,我们将在这个文件里定义区块链类。
Blockchain类
创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。
代码如下:
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
以上是区块链类的各个接口:
- 构造函数: 中创建了两个列表,一个用于储存区块,一个用于储存交易
- new_block: 创建一个新区块并将其加入链中
- new_transaction: 创建交易,并将其加入列表中
- hash: 对区块进行hash计算
- last_block: 获取最后一个区块
区块的结构
每个区块包含属性:
- 索引(
index
) - Unix时间戳(
timestamp
) - 交易列表(
transactions
) - 工作量证明(
proof
) - 前一个区块的
Hash
值。
区块的结构如下:
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
通过以上区块链的结构,相信大家对区块链的原理有了进一步的了解:每个区块都有上一区块的Hash值,从而形成了一条链;如果上一个区块变了,那么后面的区块都将不正确了,保证了区块链的安全;交易保存在区块内。
添加交易
下面我们添加一个交易,实现new_transaction
方法
def new_transaction(self, sender, recipient, amount):
"""
新交易信息,此信息将加入到下一个待挖的区块中
:param sender: <str> 发送者
:param recipient: <str> 接收者
:param amount: <int> 数额
:return: <int> 区块索引
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
每笔交易记录了转账发起人地址、转账接收人地址和每笔数额;然后将该笔交易所处区块的索引返回。因为这笔交易将记录在下一个产生的区块里,所以是上一个的区块索引+1
。
创建区块
实例化Blockchain后,下面我们开始构造创世块(没有前区块的第一个区块),并且给它加上一个工作量证明。每个区块都将需要经过工作量证明,即挖矿(每个区块都是被“挖”出来的)。
接下来,我们实现 new_block()
, new_transaction()
和 hash()
。
首先引入几个包:
from time import time
import json
import hashlib
接下来,是几个方法的实现:
def __init__(self):
self.current_transactions = []
self.chain = []
# Create the genesis block
self.new_block(previous_hash=1, proof=100)
def new_block(self, proof, previous_hash):
"""
生成新区块
:param proof: <int> 工作量证明
:param previous_hash: (Optional) <str> 前一个区块的Hash值
:return: <dict> New Block
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.chain[-1]),
}
# Reset the current list of transactions
self.current_transactions = []
self.chain.append(block)
return block
@property
def last_block(self):
return self.chain[-1]
@staticmethod
def hash(block):
"""
生成块的 SHA-256 hash值
:param block: <dict> Block
:return: <str>
"""
# We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
工作量证明 PoW
这部分有些复杂,我们分为三步讲解:
- 什么是PoW(工作量证明)?
- 为什么要进行工作量证明?
- 如何进行工作量证明?
什么是PoW
工作量证明(Proof Of Work,简称PoW),简单理解就是一份证明,用来确认你做过一定量的工作。监测工作的整个过程通常是极为低效的,而通过对工作的结果进行认证来证明完成了相应的工作量,则是一种非常高效的方式。
比如毕业证书。我们找工作时,如何证明自己接受过高等教育,是大学毕业呢?很简单,通过毕业证书。工作量证明亦是如此。
为什么要进行PoW
这里我们需要回溯一下比特币的原理。
区块链是由区块构成的,交易存储在区块中,然后每个区块是通过“挖矿”产生的。“挖矿”是什么样的一个过程呢?就是全网所有的“人”都做同一个工作,谁做得最快,那么这个区块就是谁“挖”出的,然后这个“人”就获得系统的奖励——一个比特币。
好了,问题就来了,怎么证明这个“人”的工作做得最好呢?怎么样让全网所有“人”达成**“共识”**都说这个人做得最快呢?
答案:工作量证明。
同时,这种让全网所有人达成共识,一致认定这个“人”做得最快的协议就叫做“共识协议”。共识协议不止Pow这一种,相信大家已经所有耳闻。
如何进行工作量证明
上面我们说到了,工作量证明的目的是让全网所有人达成共识,有一个“人”工作做得最快,然后这个“人”产生一个区块并获得一个比特币,那么这里就又出现了几个问题了:
- 全网所有人做什么工作?这个工作必须比较困难,耗时要长一点
- 大家怎么验证结果是正确的?
做什么工作
PoW共识协议要求全网所有“人”做的工作是:找出一个符合特定条件的数字。
比如:
假设一个整数 x 乘以另一个整数 y 的积的 Hash 值必须以 0 结尾,即 hash(x * y) = ac23dc…0。设变量 x = 5,求 y 的值?
Python代码如下:
from hashlib import sha256
x = 5
y = 0 # y未知
while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":
y += 1
print(f'The solution is y = {y}')
好了,所有的矿工只要跑上面的代码,不断的循环计算就好了,直到符合条件的y
出来为止。
如何验证结果
结果是y=21. 因为:
hash(5 * 21) = 1253e9373e...5e3600155e860
这样就验证了结果。
我们的PoW
好,下面就我们也来设计一个 PoW 算法,首先是定义一下“工作”的规则:
寻找一个数p,使得它与前一个区块的 proof (工作量) 拼接成的字符串的 Hash 值以 2 个零开头
为了避免太浪费时间,我们的规则是比较简单的。
首先修改 blackchain.py
的代码。
首先引入1个包:
from uuid import uuid4
然后添加代码:
def proof_of_work(self, last_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):
"""
工作量验证
: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[:2] == "00"
guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
好了,到此为止,我们的Blockchain
基本就完成了。希望坚持看到了这里的同学,能再坚持坚持~,接下来的东西对于不懂网络(HTPP
)、不懂后台开发的同学会稍有些吃力了。尽量让大家测试、实践的时候能方便些。