一文读懂区块链以及一个区块链的实现

 2023-09-05 阅读 111 评论 0

摘要:区块链这个技术在2017年是比较火的,基于区块链技术的比特币的价格也是高的惊人,于是我就想对区块链技术做个深入了解。在网上看了大量文章后,发现大多数文章要么只讲理论,要么就只贴代码,都不太满意。于是我就写了这篇文章,代码结合

区块链这个技术在2017年是比较火的,基于区块链技术的比特币的价格也是高的惊人,于是我就想对区块链技术做个深入了解。在网上看了大量文章后,发现大多数文章要么只讲理论,要么就只贴代码,都不太满意。于是我就写了这篇文章,代码结合理论一起讲解。

定义

我有一个习惯,就是查找一个概念或一个专有名词的时候就喜欢去维基百科上找,而不喜欢去百度上面搜。维基百科对区块链有这样一段描述:

it as an open, distributed ledger that can record transactions between two parties efficiently and in a verifiable and permanent way

简单说区块链就是开放的分布式账本,并且可以以一种可核查的(verifiable)和永久的(permanent)方式记录双方的交易。这里有几个关键词(已经加重标出了),我们依次来讲解一下这几个词。开放的,与开放的相对的是封闭的(或者说加密的),我们平常使用的数据库(如MySQL),在访问数据库之前都要先输入密码,密码错了则不能访问,这个是封闭的的做法。而开放的则不是这样,任何人都可以免费的得到这份账本,无需输入密码。分布式说明无中心节点,每个节点都是平等的,并且都保存着一份完整的账本。账本说明区块链可以存储、读取信息,我们可以大胆的认为它就是一个数据库,只不过与我们传统的数据库不太一样而已。可核查的,我们知道既然每个节点都是平等的,那么每个节点都有权向账本中写入数据(向区块链的末尾添加一个区块),其他节点就要判断你添加的这个数据是否是合法的,只有合法的数据才会被认可,并且把这个数据也添加到自己的账本。

区块

一个区块需要包含以下信息:

  1. id,用于唯一表示这个区块
  2. prev_hash,前一个区块的hash
  3. nonce,工作量证明相关,用于工作量证明算法的计数器
  4. data,存储的数据,我们这个是很简单的区块链,所以存放的是简单的字符串
  5. timestamp,到这个区块的时间,用unix时间戳表示
  6. hash,256位(二进制,每一位都是0或1),每个区块的hash都不同,同id,也可用于唯一表示这个区块

上面只是对每个属性做了个简单的介绍,具体的介绍放到对应的小节讲,如nonce会在讲解工作量证明那节会详细讲到。我们按照上面的定义可以很快写出下面的类:

class Block(object):def _calc_hash(self, data, nonce, prev_hash):return hashlib.sha256(data + str(nonce) + prev_hash).hexdigest()def __init__(self, **kwargs):self.id = kwargs['id']self.prev_hash = kwargs['prev_hash']self.nonce = kwargs['nonce']self.data = kwargs['data']self.timestamp = kwargs['timestamp']self.hash = self._calc_hash(self.data, self.nonce, self.prev_hash)
复制代码

这里我假设输入是合法的,如data和prev_hash均有值且都是str类型,nonce有值且可用str()内置函数转成str类型。如果把这段代码用到生产环境中是非常不明智的,最好先对用户的输入做一系列的合法性判断,如果不合法该怎么处理,因为本篇主要讲解原理,所以代码追求尽可能的简单。

这里用到了hashlib这个库来计算hash值,关于hash有以下几个重要推论:

  1. 源不一样则hash出来的结果一定不一样
  2. 源一样则hash出来的结果也一样
  3. 源一旦有任何改变都会导致hash出来的结果变得面目全非,实际上这点可以通过前面两点推导而来
  4. 根据结果无法反向推导出源,即不可逆
  5. 用同一个hash函数(或hash算法)得到的结果的长度都是一样长的,不管源的长度如何

本程序中用到了sha256算法,该算法得出的结果长度恒为256位。 大家可能注意到了,我是说以上几点都是推论,至于为什么说是推论,因为理论上并不成立。比如说第1点,我们可以试想以下,因为hash是256位,每一位都要么是0要么是1,那么总共有2的256次方种可能, 如果我们有2的256次方加1次不同的输入,那么必然会有至少两次hash出来的结果是一样的,但是要计算2的256次方加1次hash所需要的时间很长很长(感兴趣的读者可以自己计算一下,设每次hash所需时间为T,那么2的256次方乘以T就是需要的时间),长到两次hash的结果完全一样的概率无限趋近于0,这一点只要知道就好。

基于以上几点,我们就知道,除非某两个区块的data + str(nonce) + prev_hash的结果完全一样,否则任意两个区块的hash值都不一样且是唯一的。而两个区块的data + str(nonce) + prev_hash的结果完全一样几乎是不可能的,所以使用hash来唯一表示一个区块也是可行的。

创建创世区块

创世区块是区块链的第一个区块,一般都是预先定义好,不存储任何有价值的信息。我们定义一个创建创世区块的函数,代码如下:

def create_genesis_block():params = {'id': 0,'data': '','nonce': 0,'prev_hash': '','timestamp': int(time.time())}return Block(**params)
复制代码

这段代码很好理解,就不详细说了。需要注意的是,因为创世区块是区块链的第一个区块,所以它的prev_hash为空字符串,prev_hash是表示前一个区块的hash。

工作量证明

前面说过区块链是可核查的,即区块链要有一种方法来证明你这个区块是合法的,是合法的区块才能append到区块链的末尾,本篇就来讲述如何判断一个区块是否是合法的。 工作量证明的核心是难于计算但是易于验证。换句话说就是,客户端通过大量的计算才能得到一个结果,但是要验证这个结果是否正确则是非常容易的。要验证一个区块 是否合法,我们要先定义一个规则,任何遵守这个规则的区块我们都认为是合法的。比如我们规定一个区块的hash必须以4个0(十六进制,二进制这是16个0)开头,如以下是合法的hash

0000ea046ff88074619b8c984ad64416d7b38a7cd2601339bc31718c0c1faad7
0000a19c1565f45229616010a136680a1540a6a4f580a6acbb84d0bf65f04b60
复制代码

以下则是不合法的hash

12deea046ff88074619b8c984ad64416d7b38a7cd2601339bc31718c0c1faad7
ef23a19c1565f45229616010a136680a1540a6a4f580a6acbb84d0bf65f04b60
复制代码

这个是很好验证的,符合工作量证明的核心易于验证。我们再来讨论得到这个hash值是否是难于计算。转换成二进制后,hash的前16位必须是0,根据概率学,那么大概需要2的16次方(65536)次计算才能得到一个合法的hash值,读者可以自己写代码验证一下,如:

import hashlibdef calc_hash(data, nonce, prev_hash):return hashlib.sha256(data + str(nonce) + prev_hash).hexdigest()def main():nonce = 0data = 'hello, blockchaindfewdffbv'prev_hash = ''while True:hash = calc_hash(data, nonce, prev_hash)if hash[:4] == '0000':breaknonce += 1print 'Got you:'print nonceprint hash
复制代码

根据概率学,跑的次数越多,平均值就会越接近65536,理论上是这样,实际上应该也是,有兴趣的读者可以自己测一下。所以平均要经过65536次计算才能得到一个合法的hash,如果改变一下规则,要求hash值(十六进制)的前6位必须是0,那么平均要经过2的24次方(16777216)才能得到一个合法的hash值,因此符合工作量证明的核心难于计算。 从理论上这个规则是OK的吗,我们就来写代码来实现它吧。

NUM_ZEROS = 4
class ProofOfWork(object):def __init__(self, block):self.block = blockdef is_block_valid(self):return self.block and self.block.hash and self.block.hash.startswith('0' * NUM_ZEROS)
复制代码

代码很简单,就不解释了。NUM_ZEROS越大,难度越高,需要的计算次数也越多。但是不管NUM_ZEROS设的多大,都是易于验证的。

这个ProofOfWork类基本可以工作,但是有个问题,Block有那么多个属性,我们仅仅是验证了它的其中一个属性(hash),而忽略了其他属性。没错,我们还要验证id和prev_hash,id 必须比前一个区块的id多1,pre_hash必须等于前一个区块的hash,我们来完善代码。

NUM_ZEROS = 4
class ProofOfWork(object):def __init__(self, block, prev_block):self.block = blockself.prev_block = prev_blockdef is_block_valid(self):return self.block and self.block.hash and self.block.hash.startswith('0' * NUM_ZEROS) \and self.block.id == self.prev_block.id + 1 \and self.block.prev_hash == self.prev_block.hash
复制代码

挖矿

现在我们只有一个区块,也就是创世区块,我们自然而然就想得到更多的块,这个就是矿了。挖矿的目的是要根据blockchain中的最后一个block,找到一个符合条件的新的block。 符合条件是指符合工作量证明,只有通过工作量证明的区块才能被其他节点认可和接受。挖矿的原理很简单,就是不断的递增nonce值,得到一个对应的区块,然后去验证这个区块是否满足工作量证明,满足则说明找到了一个符合条件的矿,没找到则继续递增nonce。代码如下:

def mine(prev_block):nonce = 0while True:data = 'block ' + str(prev_block.id + 1)params = {'id': prev_block.id + 1,'data': data,'nonce': nonce,'prev_hash': prev_block.hash,'timestamp': int(time.time())}block = Block(**params)if ProofOfWork(block, prev_block).is_block_valid():return blocknonce += 1
复制代码

因为挖矿需要做大量的计算,所以挖矿是很耗费能源的(电力)。

区块链

区块链,顾名思义,就是区块形成的链。上节讲了挖矿,就算挖得再多,不把它们链起来,依然没用。话不多说,直接上代码:

class BlockChain(object):def __init__(self):self.blocks = []def add_block(self, block):if not ProofOfWork(block, self.get_last_block()).is_block_valid():return Falseself.blocks.append(block)return Truedef get_last_block(self):return self.blocks[-1]def is_chain_valid(self):prev_block = self.blocks[0]for block in self.blocks[1:]:if not ProofOfWork(block, prev_block).is_block_valid():return Falseprev_block = blockreturn True
复制代码

我们把区块链定义成了一个list,并添加了几个方法:

  • add_block: 向区块链的末尾添加一个区块,只有经过工作量证明的区块才能添加
  • get_last_block:返回区块链中的最后一个区块
  • is_chain_valid: 判断该区块链是否合法

代码很简单,就不解释了。

后记

本文在讲解区块链的原理的基础上,也实现了一个简单的区块链。当然由于篇幅所限,只讲了区块链的部分,还有很多没讲,比如既然区块链是一个数据库的话,我们并没有讲到区块链的存储,以及各个节点的同步等。这个有机会放到下篇再讲(要准备年会了,我也不知道下篇是什么时候^_^)。实际上存储以及同步都已经实现了,代码在github上,希望读者可以star一下, 另希望也能关注一下本人的技术公众号。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/55.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息