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

2018阿里云全部产品优惠券(好东东,强烈推荐)
领取地址 https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=82lei0yp

推荐:一文读懂比特币、以太坊、区块链、代币、ICO(万字长文)

[猎云注:最近区块链发展得如火如荼,比特币让不少投资者为之疯狂。但是关于区块链的一切,你真的清楚吗?一名软件和系统工程师Preethi Kasireddy在国外平台medium上发表了

区块链这个技术在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。

推荐:一文读懂火热的区块链江湖,中美两国有哪些同与不同?

[近日,第三方数据研究机构 AppBi 发布中美App Store中的区块链App报告。数据显示,中美两国应用市场上和区块链相关的App共2993款,其中中国930款(31%),美国2063款(69%

工作量证明

前面说过区块链是 可核查的 ,即区块链要有一种方法来证明你这个区块是合法的,是合法的区块才能append到区块链的末尾,本篇就来讲述如何判断一个区块是否是合法的。

工作量证明的核心是 难于计算但是易于验证 。换句话说就是,客户端通过大量的计算才能得到一个结果,但是要验证这个结果是否正确则是非常容易的。要验证一个区块

是否合法,我们要先定义一个规则,任何遵守这个规则的区块我们都认为是合法的。比如我们规定一个区块的hash必须以4个0(十六进制,二进制这是16个0)开头,如以下是合法的hash

0000ea046ff88074619b8c984ad64416d7b38a7cd2601339bc31718c0c1faad7
0000a19c1565f45229616010a136680a1540a6a4f580a6acbb84d0bf65f04b60

以下则是不合法的hash

12deea046ff88074619b8c984ad64416d7b38a7cd2601339bc31718c0c1faad7
ef23a19c1565f45229616010a136680a1540a6a4f580a6acbb84d0bf65f04b60

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

import hashlib

def calc_hash(data, nonce, prev_hash):
    return hashlib.sha256(data + str(nonce) + prev_hash).hexdigest()

def main():
    nonce = 0
    data = 'hello, blockchain'
    prev_hash = ''

    while True:
        hash = calc_hash(data, nonce, prev_hash)        
        if hash[:4] == '0000':            
            break
        nonce += 1

    print 'Got you:'
    print nonce    
    print hash

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

从理论上这个规则是OK的吗,我们就来写代码来实现它吧。

NUM_ZEROS = 4

class ProofOfWork(object):
    def __init__(self, block):
        self.block = block    
    def 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 = 4class ProofOfWork(object):
    def __init__(self, block, prev_block):
        self.block = block
        self.prev_block = prev_block    

    def 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 = 0
    while 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 block
        nonce += 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 False
        self.blocks.append(block)        
        return True

    def 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 False
            prev_block = block        
        return True

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

  • add_block: 向区块链的末尾添加一个区块,只有经过工作量证明的区块才能添加

  • get_last_block:返回区块链中的最后一个区块

  • is_chain_valid: 判断该区块链是否合法

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

后记

本文在讲解区块链的原理的基础上,也实现了一个简单的区块链。当然由于篇幅所限,只讲了区块链的部分,还有很多没讲,比如既然区块链是一个数据库的话,我们并没有讲到区块链的存储,以及各个节点的同步等。这个有机会放到下篇再讲(要准备年会了,我也不知道下篇是什么时候^_^)。

推荐:【区块链技术实现】

[ 区块链技术, 简称BT(Blockchain technology),也被称之为分布式账本技术,是一种互联网数据库技术,其特点是去中心化、公开透明,让每个人均可参与数据库记录   基

相关推荐