用Python去构建一个区块链

2018-05-10 14:43:33 Python 阅读 (3646) 评论(1)

        区块链是什么,比特币是什么,各种虚拟货币是什么,我这里一概不做解答。

        区块链本身是一种技术。

        利用区块链这个概念炒币的都是心怀鬼胎的。

        我们回归技术本身,这里结合我自己对区块链的理解,还有参考别人的文章,用Python简易构建一个区块链。

        Now, begin!

一.  区块结构,交易信息,链构成,区块hash,工作量证明pow算法等几个概念

       

1.1 区块结构由区块索引,Unix时间戳,交易信息,工作量证明,上一个区块的hash组成,交易信息包含发送者信息,接收者信息和交易数量。其中区块中的previous_hash表示上一个区块整体的hash值,这又是说,所有的区块中(除去创世块)都包含有上一个区块的hash值(有点类似斐波那契数列,每一个值都跟前一个值有对应的关系),这样一一链接在一起形成有序,规整,不可逆的链式结构,这就是所谓的区块链。            

block = {
   'index'     : index, # 区块索引
   'timestamp' : time(),# unix时间戳
   'transactions': { # 交易信息
                        'sender': sender, # 发送者
                        'recipient': recipient, # 接收者
                        'amount': amount  # 数量
                    }),
   'proof' : proof, # 工作量证明
   'previous_hash' : 'xxxxx' # 上一个区块的hash
}

    

1.2 区块的hash

 比特币采用的加密算法是sha256,这个算法的破解难度及其大,自行了解。我们这里也采用sha256进行区块的hash加密。

    1. def hash(block):
          """生成区块的SHA-256 hash值"""
          block_string = json.dumps(block, sort_keys=True).encode()
          return hashlib.sha256(block_string).hexdigest()

1.3 理解工作量证明(proof of work)

新区块的产生都需要经过工作量证明,俗称“挖矿”。

新的区块以来工作量证明算法(poW)来构造,poW的目标是找出一个符合特定的数字,这个数字很难计算出来,但容易验证,这就是工作量证明的核心思想。而且这个工作量证明算法也是当前区块跟上一个区块相关联的,我们这里这样简易设计了一个pow算法。

pow:查找一个P'使得hash(pp')以4个0开头,其中p是上一个区块的证明,p'是当前的证明。

我们这里定义两个方法去模拟实现这个“挖矿”过程

# 简易实现pow算法
def proof_of_work(self, last_proof):
    """
    简单的工作量证明
    查找一个P'使得hash(pp')以4个0开头
    p是上一个区块的证明,p'是当前的证明
    """
    proof = 0
    while self.valid_proof(last_proof, proof) is False:
        proof += 1
    return proof

# 验证工作证明是否正确
def valid_proof(last_proof, proof):
    """验证证明:是否hash(last_proof,proof)以4个0 开头"""
    guess = f'{last_proof}{proof}'.encode()
    guess_hash = hashlib.sha256(guess).hexdigest()
    return guess_hash[:4] == '0000'


二.  区块产生


2.1 节点注册,挖矿得先有矿工啊

我们提供一个节点注册的方法,这里比较简单,用网络地址代表一个矿工节点。

register_node(, address):
    parsed_url = urlparse(address)
    .nodes.add(parsed_url.netloc)


2.2 新交易信息生成,新区块生成

new_transaction方法负责添加一个新的交易信息

new_block负责把最新的一个交易信息加入到区块链中

 # 生成新区块
    def new_block(self, proof, previous_hash=None):
        """生成新块"""
        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1])
        }
        # 重置当前交易区块
        self.current_transactions = []
        # 添加新区块
        self.chain.append(block)
        return block

    # 生成新交易信息,信息将加入到下一个待挖的区块中
    def new_transaction(self, sender, recipient, amount):
        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount
        })
        return self.last_block['index'] + 1


三. 矿工

        矿工这边实际上相当于一个节点(客户端),我们是用Flask框架模拟这个节点。

        新建一个py文件写客户端(节点)代码,上面的都是区块链服务端的代码。

        客户端代码,我们写成api形式,只返回json数据。


3.1 节点注册

# 注册节点
@app.route('/nodes/register', methods=['POST'])
def register_nodes():
    address = request.values.get('address')
    if address is None:
        return '缺失参数', 400
    blockchain.register_node(address)

    response = {
        'message': '新的节点已经被添加',
        'total_nodes': list(blockchain.nodes)
    }
    return jsonify(response), 201

3.2 去挖矿

前面我们说到,每个区块都需要经过工作量证明,客户端挖矿就是不断的去找一个的数使pow算法给定的条件成立,直至条件成立。根据pow算法的难易度,让计算机去执行不同计算量的的计算,这也就是为什么这个动作为什么被称为“挖矿了”。所以说,配置更高,性能越好的计算机拥有更快的运算力量,在与其他矿工的竞争中就更具优势。

这样的挖矿动作消耗了全球很大的计算机的运力和电力!

我们这里发起一个请求,然后去调用pow算法,直至解出正确答案。

如果解出正确答案,就相当于挖出一个新的区块(既是挖矿者获得一个),并添加交易,然后添加到区块链中。

# 去挖掘新的区块
@app.route('/mine', methods=['GET'])
def mine():
    last_block = blockchain.last_block  # 已存在的最新的区块
    last_proof = last_block['proof']
    # 模拟挖矿动作,如果挖掘到新的区块并返回新区块的工作量证明
    proof = blockchain.proof_of_work(last_proof)
    # 当前节点挖到新的区块,生成一个新的交易信息
    blockchain.new_transaction(
        sender='0',   # 发送者为0表明是新挖出的区块
        recipient=node_identifier,
        amount=1
    )
    # 把当前节点挖到的区块加入到区块链中
    block = blockchain.new_block(proof, None)
    # 返回响应数据
    response = {
        'message': '新的区块已经创建',
        'index': block['index'],
        'transactions': block['transactions'],
        'proof': block['proof'],
        'previous_hash': block['previous_hash']
    }
    return jsonify(response), 200

3.3 解决冲突,实现共识

我们这里模拟了一个挖矿节点,实际中可能存在很多个节点,每个节点都保存有一个链,如何去保证这个链的一致性呢?

这里我们规定,网络中所有的链中的最长的,有效的链为实际的链。

有效是指每个区块都是正确的,没有经过任何篡改的,所以需要校验每个区块的hash和上一个区块的hash,并且还要校验每个区块的工作量证明正确与否。

最长的就是指最长的链。

定义两个方法去解决冲突,实现共识:

# 简易实现共识算法
def resolve_conflicts(self):
    """
    冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的,
    有效的链才是最终的链。网络中有效最长链才是实际的链。
    """
    neighbours = self.nodes  # 网络中所有的节点
    new_chain = None
    max_length = len(self.chain)
    # 遍历所有的节点
    for node in neighbours:
        response = requests.get(f'http://{node}/chain')
        if response.status_code == 200:
            length = response.json()['length']
            chain = response.json()['chain']
            if length > max_length and self.valid_chain(chain):
                max_length = length
                new_chain = chain
    # 如果发现有效更长链,就替换掉自己的链
    if new_chain:
        self.chain = new_chain
        return True

    return False

# 验证区块链是否是有效区块链
def valid_chain(self, chain):
    """遍历每个块,验证hash和proof"""
    last_block = chain[0]  # 第一个区块
    current_index = 1
    while current_index < len(chain):
        block = chain[current_index]

        # 1.验证当前区块的previous_hash是否和上一个区块的hash相等
        if block['previous_hash'] != self.hash(last_block):
            return False
        # 2.验证工作量证明
        if not self.valid_proof(last_block['proof'], block['proof']):
            return False

        last_block = block
        current_index += 1

    return True

然后在我们的客户端代码中,去发起一个请求解决冲突,就是比对网络上所有节点的链,如果找到有效更长链,就将这个有效更长链替换掉自己节点原来的链。

# 解决冲突
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
    replaced = blockchain.resolve_conflicts()
    if replaced:
        response = {
            'message': '发现有效更长链,我们自己的链已被替换为该链',
            'new_chain': blockchain.chain,
        }
    else:
        response = {
            'message': '我们的链是有效更长链',
            'chain': blockchain.chain
        }
    return jsonify(response), 200


四. 进行挖矿


运行flask应用,

image.png

访问127.0.0./mine模拟挖矿动作,访问3次后,此时应该有4个块(包含一个创世块)

image.png

定义应该方法去查看全部区块


image.png

还可以去发起一个交易,这里就不赘述了。



总结:区块链作为一个技术而言,还是有研究的价值的。对于其应用场景,见仁见智,我不说。另外,这个代码全在托管在gitee上的blockchain上。

我个人水平受限,可能有些地方理解也不是很透彻,如有错误,希望指正

还有就是可以访问我的项目查看我最近写的代码。

评论

老宋的迷妹 18-05-10 17:06:33 老宋NB,顶起来!!!