动手学区块链学习笔记(二):区块链以及工作量证明算法
引言
紧接上文,在介绍完区块链中的加密解密以及公钥私钥等算法后,本篇开始正式进入区块链概念与一个简单区块链系统的实现过程介绍。
区块链技术介绍
什么是区块链?
区块链,就是一个又一个区块组成的链条。每一个区块中保存了一定的信息,它们按照各自产生的时间顺序连接成链条。这个链条被保存在所有的服务器中,只要整个系统中有一台服务器可以工作,整条区块链就是安全的。这些服务器在区块链系统中被称为节点,它们为整个区块链系统提供存储空间和算力支持。如果要修改区块链中的信息,必须征得半数以上节点的同意并修改所有节点中的信息,而这些节点通常掌握在不同的主体手中,因此篡改区块链中的信息是一件极其困难的事。相比于传统的网络,区块链具有两大核心特点:一是数据难以篡改、二是去中心化。基于这两个特点,区块链所记录的信息更加真实可靠,可以帮助解决人们互不信任的问题。
来源:百度百科
总结一句话就是,区块链本质上是一个去中心化的数据库。而它能提取以下几个特点:
- 信息不可修改,添加到区块链中的信息无法再被修改;
- 只支持「增」和「查」操作,不同于传统的数据库技术支持「增、删、查、改」,区块链技术只支持「增」操作(即往区块链里的添加区块信息),和「查」操作(即查询区块链里的区块信息);而不支持「删」和「改」操作;
- 没有权限限制,任何加入区块链网络的节点都有权限「增」和「查」区块信息。
更加通俗的理解,可以查看新华网在2019年发布的刷屏的区块链究竟是什么?你想知道的都在这里!
这里直接讲区块链的区块结构,见下表为:
数据项 | 字节 | 字段 | 说明 |
---|---|---|---|
Magic NO | 4 | 魔数 | 常数0xD9B4BEF9 |
Blocksize | 4 | 区块大小 | 用字节表示的该字段之后的区块大小 |
Blockheader | 80 | 区块头 | 组成区块头的几个字段,描述区块的元数据 |
Transaction counter | 1-9 | 交易计数器 | 该区块包含的交易数量,包含coinbase交易,描述紧跟在该域后面的交易数据的数目 |
Transactions | 不定 | 交易 | 记录在区块里的交易信息,使用原生的交易信息格式,并且交易在数据流中的位置必须与Merkle树的叶子节点顺序一致 |
其中Blocksize和Magic NO都是描述区块链的一个量词,余下的三个概念就构成了区块链:
![](/assets/blank.gif)
其中区块头的结构为:
大小 | 域名 | 描述 |
---|---|---|
4 字节 | Version | 版本号,升级了软件并指定了新版本 |
32 字节 | Previous Block Hash | 与本区块形成链的前一区块的散列值,更准确的讲是前一区块的区块头的散列值 |
32 字节 | Merkle Root Hash | 这个域的准确意义解释起来稍微有点复杂,暂时可以理解为区块体的散列值 |
4 字节 | Timestamp | 可以简单理解为该区块被创建时的 Unix 时间戳(即从 UTC 1970 年 1 月 1 日 0 时 0 分 0 秒起至现在的总秒数) |
4 字节 | Difficulty Target | 难度调整的一个目标值,后续工作量介绍 |
4 字节 | Nonce | 为了找到满足难度目标所设定的随机数,为了解决32位随机数在算力飞升的情况下不够用的问题,规定时间戳和coinbase交易信息均可更改,以此扩展nonce的位数 |
在了解比特币系统中的区块头结构中 Previous Block Hash
和 Merkle Root Hash
这两个域的大致意义后,区块如何形成区块链的具体细节便清晰了。
区块链的结构有以下两个主要特点:
区块链中的每个区块都通过
Previous Block Hash
记录了前一区块的区块头的散列值;区块链中的每个区块的区块头都通过
Merkle Root Hash
记录了该区块的区块体的散列值。
这种结构和数据结构中的链表是非常相似的,见下图所示:
![](/assets/blank.gif)
在链表中,初始结点为头结点,而区块链中,初始的块则叫做创世区块(Genesis Block),链表的头结点指向的为Null,因为它本身就是head,创世块同样,它没有前置区块,所以它的Previous Block Hash
指向的值便为空。
实现区块类与区块链类
区块类
基于上一节对于区块链结构的分析,下面我们可以使用 Python 中的 Block 类来实现我们第一个版本的区块链的区块了。Block 类包含以下几个域:
字段 | 解释 |
---|---|
Timestamp
|
当前时间戳,也就是区块创建的时间 |
PrevBlockHash
|
前一个块的哈希,即父哈希 |
Hash
|
当前块的哈希 |
Data
|
区块存储的实际有效信息,也就是交易 |
用代码表示为:
class Block(object):def __init__(self, data, prev_block_hash=''):self.timestamp = int(time.time())self.prev_block_hash = prev_block_hashself.data = dataself.data_hash = hashlib.sha256(data.encode('utf-8')).hexdigest()
代码中prev_block_hash=''
相当于在申明类时,就自动创建了一个创世区块,并且它指向了在python中代表空的一种字符,使用hashlib中的sha256来计算区块体和区块头的散列值。
目前,我们仅取了 Block 结构的部分字段(Timestamp
, Data
和 PrevBlockHash
),并将它们相互拼接起来,然后在拼接后的结果上计算一个 SHA-256,就得到了一个新的哈希值,这与比特币的实现方式也是一致的:
class Block(object):......def hash(self):data_list = [str(self.timestamp), self.prev_block_hash, self.data_hash]return hashlib.sha256(''.join(data_list).encode('utf-8')).hexdigest()......
为了能够以更好的可读性的打印出 Block 类,我们给它加上 __repr__
方法:
class Block(object):......def __repr__(self):s = 'Block(Hash={}, TimeStamp={}, PrevBlockHash={}, Data={}, DataHash={})'return s.format(self.hash(), self.timestamp, self.prev_block_hash,self.data, self.data_hash)
至此,一个区块类就完成了,可以将其理解为一种数据结构,跟单链表相似,但是功能与携带信息比单链表更多。
区块链类
如果说区块类为单节点行为,而区块链类相当于赋予了该类与其它节点通信的权利,区块链类为实现区块链整个系统的主要的一个类,所以这里直接给出最简版本为:
from block import Block
from datetime import datetimeclass BlockChain(object):def __init__(self):print('Created a new blockchain.\n')self.chain = []genesis_block = Block('This is a genesis block')self.chain.append(genesis_block)def add_block(self, data):new_block = Block(data, self.chain[-1].hash())self.chain.append(new_block)def print_chain(self):for block in self.chain:print('Block Hash: {}'.format(block.hash()))print('Prev Block Hash: {}'.format(block.prev_block_hash))print('TimeStamp: {}'.format(datetime.fromtimestamp(block.timestamp)))print('Data: {}'.format(block.data))print('')
至此,我们创建一个main函数直接去调用上述代码为:
from block import Block
from blockchain import BlockChainif __name__ == '__main__':# 创建区块链block_chain = BlockChain()# 添加第一个区块到区块链中block_chain.add_block('Send 1 BTC to Ivan')# 添加第二个区块到区块链中block_chain.add_block('Send 2 more BTC to Ivan')# 打印整个区块链的信息block_chain.print_chain()
输出为:
这里初步实现了区块链的功能,但为什么还需要使用GPU以及分布式等更多设备呢?那就是下面要讲的矿工核心算法。
工作量证明算法
在上一节,我们构造了一个非常简单的数据结构 – 区块,它也是整个区块链数据库的核心。目前所完成的区块链原型,已经可以通过链式关系把区块相互关联起来:每个块都与前一个块相关联。
但是,依照上述代码实现的区块链有一个巨大的缺陷:向链中加入区块太容易,也太廉价了。区块链的一个关键点就是,一个人必须经过一系列困难的工作,才能将数据放入到区块链中。正是由于这种困难的工作,才保证了区块链的安全和一致。此外,完成这个工作的人,也会获得相应奖励(这也就是通过挖矿获得币)。
这个机制与生活现象非常类似:一个人必须通过努力工作,才能够获得回报或者奖励,用以支撑他们的生活。在区块链中,是通过网络中的参与者(矿工)不断的工作来支撑起了整个网络。矿工不断地向区块链中加入新块,然后获得相应的奖励。在这种机制的作用下,新生成的区块能够被安全地加入到区块链中,它维护了整个区块链数据库的稳定性。值得注意的是,完成了这个工作的人必须要证明这一点,即他必须要证明他的确完成了这些工作。
整个 “努力工作并进行证明” 的机制,就叫做工作量证明(proof-of-work)。
工作量证明流程
工作量证明的核心思想就是给区块链添加新区块的这一操作设置一定的难度。谁都有权限往区块链添加新区块,但并不是谁都有能力这么做,而必须付出一定的代价,这个代价就是工作量证明。
怎么给「区块链添加新区块」的这一操作设置难度呢?其实也很简单,就是规定新区块的区块头的散列值必须满足某种特征。由于散列值具有「单向性」和「雪崩效应」,因此计算机只能通过穷举计算的方法来获得具备某种特征的散列值,而工作量证明算法也就是穷举计算散列值的过程:
![](/assets/blank.gif)
这个穷举计算散列值的过程在比特币系统中也被称为挖矿。比特币的实现要求的新区块的区块头散列值特征是散列值必须是一定数目的前导 0
。前导 0
的数目被称为挖矿的难度,该数值越大表示挖矿的难度也就越大(即要找到能够加入比特币区块链的新区块的时间就越长)。比特币区块头结构中,有两个域与挖矿相关:
大小 | 域名 | 描述 |
---|---|---|
4 字节 | Difficulty Target |
表示当前挖矿的难度,但该值不是直接表示前导 0 的数目,而是一个通过特殊编码处理的值,计算方法见如下图
|
4 字节 | Nonce | 为了找到满足难度目标所设定的随机数,为了解决32位随机数在算力飞升的情况下不够用的问题,规定时间戳和coinbase交易信息均可更改,以此扩展nonce的位数 |
关于Difficulty Target,表格里已经解释得很清楚,需要注意的是,这里的nonce是区块中的nonce,主要是调整挖矿难度;还有一种是每笔交易中nonce,每个外部账户(私钥控制的账户)都有一个nonce值,从0开始连续累加,每累加一次,代表一笔交易。关于后者,会之后介绍。
所以我们可以更新区块类中的哈希方法,将Nonce加入列表:
import time
import hashlibclass Block(object):def __init__(self, data, prev_block_hash=''):self.timestamp = int(time.time())self.prev_block_hash = prev_block_hashself.data = dataself.nonce = 0 # 添加 nonce 成员,初始值为 0self.data_hash = hashlib.sha256(data.encode('utf-8')).hexdigest()def hash(self): # 计算散列值时,同样加入 nonce值data_list = [str(self.nonce),str(self.timestamp),self.prev_block_hash, self.data_hash]return hashlib.sha256(''.join(data_list).encode('utf-8')).hexdigest()def __repr__(self): # 打印输出,同样加入 nonce值return 'Block(Hash={}, TimeStamp={}, PrevBlockHash={}, Nonce={}, \Data={}, DataHash={})'.format(self.hash(), self.timestamp,self.prev_block_hash, self.nonce, self.data, self.data_hash)
然后定义工作量的nonce
和difficulty_bits
:
class ProofOfWork(object):MAX_NONCE = sys.maxsizedef __init__(self, difficulty_bits=12):self._target = 1 << (256-difficulty_bits)
这里的MAX_NONCE是用于限定在求解 nonce
值时的上限,sys.maxsize
与操作系统有关,如果是32位的操作系统,那么该值将为2^31-1
,即2147483647。如果是64位, 将为2^63-1
,即9223372036854775807
difficulty_bits
的计算规则如下图:
可能看到左边的1 << (256 - n)
,就知道这是移位的符号,学过计算机组成原理的,还能说出具体的操作,为算术左移:依次左移n位,尾部补0,最高的符号位保持不变。
这就是工作量类的核心,也是证明是否付出劳力的两道质检工序,代码为:
def mine_block(self, data, prev_hash=''):tmp_block = Block(data, prev_hash)while tmp_block.nonce<ProofOfWork.MAX_NONCE:hash_int = int(tmp_block.hash(), 16) # 16进制if hash_int < self._target: # 区块头的散列值满足要求,小于target,算法完成breakelse : # 区块头的散列值不满足要求,nonce加1,继续循环tmp_block.nonce+=1return tmp_block
这里还可以加一个验证功能,在后续将挖到的矿持久化以及上传数据库后,该值可随时进行验证,这里简单写为:
def validate_block(self, block):hash_int = int(block.hash(), 16)return True if hash_int<self._target else False
有了工作量证明我们的 Blockchain 类,在生成新区块的时候就都需要通过 proofOfWork 类的 mine_block 方法来生成区块了,另外为了更易于理解我们相应的将 Blockchain 类的 add_block 方法也更名为 mine_block 方法,在 print_chain 方法中我们加入了对区块的工作量证明校验结果,因此修改后Blockchain 类的完整代码如下:
from block import Block
from datetime import datetime
from proofofwork import ProofOfWorkclass BlockChain(object):def __init__(self):print('Created a new blockchain.\n')self.chain = []self.pow = ProofOfWork() # 创建工作量证明类genesis_block = self.pow.mine_block('This is a genesis block') # 通过工作量证明类来挖出创世区块self.chain.append(genesis_block)# 挖出一个数据为data的区块def mine_block(self, data):new_block = self.pow.mine_block(data, self.chain[-1].hash())self.chain.append(new_block)def print_chain(self):for block in self.chain:print('Block Hash: {}'.format(block.hash()))print('Prev Block Hash: {}'.format(block.prev_block_hash))print('TimeStamp: {}'.format(datetime.fromtimestamp(block.timestamp)))print('Nonce: {}'.format(block.nonce)) # 增加打印 nonce值print('Data: {}'.format(block.data))print('POW: {}'.format(self.pow.validate_block(block))) # 校验区块print('')
我们更改main.py
运行为:
from block import Block
from blockchain import BlockChainif __name__ == '__main__':# 创建区块链block_chain = BlockChain()# 挖出第一个区块block_chain.mine_block('Send 1 BTC to Ivan')# 挖出第二个区块block_chain.mine_block('Send 2 more BTC to Ivan')# 打印整个区块链的信息block_chain.print_chain()
这里以每4位为首位添一位0,因为默认的difficulty_bits
为12,可以看到截图中,有3位的16进制0开头,换算为12位的二进制0,而每向上添加4位,都将增加其计算量,比如说将其改为24,增大两倍,我们使用Linux默认计算时间工具测试:
time python3 main.py
很明显,对比上一个,时间慢了一大截,因为现在还是单线程,看单核情况发现,直接将我这个学生机的CPU的1核跑满了:
所以,这也是为什么在比特币大火的20年到22年间,各种云服务器被植入挖矿程序,导致每次上去查看,要不就显卡跑满,要不就CPU整个也全部跑满,原因就是太密集的哈希计算,让日常使用都不会触顶的服务器,第一次有了算不过来的感觉,emmm。。。
当然,之后的交易以及网络等篇幅还会对工作量算法进行修改,比如说预防作弊等,具体的可以见如下图,但也不会做得那么完全,那么至此,本篇结束。
![](/assets/blank.gif)
动手学区块链学习笔记(二):区块链以及工作量证明算法相关推荐
- 区块链学习笔记:区块链到底能干什么
来源:区块律动BlockBeats 作者:Jade 编者语: 区块链媒体被大量封号,BAT 联手全网封杀,再到昨天的国家互金举报平台将「代币发行融资」纳入举报范围,还有网上谣传的所谓「利箭行动」. 最 ...
- 区块链学习笔记:区块链浏览器
一.什么是区块链浏览器 浏览器对于人们来说真是熟悉的不能再熟悉,每每我们需要上网寻找知识,搜集资料都会用到浏览器,我们天天都可能会用到浏览器,使用的频率也十分频繁,所以我们对浏览器很熟悉,它是用来浏览 ...
- 区块链学习笔记二之区块链的加密技术
概述 区块链最常见的用途是消除交易双方的中间环节.举个例子来说,学位认证的过程.当你投递简历到企业时,企业一般需要验证你的学位在类似于学信网等第三方验证平台可查,这相当于依托第三方验证平台验证你的过往 ...
- 区块链学习笔记19——ETH难度调整
区块链学习笔记19--ETH难度调整 学习视频:北京大学肖臻老师<区块链技术与应用> 笔记参考:北京大学肖臻老师<区块链技术与应用>公开课系列笔记--目录导航页 前面学过,比特 ...
- 区块链学习笔记(三)——从商鞅变法谈“共识机制”
区块链学习笔记(三)--从商鞅变法谈"共识机制" 前言 一.商鞅变法的故事 总结一下 二.共识机制 1)什么是共识机制 2)要点 总结 前言 区块链健康运行的灵魂是"共识 ...
- 区块链学习笔记23——ETH反思
区块链学习笔记23--ETH反思 学习视频:北京大学肖臻老师<区块链技术与应用> 笔记参考:北京大学肖臻老师<区块链技术与应用>公开课系列笔记--目录导航页 智能合约真的智能吗 ...
- 区块链学习笔记15——ETH状态树
区块链学习笔记15--ETH状态树 学习视频:北京大学肖臻老师<区块链技术与应用> 笔记参考:北京大学肖臻老师<区块链技术与应用>公开课系列笔记--目录导航页 引入 要实现的功 ...
- 区块链学习笔记4——BTC实现
区块链学习笔记4--BTC实现 学习视频:北京大学肖臻老师<区块链技术与应用> 笔记参考:北京大学肖臻老师<区块链技术与应用>公开课系列笔记--目录导航页 UTXO 区块链是一 ...
- 区块链学习笔记21——ETH智能合约
区块链学习笔记21--ETH智能合约 学习视频:北京大学肖臻老师<区块链技术与应用> 笔记参考:北京大学肖臻老师<区块链技术与应用>公开课系列笔记--目录导航页 智能合约简介 ...
最新文章
- VS2015快捷键使用学习总结
- MatLab实现布朗运动
- sql server 2008 远程连接配置
- boost::transpose_graph用法的测试程序
- HBase性能优化方法总结(二):写表操作
- 【sqlserver】在没有数据库备份的情况下,获得操作记录信息【code】
- 企业网站常用中英文对照表
- martingale与Markov Process的关系
- 百度2015校园招聘软件开发笔试题及答案
- 不同网段Linux通过路由表,Linux服务器架设---《路由表配置,实现不同网段不同网卡之间的ping...
- 利用SCCM 2012 SP1为客户端进行软件批量自动安装
- java.io.IOException: Server asks us to fall back to SIMPLE auth, but this client is confi的问题
- Bailian3659 判断是否为C语言的合法标识符【文本处理】
- bg、jobs、fg
- Python:字符宽度相同的字体(等宽字体)
- 不写代码,实现动态网页设计-金蜘蛛网页设计器数据库设置
- 智能车学习日记【四】————环岛
- 使用Trinamic TMC2300步进驱动器做一个迪斯科灯项目
- 《深度学习》学习笔记
- Python与Arduino绘制超声波雷达扫描
热门文章
- qboimathtest1 t2 配对
- 大牛给计算机方向学生的 7 个建议
- cad计算机在哪,Win7系统中cad临时文件保存在哪里
- java script error_java script error 错误解决方法
- 一篇文章,搞懂自动化行业现状
- 嵌入式开发,从开发板到产品的过程是什么样的?
- “唯一艺术数字藏品“小程序被下架,数字藏品服务边界在哪
- html代码的魔方加密,魔方加密解密测试调试方法
- Tekton之一:如何部署起来 Tekton
- UnicodeEncodeError: 'ascii' codec can't encode character '\u2013'