python区块链框架_GitHub - shawnsky/blockchain: 使用 Python 从零搭建一个自己的区块链...

blockchain

本项目是 naivecoin 的 python 实现版本,包括区块链的数据结构、“挖矿”、节点同步的功能。

环境

Python >= 3.4

Flask == 1.1.1

介绍

区块链是什么?核心就是一句话,区块链是一个分布式数据库,它维护着一个持续不断增长的、有序的记录列表。

比如比特币是区块链技术的一个比较出名的应用。在数字货币的系统中,分布式数据库里保存的就是一条一条交易转账信息。在本次技术分享中,会用 Python 编程实现一个简单的区块链,包括区块链的数据结构、“挖矿”、节点同步的功能。具体来说,将会解决以下问题:

区块以及区块链的数据结构

如何创建区块

定义区块之间的关系

向区块链中添加区块

区块链网络中的节点同步

一个简单的区块链 HTTP 管理接口

区块

在一个系统中如何持续不断的存储数据呢?可能会遇到如下困难

怎么让数据逐渐增加

怎么对之前的数据进行修改

区块的数据结构

主要包括以下核心元素:

index 区块的递增编号

timestamp 时间戳

data 该区块所保存的数据

hash 该区块的hash值

previous_hash 前一个区块的hash值,从⽽将区块串联了起来

于是我们可以得到如下所示的代码

class Block:

def __init__(self,index,transactions,timestamp,previous_hash,difficulty=3,nonce=0):

self.index = index

self.transactions = transactions

self.timestamp = timestamp

self.previous_hash = previous_hash

区块的哈希值

我们有一个共识,就是我们不会去修改已经保存了的区块数据。可是仅凭大家的共识没有用,那该怎么去真正的去限制呢?

hash值是最重要的属性之一。hash值是对该块的所有数据进行计算的,也就是这个区块的摘要。这意味着如果块中的任何内容发生变化,原始hash值将不再有效。我们可以通过这个办法来检查区块数据是否被恶意的篡改。

所以,在 Block 类中,定义了一个 compute_hash() 方法来计算本区块的hash值。

def compute_hash(self):

block_json = json.dumps(self.__dict__, sort_keys=True)

return sha256(block_json.encode()).hexdigest()

区块链

区块链保存着一个区块数组,以及定义了操作区块链的一些方法。

class BlockChain:

def __init__(self):

self.blocks = []

self.__create_genesis_block()

def __create_genesis_block(self):

genesis_block = Block(0, [], time.time(), "cuc0123456789")

self.blocks.append(genesis_block)

@property

def last_block(self):

return self.blocks[-1]

创世区块

创世区块就是区块链中的第一个区块,因为每一个区块都需要一个 previous_hash 属性,区块链中的第一个区块需要我们用硬编码的方式创建出来,它的 previous_hash 可以被随便设置一个字符串

genesis_block = Block(0, [], time.time(), "cuc0123456789")

添加区块

分布式系统中,大家都可以添加区块,如何防止很多人一起添加造成的混乱呢?我们可以想出一种策略,大家同时去解一道数学难题,谁先解出来,谁就可以添加区块。

解出这道难题又叫做 Proof-of-Work(工作量证明),当你得到了这道难题的答案,大家就都会认可你解题的工作量。这个解题的过程通常被称为 “挖矿”。

一个好的题⽬要让计算机便于理解,运算规则相对简单,运算方式相对公平。于是结合Hash算法的题目被设计了了出来:

找到一个特定区块,这个区块的Hash需要有特殊的前缀。

这个前缀越特殊,难度就越⼤大。于是我们可以定义出题⽬的难度 difficulty 为你所定义的特殊前缀是由几个0组成。例如,如果你只要求找到的区块的Hash前缀是一个0(difficulty=1),那么可能相对简单。但是如果要求找到的区块的Hash的前缀有10个0(difficulty=10),那么就有点难了。

那么有一个问题,怎么去找这个区块呢?

为了找到满足 difficulty 条件的Hash,我们需要对同一个区块计算出不同的Hash,明显这是不可能的。所以我们可以通过在区块中加⼊新的可变参数来实现Hash结果的变化,因为SHA256算法会因为数据的任何微⼩变化扩散为整个hash结果的完全变化。

于是我们添加了一个叫做 Nonce 的参数,并且不断的改变这个参数的值,直到挖到我们想要的Hash结果。于是我们 Block 类的定义变为这样

class Block:

def __init__(self, index, transactions, timestamp, previous_hash, difficulty=3, nonce=0):

self.index = index

self.transactions = transactions

self.timestamp = timestamp

self.previous_hash = previous_hash

self.difficulty = difficulty

self.nonce = nonce

def compute_hash(self):

block_json = json.dumps(self.__dict__, sort_keys=True)

return sha256(block_json.encode()).hexdigest()

在 “挖矿” 之前,我们先实例化一个 Block

new_block = Block(index=last_block.index + 1,

transactions=[],

timestamp=time.time(),

previous_hash=last_block.compute_hash(),

difficulty=self.get_difficulty(self.blocks))

然后不断的去改变 new_block 的 nonce 属性的值,直到计算出了满足条件的hash值,我们定义一个方法来实现这个操作

def do_a_difficult_work(block):

block.nonce = 0

computed_hash = block.compute_hash()

while not computed_hash.startswith('0' * block.difficulty):

block.nonce += 1

computed_hash = block.compute_hash()

return computed_hash

如何确定难度

虽然我们能够指定问题的难度,但是我们要如何设置难度呢?而且如何才能让网络的节点都认同这个难度呢?

让我们回归到区块链的本身。区块链无非是一个区块的链表,并且每隔一段时间会加入一个新的区块。而我们的题目就是在控制加入区块的难度,也就是加入的时间间隔,于是我们引入一个全局参数:

BLOCK_GENERATION_INTERVAL:定义了多久产生一个区块

但是随着环境的变化,例如有更多的节点加入网络,我们并不能一致维持这个时间,因此我们每隔一段时间需要调整一下难度,于是我们引入第二个全局参数:

DIFFICULTY_ADJUSTMENT_INTERVAL:定义了每隔多久调整一次难度

在我们的代码中,我们会设置间隔为10秒。

我们会根据预期和现实之间的差异决定如何调整难度。具体来说,判断差异是否到了2倍的范围,然后对difficulty进行+1或者-1的操作

def get_difficulty(self, blocks):

last_block = self.last_block

if last_block.index % self.DIFFICULTY_ADJUST_INTERVAL == 0 and last_block.index != 0:

return self.get_adjusted_difficulty(last_block, blocks)

else:

return last_block.difficulty

def get_adjusted_difficulty(self, last_block, blocks):

previous_adjusted_block = blocks[len(blocks) - self.DIFFICULTY_ADJUST_INTERVAL]

time_expected = self.BLOCK_GEN_INTERVAL * self.DIFFICULTY_ADJUST_INTERVAL

time_taken = last_block.timestamp - previous_adjusted_block.timestamp

if time_taken < (time_expected / 2):

return previous_adjusted_block.difficulty + 1

elif time_taken > (time_expected * 2):

return previous_adjusted_block.difficulty - 1

else:

return previous_adjusted_block.difficulty

超长链攻击

存在一种情况,假如我们的数学难题被多个人同时解出来怎么办?这完全有可能,毕竟网络中的节点非常多。但是很少会有连续被多人同时解出来的情况,所以,在区块链中,对于这种“分叉”的情况,我们一般会选择最长的那一条链。

因此就会有恶意的攻击者通过自行设置低难度产生超长链,使网络中的节点信任他的恶意的超长链。面对这样的问题该如何解决呢?

我们可以实现一个难度评估函数,综合考虑整个链的实际难度,而不仅仅取决于链的长度。

def eval_real_difficulty(blocks):

tot = 0

for block in blocks:

tot += math.pow(2, block.difficulty)

return tot

节点通讯

因为在整个网络中有很多节点,大家都有可能去创建区块,这就需要大家通过协商通讯的方式达成共识,这需要以下三个基本能力:

某节点创建了一个区块,需要通知整个网络中的每个节点

新节点加入网络

节点收到消息后,可以验证链的合法性,更新自己的链

验证区块链是否合法

如果其他人给了我们一个区块链,我们如何验证这个链是正确的呢?这需要符合以下的基本要求:

区块之间的索引是+1递增的

每个区块的previous_hash需要和之前区块的Hash相同

每个区块自身的Hash需要正确,而且是一个正确的工作量证明

def is_valid_new_block(new_block, previous_block):

# 检查索引是否正确

if previous_block.index + 1 != new_block.index:

print("Wrong index: " + str(new_block.index))

return False

# 检查hash是否正常连接

elif previous_block.compute_hash() != new_block.previous_hash:

print("Wrong previous hash: " + new_block.previous_hash)

return False

# 检查hash是否是一个正确的proof

elif not new_block.compute_hash().startswith('0' * new_block.difficulty):

print("Not a proof: " + new_block.compute_hash())

return False

return True

def is_valid_chain(self, blocks):

# skip genesis block

for i in range(1, len(blocks)):

if not self.is_valid_new_block(blocks[i], blocks[i - 1]):

return False

return True

更新本地的链

如果其他人给我们的区块链是合法的,而且比本地的链要新(实际难度更大),那么就用这个新链替换本地的链。

def replace_chain(self, blocks):

if self.is_valid_chain(blocks) and self.eval_real_difficulty(self.blocks) < self.eval_real_difficulty(blocks):

self.blocks = blocks

return True

return False

为区块链创建控制接口

使用 HTTP API 的方式实现对区块链的管理,需要实现如下接口

查看本地区块链

添加区块

替换本地区块链

查看节点列表

注册节点

使用 Flask 微框架来快速实现这个功能,使用 @app.route() 装饰器快速创建 HTTP 控制器

如下代码,实现了操作本地区块链对象的 HTTP 接口

@app.route('/chain', methods=['GET', 'POST'])

def chain_handler():

if request.method == 'GET':

return Response(__block_list_to_json(local_chain.blocks), mimetype='application/json')

if request.method == 'POST':

blocks_in_json = request.form['chain']

blocks = json.loads(blocks_in_json, object_hook=Block.dict2block)

print("Recv blocks: " + str(len(blocks)))

# todo: check chain and replace local chain

if local_chain.replace_chain(blocks):

print("local chain replaced")

return "OK"

网络节点

节点列表是通过 Python 的集合来维护的,节点在刚启动的时候,只有一个节点,也就是本身。如果想要加入到网络中,就需要先联络网络中的其中一个节点。

@app.route('/peer', methods=['GET', 'POST'])

def peer_handler():

if request.method == 'GET':

dict_list = []

for peer in peers:

dict_list.append(peer)

return Response(json.dumps(dict_list), mimetype='application/json')

if request.method == 'POST':

url = request.form['url']

# todo: validate the url

print("Recv url: " + url)

peers.add(url)

return 'OK'

消息广播

有两个场景需要消息广播

发现了新的区块

有新的节点加入网络

当新的节点加入到本地的 peers 集合内之后,需要对集合内所有节点发送一个 POST 请求来注册新节点

def sync_peers():

# post net node to each peer

for peer in peers:

if peer == local_server:

continue

for url in peers:

r = requests.post('http://' + peer + '/peer',

data={'url': url})

print("peers sync OK")

当我们发现了新的区块,就需要通知区块链网络中的每个节点,可以通过遍历 peers 集合,构造 POST 请求并发送区块链。对方收到区块链后,会去验证区块链的有效性并更新它本地的链

def broadcast(blocks):

for peer in peers:

if peer == local_server:

continue

chain_json = __block_list_to_json(blocks)

r = requests.post('http://' + peer + '/chain',

data={'chain': chain_json})

print("local chan broadcast OK")

你可能感兴趣的:(python区块链框架)